UNPKG

3d-tiles-renderer

Version:

https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification

515 lines (321 loc) 12.3 kB
import { Matrix4, Vector3, Quaternion } from 'three'; import { FadeManager } from './FadeManager.js'; import { FadeMaterialManager } from './FadeMaterialManager.js'; import { FadeBatchedMesh } from './FadeBatchedMesh.js'; const HAS_POPPED_IN = Symbol( 'HAS_POPPED_IN' ); const _fromPos = /* @__PURE__ */ new Vector3(); const _toPos = /* @__PURE__ */ new Vector3(); const _fromQuat = /* @__PURE__ */ new Quaternion(); const _toQuat = /* @__PURE__ */ new Quaternion(); const _scale = /* @__PURE__ */ new Vector3(); function onUpdateBefore() { const fadeManager = this._fadeManager; const tiles = this.tiles; // store the tiles renderer state before the tiles update so we can check // whether fading started or stopped completely this._fadingBefore = fadeManager.fadeCount; this._displayActiveTiles = tiles.displayActiveTiles; // we need to display all active tiles in this case so we don't fade tiles in // when moving from off screen tiles.displayActiveTiles = true; } function onUpdateAfter() { const fadeManager = this._fadeManager; const fadeMaterialManager = this._fadeMaterialManager; const displayActiveTiles = this._displayActiveTiles; const fadingBefore = this._fadingBefore; const prevCameraTransforms = this._prevCameraTransforms; const { tiles, maximumFadeOutTiles, batchedMesh } = this; const { cameras } = tiles; // reset the active tiles flag tiles.displayActiveTiles = displayActiveTiles; // update fade step fadeManager.update(); // fire an event const fadingAfter = fadeManager.fadeCount; if ( fadingBefore !== 0 && fadingAfter !== 0 ) { tiles.dispatchEvent( { type: 'fade-change' } ); tiles.dispatchEvent( { type: 'needs-render' } ); } // update the visibility of tiles based on visibility since we must use // the active tiles for rendering fade if ( ! displayActiveTiles ) { tiles.visibleTiles.forEach( t => { // 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 ( scene ) { scene.visible = t.__inFrustum; } this.forEachBatchIds( t, ( id, batchedMesh, plugin ) => { batchedMesh.setVisibleAt( id, t.__inFrustum ); plugin.batchedMesh.setVisibleAt( id, t.__inFrustum ); } ); } ); } if ( maximumFadeOutTiles < this._fadingOutCount ) { // determine whether all the rendering cameras are moving // quickly so we can adjust how tiles fade accordingly let isMovingFast = true; cameras.forEach( camera => { if ( ! prevCameraTransforms.has( camera ) ) { return; } const currMatrix = camera.matrixWorld; const prevMatrix = prevCameraTransforms.get( camera ); currMatrix.decompose( _toPos, _toQuat, _scale ); prevMatrix.decompose( _fromPos, _fromQuat, _scale ); const angleTo = _toQuat.angleTo( _fromQuat ); const positionTo = _toPos.distanceTo( _fromPos ); // if rotation is moving > 0.25 radians per frame or position is moving > 0.1 units // then we are considering the camera to be moving too fast to notice a faster / abrupt fade isMovingFast = isMovingFast && ( angleTo > 0.25 || positionTo > 0.1 ); } ); if ( isMovingFast ) { fadeManager.completeAllFades(); } } // track the camera movement so we can use it for next frame cameras.forEach( camera => { prevCameraTransforms.get( camera ).copy( camera.matrixWorld ); } ); // update the fade state for each tile fadeManager.forEachObject( ( tile, { fadeIn, fadeOut } ) => { // prevent faded tiles from being unloaded const scene = tile.cached.scene; const isFadingOut = fadeManager.isFadingOut( tile ); tiles.markTileUsed( tile ); if ( scene ) { fadeMaterialManager.setFade( scene, fadeIn, fadeOut ); if ( isFadingOut ) { scene.visible = true; } } // fade the tiles and toggle the visibility appropriately this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { batchedMesh.setFadeAt( id, fadeIn, fadeOut ); batchedMesh.setVisibleAt( id, true ); plugin.batchedMesh.setVisibleAt( id, false ); } ); } ); // update the batched mesh fields if ( batchedMesh ) { const material = tiles.getPluginByName( 'BATCHED_TILES_PLUGIN' ).batchedMesh.material; batchedMesh.material.map = material.map; } } export class TilesFadePlugin { get fadeDuration() { return this._fadeManager.duration; } set fadeDuration( value ) { this._fadeManager.duration = Number( value ); } get fadingTiles() { return this._fadeManager.fadeCount; } constructor( options ) { options = { maximumFadeOutTiles: 50, fadeRootTiles: false, fadeDuration: 250, ...options, }; this.name = 'FADE_TILES_PLUGIN'; this.priority = - 2; this.tiles = null; this.batchedMesh = null; this._quickFadeTiles = new Set(); this._fadeManager = new FadeManager(); this._fadeMaterialManager = new FadeMaterialManager(); this._prevCameraTransforms = null; this._fadingOutCount = 0; this.maximumFadeOutTiles = options.maximumFadeOutTiles; this.fadeRootTiles = options.fadeRootTiles; this.fadeDuration = options.fadeDuration; } init( tiles ) { // event callback initialization this._onLoadModel = ( { scene } )=> { // initialize all the scene materials to fade this._fadeMaterialManager.prepareScene( scene ); }; this._onDisposeModel = ( { tile, scene } ) => { if ( this.tiles.visibleTiles.has( tile ) ) { // mark the parent as needing to fade in quickly to accommodate the children disappearing. // this can happen when a tile is forcefully disposed or removed from the lru cache while visible. this._quickFadeTiles.add( tile.parent ); } // delete the fade info from the managers on disposal of model this._fadeManager.deleteObject( tile ); this._fadeMaterialManager.deleteScene( scene ); }; this._onAddCamera = ( { camera } ) => { // track the camera transform this._prevCameraTransforms.set( camera, new Matrix4() ); }; this._onDeleteCamera = ( { camera } )=> { // remove the camera transform this._prevCameraTransforms.delete( camera ); }; this._onTileVisibilityChange = ( { tile, visible } ) => { // this function gets fired _after_ all set visible callbacks including the batched meshes // revert the scene and fade to the initial state when toggling const scene = tile.cached.scene; if ( scene ) { scene.visible = true; } this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { batchedMesh.setFadeAt( id, 0, 0 ); batchedMesh.setVisibleAt( id, false ); plugin.batchedMesh.setVisibleAt( id, false ); } ); }; this._onUpdateBefore = () => { onUpdateBefore.call( this ); }; this._onUpdateAfter = () => { onUpdateAfter.call( this ); }; tiles.addEventListener( 'load-model', this._onLoadModel ); tiles.addEventListener( 'dispose-model', this._onDisposeModel ); tiles.addEventListener( 'add-camera', this._onAddCamera ); tiles.addEventListener( 'delete-camera', this._onDeleteCamera ); tiles.addEventListener( 'update-before', this._onUpdateBefore ); tiles.addEventListener( 'update-after', this._onUpdateAfter ); tiles.addEventListener( 'tile-visibility-change', this._onTileVisibilityChange ); // initialize fade manager const fadeManager = this._fadeManager; fadeManager.onFadeSetStart = () => { tiles.dispatchEvent( { type: 'fade-start' } ); tiles.dispatchEvent( { type: 'needs-render' } ); }; fadeManager.onFadeSetComplete = () => { tiles.dispatchEvent( { type: 'fade-end' } ); tiles.dispatchEvent( { type: 'needs-render' } ); }; fadeManager.onFadeComplete = ( tile, visible ) => { // mark the fade as finished and reset the fade parameters this._fadeMaterialManager.setFade( tile.cached.scene, 0, 0 ); this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { batchedMesh.setFadeAt( id, 0, 0 ); batchedMesh.setVisibleAt( id, false ); plugin.batchedMesh.setVisibleAt( id, visible ); } ); if ( ! visible ) { // now that the tile is hidden we can run the built-in setTileVisible function for the tile tiles.invokeOnePlugin( plugin => plugin !== this && plugin.setTileVisible && plugin.setTileVisible( tile, false ) ); this._fadingOutCount --; } }; // initialize the state based on what's already present const prevCameraTransforms = new Map(); tiles.cameras.forEach( camera => { prevCameraTransforms.set( camera, new Matrix4() ); } ); tiles.forEachLoadedModel( ( scene, tile ) => { this._onLoadModel( { scene } ); } ); this.tiles = tiles; this._fadeManager = fadeManager; this._prevCameraTransforms = prevCameraTransforms; } // initializes the batched mesh if it needs to be, dispose if it it's no longer needed initBatchedMesh() { const otherBatchedMesh = this.tiles.getPluginByName( 'BATCHED_TILES_PLUGIN' )?.batchedMesh; if ( otherBatchedMesh ) { if ( this.batchedMesh === null ) { this._onBatchedMeshDispose = () => { this.batchedMesh.dispose(); this.batchedMesh.removeFromParent(); this.batchedMesh = null; otherBatchedMesh.removeEventListener( 'dispose', this._onBatchedMeshDispose ); }; const material = otherBatchedMesh.material.clone(); material.onBeforeCompile = otherBatchedMesh.material.onBeforeCompile; this.batchedMesh = new FadeBatchedMesh( otherBatchedMesh, material ); this.tiles.group.add( this.batchedMesh ); } } else { if ( this.batchedMesh !== null ) { this._onBatchedMeshDispose(); this._onBatchedMeshDispose = null; } } } // callback for fading to prevent tiles from being removed until the fade effect has completed setTileVisible( tile, visible ) { const fadeManager = this._fadeManager; // track the fade state const wasFading = fadeManager.isFading( tile ); if ( fadeManager.isFadingOut( tile ) ) { this._fadingOutCount --; } // trigger any necessary fades if ( ! visible ) { this._fadingOutCount ++; fadeManager.fadeOut( tile ); } else { // if this is a root renderable tile and this is the first time rendering in // then pop it in const isRootRenderableTile = tile.__depthFromRenderedParent === 1; if ( isRootRenderableTile ) { if ( tile[ HAS_POPPED_IN ] || this.fadeRootTiles ) { this._fadeManager.fadeIn( tile ); } tile[ HAS_POPPED_IN ] = true; } else { this._fadeManager.fadeIn( tile ); } } // if a tile needs to be jumped in then complete the fade here if ( this._quickFadeTiles.has( tile ) ) { this._fadeManager.completeFade( tile ); this._quickFadeTiles.delete( tile ); } // 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( tile ); if ( ! visible && isFading ) { return true; } return false; } dispose() { const tiles = this.tiles; this._fadeManager.completeAllFades(); if ( this.batchedMesh !== null ) { this._onBatchedMeshDispose(); } tiles.removeEventListener( 'load-model', this._onLoadModel ); tiles.removeEventListener( 'dispose-model', this._onDisposeModel ); tiles.removeEventListener( 'add-camera', this._onAddCamera ); tiles.removeEventListener( 'delete-camera', this._onDeleteCamera ); tiles.removeEventListener( 'update-before', this._onUpdateBefore ); tiles.removeEventListener( 'update-after', this._onUpdateAfter ); tiles.removeEventListener( 'tile-visibility-change', this._onTileVisibilityChange ); tiles.forEachLoadedModel( ( scene, tile ) => { this._fadeManager.deleteObject( tile ); if ( scene ) { scene.visible = true; // TODO } } ); } // helper for iterating over the batch ids for a given tile forEachBatchIds( tile, cb ) { this.initBatchedMesh(); if ( this.batchedMesh ) { const batchedPlugin = this.tiles.getPluginByName( 'BATCHED_TILES_PLUGIN' ); const instanceIds = batchedPlugin.getTileBatchIds( tile ); if ( instanceIds ) { instanceIds.forEach( id => { cb( id, this.batchedMesh, batchedPlugin ); } ); } } } }