From 2baf3ef9a1af3d3fba41b3169b9b05b308706cbd Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 25 Dec 2024 12:01:20 +0900 Subject: [PATCH] Add support for fading BatchedMesh (#894) * Remove mixing of fade state and materials * Change fade handle to tile * Separate material fade with fade tracking * Wrap the material with batched mesh uniforms * Add a FadeBatchedMesh class * Add instance data texture classe * Add fade helpers * Get basic implementation working * fix * some fixes * Working * updats * fix assignment * 32 -> 8 bit fade texture * Fix texture type * comments, clarification --- example/googleMapsExample.js | 5 +- src/base/TilesRendererBase.js | 2 +- src/plugins/three/fade/FadeBatchedMesh.js | 107 ++++++++++ src/plugins/three/fade/FadeManager.js | 173 +--------------- src/plugins/three/fade/FadeMaterialManager.js | 105 ++++++++++ .../three/fade/PassThroughBatchedMesh.js | 137 ++++++++++++ src/plugins/three/fade/TilesFadePlugin.js | 195 ++++++++++++++---- src/plugins/three/fade/wrapFadeMaterial.js | 133 ++++++++++++ 8 files changed, 650 insertions(+), 207 deletions(-) create mode 100644 src/plugins/three/fade/FadeBatchedMesh.js create mode 100644 src/plugins/three/fade/FadeMaterialManager.js create mode 100644 src/plugins/three/fade/PassThroughBatchedMesh.js create mode 100644 src/plugins/three/fade/wrapFadeMaterial.js diff --git a/example/googleMapsExample.js b/example/googleMapsExample.js index f4f8786d5..44562ca91 100644 --- a/example/googleMapsExample.js +++ b/example/googleMapsExample.js @@ -65,6 +65,7 @@ function reinstantiateTiles() { tiles.registerPlugin( new TileCompressionPlugin() ); tiles.registerPlugin( new UpdateOnChangePlugin() ); tiles.registerPlugin( new UnloadTilesPlugin() ); + tiles.registerPlugin( new TilesFadePlugin() ); tiles.registerPlugin( new GLTFExtensionsPlugin( { // Note the DRACO compression files need to be supplied via an explicit source. // We use unpkg here but in practice should be provided by the application. @@ -80,10 +81,6 @@ function reinstantiateTiles() { instanceCount: 250, } ) ); - } else { - - tiles.registerPlugin( new TilesFadePlugin() ); - } tiles.group.rotation.x = - Math.PI / 2; diff --git a/src/base/TilesRendererBase.js b/src/base/TilesRendererBase.js index 8a681dc08..60bce8212 100644 --- a/src/base/TilesRendererBase.js +++ b/src/base/TilesRendererBase.js @@ -472,7 +472,7 @@ export class TilesRendererBase { if ( parentTile === null ) { tile.__depth = 0; - tile.__depthFromRenderedParent = 0; + tile.__depthFromRenderedParent = ( tile.__hasRenderableContent ? 1 : 0 ); tile.refine = tile.refine || 'REPLACE'; } else { diff --git a/src/plugins/three/fade/FadeBatchedMesh.js b/src/plugins/three/fade/FadeBatchedMesh.js new file mode 100644 index 000000000..b2f8e5b8c --- /dev/null +++ b/src/plugins/three/fade/FadeBatchedMesh.js @@ -0,0 +1,107 @@ +import { PassThroughBatchedMesh } from './PassThroughBatchedMesh.js'; +import { RGFormat, UnsignedByteType, DataTexture } from 'three'; +import { wrapFadeMaterial } from './wrapFadeMaterial.js'; + +export class FadeBatchedMesh extends PassThroughBatchedMesh { + + constructor( ...args ) { + + super( ...args ); + + // construct a version of the material that supports fading + const material = this.material; + const params = wrapFadeMaterial( material, material.onBeforeCompile ); + material.defines.FEATURE_FADE = 1; + material.defines.USE_BATCHING_FRAG = 1; + material.needsUpdate = true; + + // fade parameters + this.fadeTexture = null; + this._fadeParams = params; + + } + + // Set the fade state + setFadeAt( index, fadeIn, fadeOut ) { + + this._initFadeTexture(); + this.fadeTexture.setValueAt( index, fadeIn * 255, fadeOut * 255 ); + + } + + // initialize the texture and resize it if needed + _initFadeTexture() { + + // calculate the new size + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + const length = size * size * 2; + const oldFadeTexture = this.fadeTexture; + if ( ! oldFadeTexture || oldFadeTexture.image.data.length !== length ) { + + // 2 bytes per RG pixel + const fadeArray = new Uint8Array( length ); + const fadeTexture = new InstanceDataTexture( fadeArray, size, size, RGFormat, UnsignedByteType ); + + // copy the data from the old fade texture if it exists + if ( oldFadeTexture ) { + + oldFadeTexture.dispose(); + + const src = oldFadeTexture.image.data; + const dst = this.fadeTexture.image.data; + const len = Math.min( src.length, dst.length ); + dst.set( new src.constructor( src.buffer, 0, len ) ); + + } + + // assign the new fade texture to the uniform, member variable + this.fadeTexture = fadeTexture; + this._fadeParams.fadeTexture.value = fadeTexture; + fadeTexture.needsUpdate = true; + + } + + } + + // dispose the fade texture. Super cannot be used here due to proxy + dispose() { + + this.fadeTexture.dispose(); + + } + +} + +// Version of data texture that can assign pixel values +class InstanceDataTexture extends DataTexture { + + setValueAt( instance, ...values ) { + + const { data, width, height } = this.image; + const itemSize = Math.floor( data.length / ( width * height ) ); + let needsUpdate = false; + for ( let i = 0; i < itemSize; i ++ ) { + + const index = instance * itemSize + i; + const prevValue = data[ index ]; + const newValue = values[ i ] || 0; + if ( prevValue !== newValue ) { + + data[ index ] = newValue; + needsUpdate = true; + + } + + } + + if ( needsUpdate ) { + + this.needsUpdate = true; + + } + + } + +} diff --git a/src/plugins/three/fade/FadeManager.js b/src/plugins/three/fade/FadeManager.js index 50abb9d86..9e94bc9ec 100644 --- a/src/plugins/three/fade/FadeManager.js +++ b/src/plugins/three/fade/FadeManager.js @@ -9,7 +9,6 @@ export class FadeManager { this.fadeCount = 0; this._lastTick = - 1; this._fadeState = new Map(); - this._fadeParams = new WeakMap(); this.onFadeComplete = null; this.onFadeStart = null; this.onFadeSetComplete = null; @@ -17,21 +16,6 @@ export class FadeManager { } - // initialize materials in the object - prepareObject( object ) { - - object.traverse( child => { - - if ( child.material ) { - - this.prepareMaterial( child.material ); - - } - - } ); - - } - // delete the object from the fade, reset the material data deleteObject( object ) { @@ -43,103 +27,6 @@ export class FadeManager { this.completeFade( object ); - const fadeParams = this._fadeParams; - object.traverse( child => { - - const material = child.material; - if ( material ) { - - fadeParams.delete( material ); - material.onBeforeCompile = () => {}; - material.needsUpdate = true; - - } - - } ); - - } - - // initialize the material - prepareMaterial( material ) { - - const fadeParams = this._fadeParams; - if ( fadeParams.has( material ) ) { - - return; - - } - - const params = { - fadeIn: { value: 0 }, - fadeOut: { value: 0 }, - }; - - material.defines = { - FEATURE_FADE: 0, - }; - - material.onBeforeCompile = shader => { - - shader.uniforms = { - ...shader.uniforms, - ...params, - }; - - shader.fragmentShader = shader.fragmentShader - .replace( /void main\(/, value => /* glsl */` - #if FEATURE_FADE - - // adapted from https://www.shadertoy.com/view/Mlt3z8 - float bayerDither2x2( vec2 v ) { - - return mod( 3.0 * v.y + 2.0 * v.x, 4.0 ); - - } - - float bayerDither4x4( vec2 v ) { - - vec2 P1 = mod( v, 2.0 ); - vec2 P2 = floor( 0.5 * mod( v, 4.0 ) ); - return 4.0 * bayerDither2x2( P1 ) + bayerDither2x2( P2 ); - - } - - uniform float fadeIn; - uniform float fadeOut; - #endif - - ${ value } - ` ) - .replace( /#include /, value => /* glsl */` - - ${ value } - - #if FEATURE_FADE - - float bayerValue = bayerDither4x4( floor( mod( gl_FragCoord.xy, 4.0 ) ) ); - float bayerBins = 16.0; - float dither = ( 0.5 + bayerValue ) / bayerBins; - if ( dither >= fadeIn ) { - - discard; - - } - - if ( dither < fadeOut ) { - - discard; - - } - - #endif - - ` ); - - - }; - - fadeParams.set( material, params ); - } // Ensure we're storing a fade timer for the provided object @@ -162,20 +49,6 @@ export class FadeManager { fadeState.set( object, state ); - const fadeParams = this._fadeParams; - object.traverse( child => { - - const material = child.material; - if ( material && fadeParams.has( material ) ) { - - const params = fadeParams.get( material ); - params.fadeIn.value = 0; - params.fadeOut.value = 0; - - } - - } ); - return true; } @@ -184,22 +57,15 @@ export class FadeManager { completeFade( object ) { const fadeState = this._fadeState; - if ( ! fadeState.has( object ) ) return; + if ( ! fadeState.has( object ) ) { - const visible = fadeState.get( object ).fadeOutTarget === 0; - - fadeState.delete( object ); - object.traverse( child => { - - const material = child.material; - if ( material && material.defines.FEATURE_FADE !== 0 ) { + return; - material.defines.FEATURE_FADE = 0; - material.needsUpdate = true; + } - } + const visible = fadeState.get( object ).fadeOutTarget === 0; - } ); + fadeState.delete( object ); // fire events this.fadeCount --; @@ -230,7 +96,11 @@ export class FadeManager { forEachObject( cb ) { - this._fadeState.forEach( ( info, scene ) => cb( scene ) ); + this._fadeState.forEach( ( info, object ) => { + + cb( object, info ); + + } ); } @@ -321,7 +191,6 @@ export class FadeManager { this._lastTick = time; const fadeState = this._fadeState; - const fadeParams = this._fadeParams; fadeState.forEach( ( state, object ) => { // tick the fade values @@ -344,28 +213,6 @@ export class FadeManager { state.fadeIn = fadeIn; state.fadeOut = fadeOut; - // update the material fields - const defineValue = Number( fadeOut !== fadeOutTarget || fadeIn !== fadeInTarget ); - object.traverse( child => { - - const material = child.material; - if ( material && fadeParams.has( material ) ) { - - const uniforms = fadeParams.get( material ); - uniforms.fadeIn.value = fadeIn; - uniforms.fadeOut.value = fadeOut; - - if ( defineValue !== material.defines.FEATURE_FADE ) { - - material.defines.FEATURE_FADE = defineValue; - material.needsUpdate = true; - - } - - } - - } ); - // Check if the fade in and fade out animations are complete const fadeOutComplete = fadeOut === 1 || fadeOut === 0; const fadeInComplete = fadeIn === 1 || fadeIn === 0; diff --git a/src/plugins/three/fade/FadeMaterialManager.js b/src/plugins/three/fade/FadeMaterialManager.js new file mode 100644 index 000000000..27fb33e48 --- /dev/null +++ b/src/plugins/three/fade/FadeMaterialManager.js @@ -0,0 +1,105 @@ +import { wrapFadeMaterial } from './wrapFadeMaterial.js'; + +// Class for managing and updating extended fade parameters +export class FadeMaterialManager { + + constructor() { + + this._fadeParams = new WeakMap(); + this.fading = 0; + + } + + // Set the fade parameters for the given scene + setFade( scene, fadeIn, fadeOut ) { + + if ( ! scene ) { + + return; + + } + + // traverse the scene and update the fade parameters of all materials + const fadeParams = this._fadeParams; + scene.traverse( child => { + + const material = child.material; + if ( material ) { + + const params = fadeParams.get( material ); + params.fadeIn.value = fadeIn; + params.fadeOut.value = fadeOut; + + const fadeInComplete = fadeIn === 0 || fadeIn === 1; + const fadeOutComplete = fadeOut === 0 || fadeOut === 1; + const value = Number( ! fadeInComplete || ! fadeOutComplete ); + if ( material.defines.FEATURE_FADE !== value ) { + + this.fading += value === 1 ? 1 : - 1; + material.defines.FEATURE_FADE = value; + material.needsUpdate = true; + + } + + } + + } ); + + } + + // initialize materials in the object + prepareScene( scene ) { + + scene.traverse( child => { + + if ( child.material ) { + + this.prepareMaterial( child.material ); + + } + + } ); + + } + + // delete the object from the fade, reset the material data + deleteScene( scene ) { + + if ( ! scene ) { + + return; + + } + + // revert the materials + const fadeParams = this._fadeParams; + scene.traverse( child => { + + const material = child.material; + if ( material ) { + + fadeParams.delete( material ); + material.onBeforeCompile = () => {}; + material.needsUpdate = true; + + } + + } ); + + } + + // initialize the material + prepareMaterial( material ) { + + const fadeParams = this._fadeParams; + if ( fadeParams.has( material ) ) { + + return; + + } + + fadeParams.set( material, wrapFadeMaterial( material ) ); + + } + +} diff --git a/src/plugins/three/fade/PassThroughBatchedMesh.js b/src/plugins/three/fade/PassThroughBatchedMesh.js new file mode 100644 index 000000000..36777e796 --- /dev/null +++ b/src/plugins/three/fade/PassThroughBatchedMesh.js @@ -0,0 +1,137 @@ +import { MeshBasicMaterial } from 'three'; + +// A hacky version of BatchedMesh that passes through functions and geometry and other fields from the underlying +// BatchedMesh. Calls to "this" or "super" will not work in subfunctions. +export class PassThroughBatchedMesh { + + constructor( other, material = new MeshBasicMaterial() ) { + + // the other batched mesh + this.other = other; + + // guarded fields + this.material = material; + this.visible = true; + this.parent = null; + this._instanceInfo = []; + this._visibilityChanged = true; + + // the proxy instance tht pass through arguments to the underlying mesh + const proxyTarget = new Proxy( this, { + + get( target, key ) { + + if ( key in target ) { + + return target[ key ]; + + } else { + + // sync instances on function call and call functions on "this" instance + const value = other[ key ]; + if ( value instanceof Function ) { + + return ( ...args ) => { + + target.syncInstances(); + return value.call( proxyTarget, ...args ); + + }; + + } else { + + return other[ key ]; + + } + + } + + }, + + set( target, key, value ) { + + if ( key in target ) { + + target[ key ] = value; + + } else { + + other[ key ] = value; + + } + + return true; + + }, + + deleteProperty( target, key ) { + + if ( key in target ) { + + return delete target[ key ]; + + } else { + + return delete other[ key ]; + + } + + }, + + // ownKeys() {}, + // has(target, key) {}, + // defineProperty(target, key, descriptor) {}, + // getOwnPropertyDescriptor(target, key) {}, + + } ); + + return proxyTarget; + + } + + syncInstances() { + + const instanceInfo = this._instanceInfo; + const otherInstanceInfo = this.other._instanceInfo; + while ( otherInstanceInfo.length > instanceInfo.length ) { + + const index = instanceInfo.length; + instanceInfo.push( new Proxy( { visible: false }, { + + get( target, key ) { + + if ( key in target ) { + + return target[ key ]; + + } else { + + return otherInstanceInfo[ index ][ key ]; + + } + + }, + + set( target, key, value ) { + + if ( key in target ) { + + target[ key ] = value; + + } else { + + otherInstanceInfo[ index ][ key ] = value; + + } + + return true; + + } + + } ) ); + + } + + } + +} diff --git a/src/plugins/three/fade/TilesFadePlugin.js b/src/plugins/three/fade/TilesFadePlugin.js index 6580f7219..c18c0a131 100644 --- a/src/plugins/three/fade/TilesFadePlugin.js +++ b/src/plugins/three/fade/TilesFadePlugin.js @@ -1,5 +1,7 @@ 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 = new Vector3(); @@ -8,14 +10,10 @@ const _fromQuat = new Quaternion(); const _toQuat = new Quaternion(); const _scale = new Vector3(); -function onTileVisibilityChange( scene, tile, visible ) { - - // ensure the tiles are marked as visible on visibility toggle since - // it's possible we disable them when adjusting visibility based on frustum - scene.visible = true; +function onTileVisibilityChange( tile, visible ) { const fadeManager = this._fadeManager; - if ( fadeManager.isFadingOut( scene ) ) { + if ( fadeManager.isFadingOut( tile ) ) { this._fadingOutCount --; @@ -24,7 +22,7 @@ function onTileVisibilityChange( scene, tile, visible ) { if ( ! visible ) { this._fadingOutCount ++; - fadeManager.fadeOut( scene ); + fadeManager.fadeOut( tile ); } else { @@ -35,16 +33,15 @@ function onTileVisibilityChange( scene, tile, visible ) { if ( tile[ HAS_POPPED_IN ] || this.fadeRootTiles ) { - this._fadeManager.fadeIn( scene ); + this._fadeManager.fadeIn( tile ); } tile[ HAS_POPPED_IN ] = true; - } else { - this._fadeManager.fadeIn( scene ); + this._fadeManager.fadeIn( tile ); } @@ -54,24 +51,32 @@ function onTileVisibilityChange( scene, tile, visible ) { function onLoadModel( scene, tile ) { - this._fadeManager.prepareObject( scene ); - this._tileMap.set( scene, tile ); + this._fadeMaterialManager.prepareScene( scene ); } -function onDisposeModel( scene ) { +function onDisposeModel( scene, tile ) { - this._fadeManager.deleteObject( scene ); - this._tileMap.delete( scene ); + this._fadeManager.deleteObject( tile ); + this._fadeMaterialManager.deleteScene( scene ); } -function onFadeComplete( object, visible ) { +function onFadeComplete( tile, visible ) { + + // mark the fade as finished + this._fadeMaterialManager.setFade( tile.cached.scene, 0, 0 ); + + this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { + + 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 - const tile = this._tileMap.get( object ); this.tiles.invokeOnePlugin( plugin => plugin !== this && plugin.setTileVisible && plugin.setTileVisible( tile, false ) ); this._fadingOutCount --; @@ -111,11 +116,11 @@ function onUpdateBefore() { function onUpdateAfter() { const fadeManager = this._fadeManager; + const fadeMaterialManager = this._fadeMaterialManager; const displayActiveTiles = this._displayActiveTiles; const fadingBefore = this._fadingBefore; const tiles = this.tiles; const prevCameraTransforms = this._prevCameraTransforms; - const tileMap = this._tileMap; const lruCache = tiles.lruCache; const cameras = tiles.cameras; @@ -140,18 +145,24 @@ function onUpdateAfter() { tiles.visibleTiles.forEach( t => { + // TODO + const scene = t.cached.scene; + // 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 ) ) { + const isFadingOut = fadeManager.isFadingOut( t ); + if ( scene ) { - scene.visible = true; + scene.visible = isFadingOut || t.__inFrustum; - } else { + } - scene.visible = t.__inFrustum; + this.forEachBatchIds( ( id, batchedMesh, plugin ) => { - } + batchedMesh.setVisibleAt( id, isFadingOut || t.__inFrustum ); + plugin.batchedMesh.setVisibleAt( id, t.__inFrustum ); + + } ); } ); @@ -200,13 +211,31 @@ function onUpdateAfter() { } ); - // prevent faded tiles from being unloaded - fadeManager.forEachObject( scene => { + fadeManager.forEachObject( ( tile, { fadeIn, fadeOut } ) => { + + // prevent faded tiles from being unloaded + lruCache.markUsed( tile ); + fadeMaterialManager.setFade( tile.cached.scene, fadeIn, fadeOut ); - lruCache.markUsed( tileMap.get( scene ) ); + const isFading = fadeManager.isFading( tile ); + this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { + + batchedMesh.setFadeAt( id, fadeIn, fadeOut ); + batchedMesh.setVisibleAt( id, isFading ); + plugin.batchedMesh.setVisibleAt( id, ! isFading ); + + } ); } ); + if ( this.batchedMesh ) { + + const material = this.tiles.getPluginByName( 'BATCHED_MESH_PLUGIN' ).batchedMesh.material; + this.batchedMesh.material.map = material.map; + this.batchedMesh.material.needsUpdate = true; + + } + } export class TilesFadePlugin { @@ -241,12 +270,13 @@ export class TilesFadePlugin { }; this.name = 'FADE_TILES_PLUGIN'; - this.priority = - 1; + this.priority = - 2; this.tiles = null; + this.batchedMesh = null; this._fadeManager = new FadeManager(); + this._fadeMaterialManager = new FadeMaterialManager(); this._prevCameraTransforms = null; - this._tileMap = null; this._fadingOutCount = 0; this.maximumFadeOutTiles = options.maximumFadeOutTiles; @@ -276,7 +306,6 @@ export class TilesFadePlugin { this.tiles = tiles; this._fadeManager = fadeManager; - this._tileMap = new Map(); this._prevCameraTransforms = new Map(); tiles.cameras.forEach( camera => { @@ -292,11 +321,31 @@ export class TilesFadePlugin { } ); this._onLoadModel = e => onLoadModel.call( this, e.scene, e.tile ); - this._onDisposeModel = e => onDisposeModel.call( this, e.scene ); + this._onDisposeModel = e => onDisposeModel.call( this, e.scene, e.tile ); 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 ); + this._onTileVisibilityChange = ( { tile, visible } ) => { + + // ensure the tiles are marked as visible on visibility toggle since + // it's possible we disable them when adjusting visibility based on frustum + const scene = tile.cached.scene; + if ( scene ) { + + scene.visible = true; // TODO + + } + + this.forEachBatchIds( tile, ( id, batchedMesh, plugin ) => { + + batchedMesh.setFadeAt( id, 0, 0 ); + batchedMesh.setVisibleAt( id, true ); + plugin.batchedMesh.setVisibleAt( id, false ); + + } ); + + }; tiles.addEventListener( 'load-model', this._onLoadModel ); tiles.addEventListener( 'dispose-model', this._onDisposeModel ); @@ -304,14 +353,50 @@ export class TilesFadePlugin { 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 ); + + } + + initBatchedMesh() { + + const otherBatchedMesh = this.tiles.getPluginByName( 'BATCHED_MESH_PLUGIN' )?.batchedMesh; + if ( otherBatchedMesh ) { + + if ( this.batchedMesh === null ) { + + this._onBatchedMeshDispose = () => { + + this.batchedMesh.dispose(); + this.batchedMesh.removeFromParent(); + 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; + + } + + } } setTileVisible( tile, visible ) { - const scene = tile.cached.scene; - const wasFading = this._fadeManager.isFading( scene ); - onTileVisibilityChange.call( this, scene, tile, visible ); + const wasFading = this._fadeManager.isFading( tile ); + onTileVisibilityChange.call( this, tile, visible ); // if a tile was already fading then it's already marked as visible and in the scene if ( wasFading ) { @@ -322,7 +407,7 @@ export class TilesFadePlugin { // cancel the visibility change trigger because we're fading and will call this after // fade completes. - const isFading = this._fadeManager.isFading( scene ); + const isFading = this._fadeManager.isFading( tile ); if ( ! visible && isFading ) { return true; @@ -339,20 +424,52 @@ export class TilesFadePlugin { 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.forEachLoadedModel( scene => { + tiles.removeEventListener( 'tile-visibility-change', this._onTileVisibilityChange ); + tiles.forEachLoadedModel( ( scene, tile ) => { + + this._fadeManager.deleteObject( tile ); + if ( scene ) { - this._fadeManager.deleteObject( scene ); - this._tileMap.delete( scene ); - scene.visible = true; + scene.visible = true; // TODO + + } } ); } + forEachBatchIds( tile, cb ) { + + this.initBatchedMesh(); + + if ( this.batchedMesh ) { + + const batchedPlugin = this.tiles.getPluginByName( 'BATCHED_MESH_PLUGIN' ); + const instanceIds = batchedPlugin._tileToInstanceId.get( tile ); + if ( instanceIds ) { + + instanceIds.forEach( id => { + + cb( id, this.batchedMesh, batchedPlugin ); + + } ); + + } + + } + + } + } diff --git a/src/plugins/three/fade/wrapFadeMaterial.js b/src/plugins/three/fade/wrapFadeMaterial.js new file mode 100644 index 000000000..7208fc102 --- /dev/null +++ b/src/plugins/three/fade/wrapFadeMaterial.js @@ -0,0 +1,133 @@ +// Adjusts the provided material to support fading in and out using a bayer pattern. Providing a "previous" +// before compile can be used to chain shader adjustments. Returns the added uniforms used for fading. +export function wrapFadeMaterial( material, previousOnBeforeCompile ) { + + const params = { + fadeIn: { value: 0 }, + fadeOut: { value: 0 }, + fadeTexture: { value: null }, + }; + + material.defines = { + ...( material.defines || {} ), + FEATURE_FADE: 0, + }; + + material.onBeforeCompile = shader => { + + if ( previousOnBeforeCompile ) { + + previousOnBeforeCompile( shader ); + + } + + shader.uniforms = { + ...shader.uniforms, + ...params, + }; + + shader.vertexShader = shader.vertexShader + .replace( + /void\s+main\(\)\s+{/, + value => /* glsl */` + #ifdef USE_BATCHING_FRAG + + varying float vBatchId; + + #endif + + ${ value } + + #ifdef USE_BATCHING_FRAG + + // add 0.5 to the value to avoid floating error that may cause flickering + vBatchId = getIndirectIndex( gl_DrawID ) + 0.5; + + #endif + ` + ); + + shader.fragmentShader = shader.fragmentShader + .replace( /void main\(/, value => /* glsl */` + #if FEATURE_FADE + + // adapted from https://www.shadertoy.com/view/Mlt3z8 + float bayerDither2x2( vec2 v ) { + + return mod( 3.0 * v.y + 2.0 * v.x, 4.0 ); + + } + + float bayerDither4x4( vec2 v ) { + + vec2 P1 = mod( v, 2.0 ); + vec2 P2 = floor( 0.5 * mod( v, 4.0 ) ); + return 4.0 * bayerDither2x2( P1 ) + bayerDither2x2( P2 ); + + } + + // the USE_BATCHING define is not available in fragment shaders + #ifdef USE_BATCHING_FRAG + + // functions for reading the fade state of a given batch id + uniform sampler2D fadeTexture; + varying float vBatchId; + vec2 getFadeValues( const in float i ) { + + int size = textureSize( fadeTexture, 0 ).x; + int j = int( i ); + int x = j % size; + int y = j / size; + return texelFetch( fadeTexture, ivec2( x, y ), 0 ).rg; + + } + + #else + + uniform float fadeIn; + uniform float fadeOut; + + #endif + + #endif + + ${ value } + ` ) + .replace( /#include /, value => /* glsl */` + + ${ value } + + #if FEATURE_FADE + + #ifdef USE_BATCHING_FRAG + + vec2 fadeValues = getFadeValues( vBatchId ); + float fadeIn = fadeValues.r; + float fadeOut = fadeValues.g; + + #endif + + float bayerValue = bayerDither4x4( floor( mod( gl_FragCoord.xy, 4.0 ) ) ); + float bayerBins = 16.0; + float dither = ( 0.5 + bayerValue ) / bayerBins; + if ( dither >= fadeIn ) { + + discard; + + } + + if ( dither < fadeOut ) { + + discard; + + } + + #endif + + ` ); + + }; + + return params; + +}