diff --git a/README.md b/README.md index 35f8e4415..6fc4af62c 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,23 @@ if ( intersects.length ) { ## TilesRenderer -_extends [TilesRendererBase](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/src/base/TilesRendererBase.js), which can be used to implement a 3d tiles renderer in other engines_ +_extends `THREE.EventDispatcher`` & [TilesRendererBase](https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/src/base/TilesRendererBase.js), which can be used to implement a 3d tiles renderer in other engines_ + +### events + +```js +// fired when a new root or child tile set is loaded +{ type: 'load-tile-set', tileSet: Object, url: String } + +// fired when a tile model is loaded +{ type: 'load-model', scene: THREE.Group, tile: Object } + +// fired when a tile model is disposed +{ type: 'dispose-model', scene: THREE.Group, tile: Object } + +// fired when a tiles visibility changes +{ type: 'tile-visibility-change', scene: THREE.Group, tile: Object } +``` ### .fetchOptions diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js index 99e7f0cbe..8383a5588 100644 --- a/example/src/FadeManager.js +++ b/example/src/FadeManager.js @@ -6,10 +6,14 @@ export class FadeManager { constructor() { this.duration = 250; + this.fadeCount = 0; this._lastTick = - 1; this._fadeState = new Map(); this._fadeParams = new WeakMap(); - this.onFadeFinish = () => {}; + this.onFadeComplete = null; + this.onFadeStart = null; + this.onFadeSetComplete = null; + this.onFadeSetStart = null; } @@ -195,7 +199,20 @@ export class FadeManager { } ); - this.onFadeFinish( object ); + // fire events + this.fadeCount --; + + if ( this.onFadeComplete ) { + + this.onFadeComplete( object ); + + } + + if ( this.fadeCount === 0 && this.onFadeSetComplete ) { + + this.onFadeSetComplete(); + + } } @@ -212,13 +229,30 @@ export class FadeManager { // Fade the object in fadeIn( object ) { - this.guaranteeState( object ); - + const noState = this.guaranteeState( object ); const state = this._fadeState.get( object ); state.fadeInTarget = 1; state.fadeOutTarget = 0; state.fadeOut = 0; + // Fire events + if ( noState ) { + + this.fadeCount ++; + if ( this.fadeCount === 1 && this.onFadeSetStart ) { + + this.onFadeSetStart(); + + } + + if ( this.onFadeStart ) { + + this.onFadeStart( object ); + + } + + } + } // Fade the object out @@ -227,11 +261,26 @@ export class FadeManager { const noState = this.guaranteeState( object ); const state = this._fadeState.get( object ); state.fadeOutTarget = 1; + + // Fire events and initialize state if ( noState ) { state.fadeInTarget = 1; state.fadeIn = 1; + this.fadeCount ++; + if ( this.fadeCount === 1 && this.onFadeSetStart ) { + + this.onFadeSetStart(); + + } + + if ( this.onFadeStart ) { + + this.onFadeStart( object ); + + } + } } diff --git a/example/src/FadeTilesRenderer.js b/example/src/FadeTilesRenderer.js index 0ba4961d4..d731bb603 100644 --- a/example/src/FadeTilesRenderer.js +++ b/example/src/FadeTilesRenderer.js @@ -46,7 +46,7 @@ function onLoadModel( scene ) { } -function onFadeFinish( object ) { +function onFadeComplete( object ) { // when the fade finishes ensure we dispose the tile and remove it from the fade group if ( object.parent === this._fadeGroup ) { @@ -113,14 +113,17 @@ export const FadeTilesRendererMixin = base => class extends base { const fadeGroup = new Group(); const fadeManager = new FadeManager(); + fadeManager.onFadeSetStart = () => this.dispatchEvent( { type: 'fade-start' } ); + fadeManager.onFadeSetComplete = () => this.dispatchEvent( { type: 'fade-end' } ); + this.group.add( fadeGroup ); - fadeManager.onFadeFinish = onFadeFinish.bind( this ); + fadeManager.onFadeComplete = onFadeComplete.bind( this ); this._fadeManager = fadeManager; this._fadeGroup = fadeGroup; - this.onLoadModel = onLoadModel.bind( this ); - this.onTileVisibilityChange = onTileVisibilityChange.bind( this ); + this.addEventListener( 'load-model', e => onLoadModel.call( this, e.scene ) ); + this.addEventListener( 'tile-visibility-change', e => onTileVisibilityChange.call( this, e.scene, e.tile, e.visible ) ); this.initialLayerRendered = false; this.prevCameraTransforms = new Map(); @@ -138,9 +141,18 @@ export const FadeTilesRendererMixin = base => class extends base { this.displayActiveTiles = true; // update the tiles + const fadingBefore = this._fadeManager.fadeCount; + super.update( ...args ); this._fadeManager.update(); + const fadingAfter = this._fadeManager.fadeCount; + if ( fadingBefore !== 0 && fadingAfter !== 0 ) { + + this.dispatchEvent( { type: 'fade-change' } ); + + } + this.displayActiveTiles = displayActiveTiles; this.fadeDuration = fadeDuration; @@ -241,7 +253,7 @@ export const FadeTilesRendererMixin = base => class extends base { this.disposeSet.forEach( object => { - onFadeFinish.call( this, object ); + onFadeComplete.call( this, object ); } ); diff --git a/src/three/TilesRenderer.d.ts b/src/three/TilesRenderer.d.ts index 89ba931f9..fea0898c7 100644 --- a/src/three/TilesRenderer.d.ts +++ b/src/three/TilesRenderer.d.ts @@ -4,6 +4,8 @@ import { Tileset } from '../base/Tileset'; import { TilesRendererBase } from '../base/TilesRendererBase'; import { TilesGroup } from './TilesGroup'; + + export class TilesRenderer extends TilesRendererBase { autoDisableRendererCulling : Boolean; @@ -32,4 +34,9 @@ export class TilesRenderer extends TilesRendererBase { onDisposeModel : ( ( scene : Object3D, tile : Tile ) => void ) | null; onTileVisibilityChange : ( ( scene : Object3D, tile : Tile, visible : boolean ) => void ) | null; + addEventListener( type: String, cb: ( e : Object ) => void ); + hasEventListener( type: String, cb: ( e : Object ) => void ); + removeEventListener( type: String, cb: ( e : Object ) => void ); + dispatchEvent( e : Object ); + } diff --git a/src/three/TilesRenderer.js b/src/three/TilesRenderer.js index 444b1d269..ddaf80de6 100644 --- a/src/three/TilesRenderer.js +++ b/src/three/TilesRenderer.js @@ -10,7 +10,8 @@ import { Vector3, Vector2, Frustum, - LoadingManager + LoadingManager, + EventDispatcher, } from 'three'; import { raycastTraverse, raycastTraverseFirstHit } from './raycastTraverse.js'; import { readMagicBytes } from '../utilities/readMagicBytes.js'; @@ -66,8 +67,9 @@ export class TilesRenderer extends TilesRendererBase { this.cameraInfo = []; this.activeTiles = new Set(); this.visibleTiles = new Set(); - this._autoDisableRendererCulling = true; this.optimizeRaycast = true; + this._autoDisableRendererCulling = true; + this._eventDispatcher = new EventDispatcher(); this.onLoadTileSet = null; this.onLoadModel = null; @@ -105,6 +107,30 @@ export class TilesRenderer extends TilesRendererBase { } + addEventListener( ...args ) { + + this._eventDispatcher.addEventListener( ...args ); + + } + + hasEventListener( ...args ) { + + this._eventDispatcher.hasEventListener( ...args ); + + } + + removeEventListener( ...args ) { + + this._eventDispatcher.removeEventListener( ...args ); + + } + + dispatchEvent( ...args ) { + + this._eventDispatcher.dispatchEvent( ...args ); + + } + /* Public API */ getBounds( target ) { @@ -293,18 +319,25 @@ export class TilesRenderer extends TilesRendererBase { const pr = super.fetchTileSet( url, ...rest ); pr.then( json => { - if ( this.onLoadTileSet ) { + // Push this onto the end of the event stack to ensure this runs + // after the base renderer has placed the provided json where it + // needs to be placed and is ready for an update. + Promise.resolve().then( () => { + + this.dispatchEvent( { + type: 'load-tile-set', + tileSet: json, + url, + } ); - // Push this onto the end of the event stack to ensure this runs - // after the base renderer has placed the provided json where it - // needs to be placed and is ready for an update. - Promise.resolve().then( () => { + if ( this.onLoadTileSet ) { this.onLoadTileSet( json, url ); - } ); + } + + } ); - } } ); return pr; @@ -675,6 +708,12 @@ export class TilesRenderer extends TilesRendererBase { cached.scene = scene; cached.metadata = metadata; + this.dispatchEvent( { + type: 'load-model', + scene, + tile, + } ); + if ( this.onLoadModel ) { this.onLoadModel( scene, tile ); @@ -721,6 +760,13 @@ export class TilesRenderer extends TilesRendererBase { } + + this.dispatchEvent( { + type: 'dispose-model', + scene: cached.scene, + tile, + } ); + if ( this.onDisposeModel ) { this.onDisposeModel( cached.scene, tile ); @@ -759,6 +805,13 @@ export class TilesRenderer extends TilesRendererBase { } + this.dispatchEvent( { + type: 'tile-visibility-change', + scene, + tile, + visible, + } ); + if ( this.onTileVisibilityChange ) { this.onTileVisibilityChange( scene, tile, visible ); diff --git a/src/three/renderers/GoogleTilesRenderer.js b/src/three/renderers/GoogleTilesRenderer.js index e9efa45b0..5f2d4edf5 100644 --- a/src/three/renderers/GoogleTilesRenderer.js +++ b/src/three/renderers/GoogleTilesRenderer.js @@ -29,7 +29,7 @@ const GoogleTilesRendererMixin = base => class extends base { this.lruCache.maxSize = 5000; this.errorTarget = 40; - this.onLoadTileSet = tileset => { + const onLoadCallback = () => { // find the session id in the first sub tile set let session; @@ -61,12 +61,15 @@ const GoogleTilesRendererMixin = base => class extends base { }; // clear the callback once the root is loaded - this.onLoadTileSet = null; + this.removeEventListener( 'load-tile-set', onLoadCallback ); }; - this.onTileVisibilityChange = ( scene, tile, visible ) => { + this.addEventListener( 'load-tile-set', onLoadCallback ); + this.addEventListener( 'tile-visibility-change', e => { + + const { tile, visible } = e; const copyright = tile.cached.metadata.asset.copyright || ''; if ( visible ) { @@ -78,7 +81,7 @@ const GoogleTilesRendererMixin = base => class extends base { } - }; + } ); }