From 71c8bc192217c40bf8a38bc79924885dfb35f2ff Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 Dec 2023 12:22:17 +0900 Subject: [PATCH 01/17] Add fade utils --- example/src/FadeManager.js | 265 +++++++++++++++++++++++++++++++ example/src/FadeTilesRenderer.js | 82 ++++++++++ 2 files changed, 347 insertions(+) create mode 100644 example/src/FadeManager.js create mode 100644 example/src/FadeTilesRenderer.js diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js new file mode 100644 index 000000000..44348a609 --- /dev/null +++ b/example/src/FadeManager.js @@ -0,0 +1,265 @@ +import { MathUtils } from 'three'; + +const { clamp } = MathUtils; +export class FadeManager { + + constructor() { + + this.duration = 250; + this._lastTick = - 1; + this._fadeState = new Map(); + this._fadeParams = new WeakMap(); + this.onFadeFinish = () => {}; + + } + + // initialize materials in the object + prepareObject( object ) { + + object.traverse( child => { + + if ( child.material ) { + + this.prepareMaterial( child.material ); + + } + + } ); + + } + + deleteObject( object ) { + + this._fadeParams.delete( object ); + object.traverse( child => { + + const material = child.material; + if ( material ) { + + this._fadeParams.delete( material ); + + } + + } ); + + } + + // 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: 1, + }; + + material.onBeforeCompile = shader => { + + shader.uniforms = { + ...shader.uniforms, + ...params, + }; + + material.PARAMS = 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 ); + + } + + guaranteeState( object ) { + + const fadeState = this._fadeState; + if ( fadeState.has( object ) ) { + + return; + + } + + const state = { + fadeInTarget: 0, + fadeOutTarget: 0, + fadeIn: 0, + fadeOut: 0, + }; + + 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; + + } + + } ); + + } + + completeFade( object ) { + + const fadeState = this._fadeState; + if ( ! fadeState.has( object ) ) return; + + fadeState.delete( object ); + this.onFadeFinish( object ); + + } + + fadeIn( object ) { + + this.guaranteeState( object ); + + const state = this._fadeState.get( object ); + state.fadeInTarget = 1; + state.fadeOutTarget = 0; + state.fadeOut = 0; + + } + + fadeOut( object ) { + + this.guaranteeState( object ); + + const state = this._fadeState.get( object ); + state.fadeOutTarget = 1; + + } + + update() { + + const time = window.performance.now(); + const delta = ( time - this._lastTick ) / this.duration; + this._lastTick = time; + + const fadeState = this._fadeState; + const fadeParams = this._fadeParams; + fadeState.forEach( ( state, object ) => { + + // tick the fade values + const { + fadeOutTarget, + fadeInTarget, + } = state; + + let { + fadeOut, + fadeIn, + } = state; + + const fadeInSign = Math.sign( fadeInTarget - fadeIn ); + fadeIn = clamp( fadeIn + fadeInSign * delta, 0, 1 ); + + const fadeOutSign = Math.sign( fadeOutTarget - fadeOut ); + fadeOut = clamp( fadeOut + fadeOutSign * delta, 0, 1 ); + + state.fadeIn = fadeIn; + state.fadeOut = fadeOut; + + // TODO: if we're at a "stable" state - ie reached the target values, then we + // should adjust the material define + // TODO: properly remove the tiles once the fade out has finished + // TODO: don't dispose of tiles until they've been faded out + + // 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; + + } + + } + + } ); + + + if ( fadeOut === 1.0 ) { + + this.completeFade( object ); + + } + + } ); + + } + +} diff --git a/example/src/FadeTilesRenderer.js b/example/src/FadeTilesRenderer.js new file mode 100644 index 000000000..1cf771393 --- /dev/null +++ b/example/src/FadeTilesRenderer.js @@ -0,0 +1,82 @@ +import { Group } from 'three'; +import { TilesRenderer } from '../..'; +import { FadeManager } from './FadeManager.js'; + +function onTileVisibilityChange( scene, tile, visible ) { + + // TODO: we should only do this when jumping from parent to child tiles. + // do not fade when a tile is made visible from frustum culling + if ( ! visible ) { + + this._fadeGroup.add( scene ); + this._fadeManager.fadeOut( scene ); + + } else { + + this._fadeManager.fadeIn( scene ); + + } + +} + +function onLoadModel( scene ) { + + this._fadeManager.prepareObject( scene ); + +} + +function onDisposeModel( scene ) { + + this._fadeManager.deleteObject( scene ); + +} + +export class FadeTilesRenderer extends TilesRenderer { + + get fadeDuration() { + + return this._fadeManager.duration; + + } + + set fadeDuration( value ) { + + this._fadeManager.duration = Number( value ); + + } + + constructor( ...args ) { + + super( ...args ); + + const fadeGroup = new Group(); + this.group.add( fadeGroup ); + + const fadeManager = new FadeManager(); + fadeManager.onFadeOutFinish = object => { + + if ( object.parent === fadeGroup ) { + + fadeGroup.remove( object ); + + } + + }; + + this._fadeManager = fadeManager; + this._fadeGroup = fadeGroup; + + this.onLoadModel = onLoadModel.bind( this ); + this.onDisposeModel = onDisposeModel.bind( this ); + this.onTileVisibilityChange = onTileVisibilityChange.bind( this ); + + } + + update( ...args ) { + + super.update( ...args ); + this._fadeManager.update(); + + } + +} From d5a76afcf190435ba458d546ac1d7b7b6ac7f4d4 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 Dec 2023 12:24:34 +0900 Subject: [PATCH 02/17] Delete line --- example/src/FadeManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js index 44348a609..9c4b09673 100644 --- a/example/src/FadeManager.js +++ b/example/src/FadeManager.js @@ -251,7 +251,6 @@ export class FadeManager { } ); - if ( fadeOut === 1.0 ) { this.completeFade( object ); From 1d7588272af817e17ac89ee50712a20b0324a359 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 Dec 2023 12:44:49 +0900 Subject: [PATCH 03/17] Updates --- example/src/FadeManager.js | 16 ++++++++++++++-- example/src/FadeTilesRenderer.js | 9 ++++++++- src/base/traverseFunctions.js | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js index 9c4b09673..b315c54f3 100644 --- a/example/src/FadeManager.js +++ b/example/src/FadeManager.js @@ -170,6 +170,18 @@ export class FadeManager { if ( ! fadeState.has( object ) ) return; fadeState.delete( object ); + object.traverse( child => { + + const material = child.material; + if ( material.defines.FEATURE_FADE !== 0 ) { + + material.defines.FEATURE_FADE = 0; + material.needsUpdate = true; + + } + + } ); + this.onFadeFinish( object ); } @@ -242,8 +254,8 @@ export class FadeManager { if ( defineValue !== material.defines.FEATURE_FADE ) { - // material.defines.FEATURE_FADE = defineValue; - // material.needsUpdate = true; + material.defines.FEATURE_FADE = defineValue; + material.needsUpdate = true; } diff --git a/example/src/FadeTilesRenderer.js b/example/src/FadeTilesRenderer.js index 1cf771393..06be585a2 100644 --- a/example/src/FadeTilesRenderer.js +++ b/example/src/FadeTilesRenderer.js @@ -4,6 +4,13 @@ import { FadeManager } from './FadeManager.js'; function onTileVisibilityChange( scene, tile, visible ) { + if ( tile.__wasInFrustum !== tile.__inFrustum ) { + + // TODO: possibly need to cancel fade? + return; + + } + // TODO: we should only do this when jumping from parent to child tiles. // do not fade when a tile is made visible from frustum culling if ( ! visible ) { @@ -53,7 +60,7 @@ export class FadeTilesRenderer extends TilesRenderer { this.group.add( fadeGroup ); const fadeManager = new FadeManager(); - fadeManager.onFadeOutFinish = object => { + fadeManager.onFadeFinish = object => { if ( object.parent === fadeGroup ) { diff --git a/src/base/traverseFunctions.js b/src/base/traverseFunctions.js index 97ba16f65..3503b8529 100644 --- a/src/base/traverseFunctions.js +++ b/src/base/traverseFunctions.js @@ -429,6 +429,7 @@ export function toggleTiles( tile, renderer ) { tile.__wasSetActive = setActive; tile.__wasSetVisible = setVisible; tile.__usedLastFrame = isUsed; + tile.__wasInFrustum = tile.__inFrustum; const children = tile.children; for ( let i = 0, l = children.length; i < l; i ++ ) { From 7d88583268fa3d3b1f95aed55b7bdaf344a99dcb Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 Dec 2023 17:09:17 +0900 Subject: [PATCH 04/17] Updates to fade example --- example/src/FadeManager.js | 13 ++++-- example/src/FadeTilesRenderer.js | 77 ++++++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js index b315c54f3..181943dfd 100644 --- a/example/src/FadeManager.js +++ b/example/src/FadeManager.js @@ -30,6 +30,12 @@ export class FadeManager { deleteObject( object ) { + if ( ! object ) { + + return; + + } + this._fadeParams.delete( object ); object.traverse( child => { @@ -60,7 +66,7 @@ export class FadeManager { }; material.defines = { - FEATURE_FADE: 1, + FEATURE_FADE: 0, }; material.onBeforeCompile = shader => { @@ -173,7 +179,7 @@ export class FadeManager { object.traverse( child => { const material = child.material; - if ( material.defines.FEATURE_FADE !== 0 ) { + if ( material && material.defines.FEATURE_FADE !== 0 ) { material.defines.FEATURE_FADE = 0; material.needsUpdate = true; @@ -208,8 +214,9 @@ export class FadeManager { update() { + // clamp delta in case duration is really small or 0 const time = window.performance.now(); - const delta = ( time - this._lastTick ) / this.duration; + const delta = clamp( ( time - this._lastTick ) / this.duration, 0, 1 ); this._lastTick = time; const fadeState = this._fadeState; diff --git a/example/src/FadeTilesRenderer.js b/example/src/FadeTilesRenderer.js index 06be585a2..ee22bf8e9 100644 --- a/example/src/FadeTilesRenderer.js +++ b/example/src/FadeTilesRenderer.js @@ -4,15 +4,9 @@ import { FadeManager } from './FadeManager.js'; function onTileVisibilityChange( scene, tile, visible ) { - if ( tile.__wasInFrustum !== tile.__inFrustum ) { - // TODO: possibly need to cancel fade? - return; - } - // TODO: we should only do this when jumping from parent to child tiles. - // do not fade when a tile is made visible from frustum culling if ( ! visible ) { this._fadeGroup.add( scene ); @@ -32,9 +26,45 @@ function onLoadModel( scene ) { } -function onDisposeModel( scene ) { +function onFadeFinish( object ) { + + if ( object.parent === this._fadeGroup ) { + + this._fadeGroup.remove( object ); + + if ( this.disposeSet.has( object ) ) { + + this._fadeManager.deleteObject( object ); + object.traverse( child => { + + const { geometry, material } = child; + if ( geometry ) { + + geometry.dispose(); + + } + + if ( material ) { + + material.dispose(); + for ( const key in material ) { + + const value = material[ key ]; + if ( value && value.dispose && typeof value.dispose === 'function' ) { + + value.dispose(); + + } + + } + + } + + } ); - this._fadeManager.deleteObject( scene ); + } + + } } @@ -60,23 +90,16 @@ export class FadeTilesRenderer extends TilesRenderer { this.group.add( fadeGroup ); const fadeManager = new FadeManager(); - fadeManager.onFadeFinish = object => { - - if ( object.parent === fadeGroup ) { - - fadeGroup.remove( object ); - - } - - }; + fadeManager.onFadeFinish = onFadeFinish.bind( this ); this._fadeManager = fadeManager; this._fadeGroup = fadeGroup; this.onLoadModel = onLoadModel.bind( this ); - this.onDisposeModel = onDisposeModel.bind( this ); this.onTileVisibilityChange = onTileVisibilityChange.bind( this ); + this.disposeSet = new Set(); + } update( ...args ) { @@ -86,4 +109,22 @@ export class FadeTilesRenderer extends TilesRenderer { } + disposeTile( tile ) { + + const scene = tile.cached.scene; + if ( scene && scene.parent === this._fadeGroup ) { + + this.disposeSet.add( scene ); + super.disposeTile( tile ); + this._fadeGroup.add( scene ); + + } else { + + super.disposeTile( tile ); + this._fadeManager.deleteObject( scene ); + + } + + } + } From e8d62fefe38c18bdcdcd5688144fa56231ba16ea Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 21 Dec 2023 00:14:02 +0900 Subject: [PATCH 05/17] Cleanup --- example/src/FadeManager.js | 7 +------ example/src/FadeTilesRenderer.js | 9 ++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/example/src/FadeManager.js b/example/src/FadeManager.js index 181943dfd..8a867974e 100644 --- a/example/src/FadeManager.js +++ b/example/src/FadeManager.js @@ -243,11 +243,6 @@ export class FadeManager { state.fadeIn = fadeIn; state.fadeOut = fadeOut; - // TODO: if we're at a "stable" state - ie reached the target values, then we - // should adjust the material define - // TODO: properly remove the tiles once the fade out has finished - // TODO: don't dispose of tiles until they've been faded out - // update the material fields const defineValue = Number( fadeOut !== fadeOutTarget || fadeIn !== fadeInTarget ); object.traverse( child => { @@ -270,7 +265,7 @@ export class FadeManager { } ); - if ( fadeOut === 1.0 ) { + if ( fadeOut === 1.0 || fadeOut > fadeIn ) { this.completeFade( object ); diff --git a/example/src/FadeTilesRenderer.js b/example/src/FadeTilesRenderer.js index ee22bf8e9..8bc7495ff 100644 --- a/example/src/FadeTilesRenderer.js +++ b/example/src/FadeTilesRenderer.js @@ -4,9 +4,6 @@ import { FadeManager } from './FadeManager.js'; function onTileVisibilityChange( scene, tile, visible ) { - - - if ( ! visible ) { this._fadeGroup.add( scene ); @@ -68,7 +65,7 @@ function onFadeFinish( object ) { } -export class FadeTilesRenderer extends TilesRenderer { +export const FadeTilesRendererMixin = base => class extends base { get fadeDuration() { @@ -127,4 +124,6 @@ export class FadeTilesRenderer extends TilesRenderer { } -} +}; + +export const FadeTilesRenderer = FadeTilesRendererMixin( TilesRenderer ); From 6ba3d7265a6d589a10782a4a5bc1d0543fd1f254 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 21 Dec 2023 00:44:10 +0900 Subject: [PATCH 06/17] Add fade example --- example/fadingTiles.html | 52 +++++++++++++++ example/fadingTiles.js | 134 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 example/fadingTiles.html create mode 100644 example/fadingTiles.js diff --git a/example/fadingTiles.html b/example/fadingTiles.html new file mode 100644 index 000000000..6ba9f8881 --- /dev/null +++ b/example/fadingTiles.html @@ -0,0 +1,52 @@ + + + + + + + Dingo Gap Tile Set + + + + +
+ Demonstration of tiles use a dither fade to change, smoothing out the transition. +
+ + + diff --git a/example/fadingTiles.js b/example/fadingTiles.js new file mode 100644 index 000000000..36ef665d8 --- /dev/null +++ b/example/fadingTiles.js @@ -0,0 +1,134 @@ +import { + FadeTilesRenderer, +} from './src/FadeTilesRenderer.js'; +import { + Scene, + DirectionalLight, + AmbientLight, + WebGLRenderer, + PerspectiveCamera, + Group, + FogExp2, +} from 'three'; +import { FlyOrbitControls } from './FlyOrbitControls.js'; +import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; + +let camera, controls, scene, renderer; +let groundTiles, skyTiles; + +const params = { + + fog: false, + errorTarget: 12, + fadeDuration: 0.25, + renderScale: 1, + displayActiveTiles: true, + +}; + +init(); +render(); + +function init() { + + const fog = new FogExp2( 0xd8cec0, .0075, 250 ); + scene = new Scene(); + + // primary camera view + renderer = new WebGLRenderer( { antialias: true } ); + renderer.setPixelRatio( window.devicePixelRatio ); + renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setClearColor( 0xd8cec0 ); + + document.body.appendChild( renderer.domElement ); + renderer.domElement.tabIndex = 1; + + camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 ); + camera.position.set( 20, 10, 20 ); + + // controls + controls = new FlyOrbitControls( camera, renderer.domElement ); + controls.screenSpacePanning = false; + controls.minDistance = 1; + controls.maxDistance = 2000; + controls.maxPolarAngle = Math.PI / 2; + controls.baseSpeed = 0.1; + controls.fastSpeed = 0.2; + + // lights + const dirLight = new DirectionalLight( 0xffffff ); + dirLight.position.set( 1, 2, 3 ); + scene.add( dirLight ); + + const ambLight = new AmbientLight( 0xffffff, 0.2 ); + scene.add( ambLight ); + + const tilesParent = new Group(); + tilesParent.rotation.set( Math.PI / 2, 0, 0 ); + scene.add( tilesParent ); + + groundTiles = new FadeTilesRenderer( 'https://raw.githubusercontent.com/NASA-AMMOS/3DTilesSampleData/master/msl-dingo-gap/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize_tileset.json' ); + groundTiles.fetchOptions.mode = 'cors'; + groundTiles.lruCache.minSize = 900; + groundTiles.lruCache.maxSize = 1300; + groundTiles.errorTarget = 12; + groundTiles.displayActiveTiles = true; + + skyTiles = new FadeTilesRenderer( 'https://raw.githubusercontent.com/NASA-AMMOS/3DTilesSampleData/master/msl-dingo-gap/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_sky/0528_0260184_to_s64o256_sky_tileset.json' ); + skyTiles.fetchOptions.mode = 'cors'; + skyTiles.lruCache = groundTiles.lruCache; + skyTiles.displayActiveTiles = true; + + tilesParent.add( groundTiles.group, skyTiles.group ); + + onWindowResize(); + window.addEventListener( 'resize', onWindowResize, false ); + + const gui = new GUI(); + gui.add( params, 'fog' ).onChange( v => { + + scene.fog = v ? fog : null; + + } ); + + gui.add( params, 'displayActiveTiles' ); + + gui.add( params, 'errorTarget', 0, 100 ); + gui.add( params, 'fadeDuration', 0, 5 ); + gui.add( params, 'renderScale', 0.1, 1.0, 0.05 ).onChange( v => renderer.setPixelRatio( v * window.devicePixelRatio ) ); + gui.open(); + +} + +function onWindowResize() { + + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize( window.innerWidth, window.innerHeight ); + +} + +function render() { + + requestAnimationFrame( render ); + + camera.updateMatrixWorld(); + + groundTiles.errorTarget = params.errorTarget; + + groundTiles.setCamera( camera ); + groundTiles.setResolutionFromRenderer( camera, renderer ); + groundTiles.displayActiveTiles = params.displayActiveTiles; + groundTiles.update(); + + skyTiles.setCamera( camera ); + skyTiles.setResolutionFromRenderer( camera, renderer ); + skyTiles.displayActiveTiles = params.displayActiveTiles; + skyTiles.update(); + + groundTiles.fadeDuration = params.fadeDuration * 1000; + skyTiles.fadeDuration = params.fadeDuration * 1000; + + renderer.render( scene, camera ); + +} From 5c1da4bbcfb912d7ccaf5c9da639084b33838c4d Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 21 Dec 2023 00:44:34 +0900 Subject: [PATCH 07/17] Add an example of dither fade tiles --- example/fadingTiles.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/fadingTiles.html b/example/fadingTiles.html index 6ba9f8881..39fd0cdcf 100644 --- a/example/fadingTiles.html +++ b/example/fadingTiles.html @@ -4,7 +4,7 @@ - Dingo Gap Tile Set + Dither Fade Tiles