diff --git a/src/plugins/three/fade/FadeManager.js b/src/plugins/three/fade/FadeManager.js index bd8e4b239..9e94bc9ec 100644 --- a/src/plugins/three/fade/FadeManager.js +++ b/src/plugins/three/fade/FadeManager.js @@ -96,9 +96,9 @@ export class FadeManager { forEachObject( cb ) { - this._fadeState.forEach( ( info, scene ) => { + this._fadeState.forEach( ( info, object ) => { - cb( scene ); + cb( object, info ); } ); diff --git a/src/plugins/three/fade/FadeMaterialManager.js b/src/plugins/three/fade/FadeMaterialManager.js new file mode 100644 index 000000000..5eea03d4f --- /dev/null +++ b/src/plugins/three/fade/FadeMaterialManager.js @@ -0,0 +1,169 @@ +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; + + } + + 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; + + } + + 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; + + } + + 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 ); + + } + +} diff --git a/src/plugins/three/fade/TilesFadePlugin.js b/src/plugins/three/fade/TilesFadePlugin.js index 058c010b9..d236a040e 100644 --- a/src/plugins/three/fade/TilesFadePlugin.js +++ b/src/plugins/three/fade/TilesFadePlugin.js @@ -1,5 +1,6 @@ import { Matrix4, Vector3, Quaternion } from 'three'; import { FadeManager } from './FadeManager.js'; +import { FadeMaterialManager } from './FadeMaterialManager.js'; const HAS_POPPED_IN = Symbol( 'HAS_POPPED_IN' ); const _fromPos = new Vector3(); @@ -59,16 +60,22 @@ function onTileVisibilityChange( tile, visible ) { function onLoadModel( scene, tile ) { + this._fadeMaterialManager.prepareScene( scene ); + } function onDisposeModel( scene, tile ) { this._fadeManager.deleteObject( tile ); + this._fadeMaterialManager.deleteScene( scene ); } function onFadeComplete( tile, visible ) { + // mark the fade as finished + this._fadeMaterialManager.setFade( tile.cached.scene, 0, 0 ); + if ( ! visible ) { // now that the tile is hidden we can run the built-in setTileVisible function for the tile @@ -111,6 +118,7 @@ function onUpdateBefore() { function onUpdateAfter() { const fadeManager = this._fadeManager; + const fadeMaterialManager = this._fadeMaterialManager; const displayActiveTiles = this._displayActiveTiles; const fadingBefore = this._fadingBefore; const tiles = this.tiles; @@ -210,10 +218,11 @@ function onUpdateAfter() { } ); - // prevent faded tiles from being unloaded - fadeManager.forEachObject( tile => { + fadeManager.forEachObject( ( tile, { fadeIn, fadeOut } ) => { + // prevent faded tiles from being unloaded lruCache.markUsed( tile ); + fadeMaterialManager.setFade( tile.cached.scene, fadeIn, fadeOut ); } ); @@ -255,6 +264,7 @@ export class TilesFadePlugin { this.tiles = null; this._fadeManager = new FadeManager(); + this._fadeMaterialManager = new FadeMaterialManager(); this._prevCameraTransforms = null; this._tileMap = null; // TODO this._fadingOutCount = 0;