Skip to content

Commit

Permalink
TilesFadePlugin: Add support for unload tiles plugin (#884)
Browse files Browse the repository at this point in the history
* Progress fade

* Fix fade manager

* Remove fade group

* Add unload to demo

* Move plugin to plugin exports

* Update README

* Simplify fade out counting

* Remove extra lines / accidental commits

* Add comment
  • Loading branch information
gkjohnson authored Dec 20, 2024
1 parent e7bd38e commit 4cb8198
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 56 deletions.
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();

}

}

0 comments on commit 4cb8198

Please sign in to comment.