Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TilesFadePlugin: Add support for unload tiles plugin #884

Merged
merged 9 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions example/googleMapsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TilesFadePlugin,
UpdateOnChangePlugin,
TileCompressionPlugin,
UnloadTilesPlugin,
} from '3d-tiles-renderer/plugins';
import {
Scene,
Expand Down Expand Up @@ -63,6 +64,7 @@ function reinstantiateTiles() {
tiles.registerPlugin( new GoogleCloudAuthPlugin( { apiToken: params.apiKey, autoRefreshToken: true } ) );
tiles.registerPlugin( new TileCompressionPlugin() );
tiles.registerPlugin( new UpdateOnChangePlugin() );
tiles.registerPlugin( new UnloadTilesPlugin() );

if ( params.useBatchedMesh ) {

Expand Down
64 changes: 31 additions & 33 deletions src/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,37 @@ transformLatLonHeightToOrigin( lat, lon, height = 0 ) : void

Transforms the centers the tile set such that the given coordinates and height are positioned at the origin with "X" facing west and "Z" facing north.

## UnloadTilesPlugin

Plugin that unloads geometry, textures, and materials of any given tile when the visibility changes to non-visible to save GPU memory. The model still exists on the CPU until it is completely removed from the cache.

### .estimatedGpuBytes

```js
estimatedGPUBytes : number
```

The number of bytes that are actually uploaded to the GPU for rendering compared to `lruCache.cachedBytes` which reports the amount of texture and geometry buffer bytes actually downloaded.

### .constructor

```js
constructor( options : Object )
```

Available options are as follows:

```js
{
// The amount of time to wait in milliseconds before unloading tile content from the GPU. This option can be
// used to account for cases where the user is moving the camera and tiles are coming in and out of frame.
delay: 0,

// The amount of bytes to unload to.
bytesTarget: 0,
}
```

## BatchedTilesPlugin

_available in the examples directory_
Expand Down Expand Up @@ -581,36 +612,3 @@ Available options are as follows:
discardOriginalContent: true
}
```

## UnloadTilesPlugin

_available in the examples directory_

Plugin that unloads geometry, textures, and materials of any given tile when the visibility changes to non-visible to save GPU memory. The model still exists on the CPU until it is completely removed from the cache.

### .estimatedGpuBytes

```js
estimatedGPUBytes : number
```

The number of bytes that are actually uploaded to the GPU for rendering compared to `lruCache.cachedBytes` which reports the amount of texture and geometry buffer bytes actually downloaded.

### .constructor

```js
constructor( options : Object )
```

Available options are as follows:

```js
{
// The amount of time to wait in milliseconds before unloading tile content from the GPU. This option can be
// used to account for cases where the user is moving the camera and tiles are coming in and out of frame.
delay: 0,

// The amount of bytes to unload to.
bytesTarget: 0,
}
```
1 change: 1 addition & 0 deletions src/plugins/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { UpdateOnChangePlugin } from './three/UpdateOnChangePlugin';
export { TileCompressionPlugin } from './three/TileCompressionPlugin';
export { GLTFExtensionsPlugin } from './three/GLTFExtensionsPlugin';
export { ReorientationPlugin } from './three/ReorientationPlugin';
export { UnloadTilesPlugin } from './three/UnloadTilesPlugin';
export { TilesFadePlugin } from './three/fade/TilesFadePlugin';
export * from './three/DebugTilesPlugin';

Expand Down
1 change: 1 addition & 0 deletions src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { UpdateOnChangePlugin } from './three/UpdateOnChangePlugin.js';
export { TileCompressionPlugin } from './three/TileCompressionPlugin.js';
export { GLTFExtensionsPlugin } from './three/GLTFExtensionsPlugin.js';
export { ReorientationPlugin } from './three/ReorientationPlugin.js';
export { UnloadTilesPlugin } from './three/UnloadTilesPlugin.js';
export { TilesFadePlugin } from './three/fade/TilesFadePlugin.js';
export * from './three/DebugTilesPlugin.js';

Expand Down
8 changes: 8 additions & 0 deletions src/plugins/three/UnloadTilesPlugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class UnloadTilesPlugin {

constructor( options: {
delay: number,
bytesTarget: number,
} );

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class UnloadTilesPlugin {

}

constructor( options ) {
constructor( options = {} ) {

const {
delay = 0,
Expand Down
17 changes: 16 additions & 1 deletion src/plugins/three/fade/FadeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ export class FadeManager {
const fadeState = this._fadeState;
if ( ! fadeState.has( object ) ) return;

const visible = fadeState.get( object ).fadeOutTarget === 0;

fadeState.delete( object );
object.traverse( child => {

Expand All @@ -204,7 +206,7 @@ export class FadeManager {

if ( this.onFadeComplete ) {

this.onFadeComplete( object );
this.onFadeComplete( object, visible );

}

Expand Down Expand Up @@ -291,6 +293,19 @@ export class FadeManager {

}

isFading( object ) {

return this._fadeState.has( object );

}

isFadingOut( object ) {

const state = this._fadeState.get( object );
return state && state.fadeOutTarget === 1;

}

// Tick the fade timer for each actively fading object
update() {

Expand Down
82 changes: 61 additions & 21 deletions src/plugins/three/fade/TilesFadePlugin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Group, Matrix4, Vector3, Quaternion } from 'three';
import { Matrix4, Vector3, Quaternion } from 'three';
import { FadeManager } from './FadeManager.js';

const HAS_POPPED_IN = Symbol( 'HAS_POPPED_IN' );
Expand All @@ -14,10 +14,17 @@ function onTileVisibilityChange( scene, tile, visible ) {
// it's possible we disable them when adjusting visibility based on frustum
scene.visible = true;

const fadeManager = this._fadeManager;
if ( fadeManager.isFadingOut( scene ) ) {

this._fadingOutCount --;

}

if ( ! visible ) {

this._fadeGroup.add( scene );
this._fadeManager.fadeOut( scene );
this._fadingOutCount ++;
fadeManager.fadeOut( scene );

} else {

Expand Down Expand Up @@ -59,12 +66,15 @@ function onDisposeModel( scene ) {

}

function onFadeComplete( object ) {
function onFadeComplete( object, visible ) {

if ( ! visible ) {

// when the fade finishes ensure we dispose the tile and remove it from the fade group
if ( object.parent === this._fadeGroup ) {
// now that the tile is hidden we can run the built-in setTileVisible function for the tile
const tile = this._tileMap.get( object );
this.tiles.invokeOnePlugin( plugin => plugin !== this && plugin.setTileVisible && plugin.setTileVisible( tile, false ) );

this._fadeGroup.remove( object );
this._fadingOutCount --;

}

Expand Down Expand Up @@ -101,7 +111,6 @@ function onUpdateBefore() {
function onUpdateAfter() {

const fadeManager = this._fadeManager;
const fadeGroup = this._fadeGroup;
const displayActiveTiles = this._displayActiveTiles;
const fadingBefore = this._fadingBefore;
const tiles = this.tiles;
Expand Down Expand Up @@ -131,13 +140,24 @@ function onUpdateAfter() {

tiles.visibleTiles.forEach( t => {

t.cached.scene.visible = t.__inFrustum;
// if a tile is fading out then it may not be traversed and thus will not have
// the frustum flag set correctly.
const scene = t.cached.scene;
if ( fadeManager.isFadingOut( scene ) ) {

scene.visible = true;

} else {

scene.visible = t.__inFrustum;

}

} );

}

if ( this.maximumFadeOutTiles < fadeGroup.children.length ) {
if ( this.maximumFadeOutTiles < this._fadingOutCount ) {

// determine whether all the rendering cameras are moving
// quickly so we can adjust how tiles fade accordingly
Expand Down Expand Up @@ -221,12 +241,13 @@ export class TilesFadePlugin {
};

this.name = 'FADE_TILES_PLUGIN';
this.priority = - 1;

this.tiles = null;
this._fadeManager = new FadeManager();
this._prevCameraTransforms = null;
this._fadeGroup = null;
this._tileMap = null;
this._fadingOutCount = 0;

this.maximumFadeOutTiles = options.maximumFadeOutTiles;
this.fadeRootTiles = options.fadeRootTiles;
Expand All @@ -236,10 +257,6 @@ export class TilesFadePlugin {

init( tiles ) {

const fadeGroup = new Group();
fadeGroup.name = 'TilesFadeGroup';
tiles.group.add( fadeGroup );

const fadeManager = this._fadeManager;
fadeManager.onFadeSetStart = () => {

Expand All @@ -259,7 +276,6 @@ export class TilesFadePlugin {

this.tiles = tiles;
this._fadeManager = fadeManager;
this._fadeGroup = fadeGroup;
this._tileMap = new Map();
this._prevCameraTransforms = new Map();

Expand All @@ -277,28 +293,54 @@ export class TilesFadePlugin {

this._onLoadModel = e => onLoadModel.call( this, e.scene, e.tile );
this._onDisposeModel = e => onDisposeModel.call( this, e.scene );
this._onTileVisibilityChange = e => onTileVisibilityChange.call( this, e.scene, e.tile, e.visible );
this._onAddCamera = e => onAddCamera.call( this, e.camera );
this._onDeleteCamera = e => onDeleteCamera.call( this, e.camera );
this._onUpdateBefore = () => onUpdateBefore.call( this );
this._onUpdateAfter = () => onUpdateAfter.call( this );

tiles.addEventListener( 'load-model', this._onLoadModel );
tiles.addEventListener( 'dispose-model', this._onDisposeModel );
tiles.addEventListener( 'tile-visibility-change', this._onTileVisibilityChange );
tiles.addEventListener( 'add-camera', this._onAddCamera );
tiles.addEventListener( 'delete-camera', this._onDeleteCamera );
tiles.addEventListener( 'update-before', this._onUpdateBefore );
tiles.addEventListener( 'update-after', this._onUpdateAfter );

}

setTileVisible( tile, visible ) {

const scene = tile.cached.scene;
const wasFading = this._fadeManager.isFading( scene );
onTileVisibilityChange.call( this, scene, tile, visible );

// if a tile was already fading then it's already marked as visible and in the scene
if ( wasFading ) {

return true;

}

// cancel the visibility change trigger because we're fading and will call this after
// fade completes.
const isFading = this._fadeManager.isFading( scene );
if ( ! visible && isFading ) {

return true;

}

return false;

}

dispose() {

const tiles = this.tiles;

this._fadeManager.completeAllFades();

tiles.removeEventListener( 'load-model', this._onLoadModel );
tiles.removeEventListener( 'dispose-model', this._onDisposeModel );
tiles.removeEventListener( 'tile-visibility-change', this._onTileVisibilityChange );
tiles.removeEventListener( 'add-camera', this._onAddCamera );
tiles.removeEventListener( 'delete-camera', this._onDeleteCamera );
tiles.removeEventListener( 'update-before', this._onUpdateBefore );
Expand All @@ -311,8 +353,6 @@ export class TilesFadePlugin {

} );

this._fadeGroup.removeFromParent();

}

}
Loading