From dc296606d91832ed6ee7f04e0c0f1707a185f931 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 18 Apr 2024 19:33:30 -0700 Subject: [PATCH 01/24] Add initial texture skinning --- example/src/AlternateTextureTilesRenderer.js | 275 +++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 example/src/AlternateTextureTilesRenderer.js diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js new file mode 100644 index 00000000..a022c50d --- /dev/null +++ b/example/src/AlternateTextureTilesRenderer.js @@ -0,0 +1,275 @@ +import { TextureLoader } from 'three'; + +// TODO: Enable TilesRenderer to delay load model events until all textures have loaded +// TODO: Load textures while the tile geometry is loading +// TODO: Make sure we fire symmetrical events +// TODO: Add basic overlay support for custom materials w/ opacity blending +// TODO: Add support for toggling layers +// TODO: Make it easier / more clear to get all loaded tiles and associated scene data. Maybe a "forEach" function? +class TextureCache { + + constructor() { + + this.cache = {}; + this.urlResolver = url => url; + this.fetchOptions = {}; + + } + + getTextureLoader() { + + const fetchOptions = this.fetchOptions; + const loader = new TextureLoader(); + + if ( fetchOptions.credentials === 'include' && fetchOptions.mode === 'cors' ) { + + loader.setCrossOrigin( 'use-credentials' ); + + } + + if ( 'credentials' in fetchOptions ) { + + loader.setWithCredentials( fetchOptions.credentials === 'include' ); + + } + + if ( fetchOptions.headers ) { + + loader.setRequestHeader( fetchOptions.headers ); + + } + + return loader; + + } + + loadTexture( key ) { + + const cache = cache; + if ( key in cache ) { + + cache[ key ].refs ++; + return cache[ key ].promise; + + } + + const abortController = new AbortController(); + const promise = this.getTextureLoader() + .loadAsync( this.urlResolver( key ) ) + .then( tex => { + + if ( ! abortController.signal.aborted ) { + + cache[ key ].texture = tex; + return tex; + + } else { + + throw new Error( 'TextureCache: Texture load aborted.' ); + + } + + } ); + + this.cache = { + refs: 1, + texture: null, + abortController, + promise, + }; + + return promise; + + } + + getTexture( key ) { + + const cache = this.cache; + if ( key in cache ) { + + return cache[ key ].texture; + + } else { + + return null; + + } + + } + + deleteTexture( key ) { + + const cache = this.cache; + if ( key in cache ) { + + const info = cache[ key ]; + info.refs --; + + if ( info.refs === 0 ) { + + if ( info.texture ) { + + info.texture.dispose(); + + } else if ( info.abortController ) { + + info.abortController.abort(); + + } + + delete this.cache[ key ]; + + } + + } + + } + + dispose() { + + const cache = this.cache; + for ( const key in cache ) { + + const info = cache[ key ]; + if ( info.texture ) { + + info.texture.dispose(); + + } else if ( info.abortController ) { + + info.abortController.abort(); + + } + + } + + } + +} + +export const AlternateTextureTilesRendererMixin = base => class extends base { + + constructor( ...args ) { + + super( ...args ); + this.caches = {}; + this.urlResolver = ( name, key ) => null; + + this.addEventListener( 'load-model', ( { scene, tile } ) => { + + const caches = this.caches; + for ( const key in caches ) { + + const cache = caches[ key ]; + cache + .loadTexture( this.getTileKey( tile ) ) + .then( texture => { + + this.dispatchEvent( { + event: 'load-layer-texture', + layer: key, + tile, + scene, + texture, + } ); + + } ) + .catch( () => {} ); + + + } + + } ); + + this.addEventListener( 'dispose-model', ( { tile } ) => { + + const caches = this.caches; + for ( const key in caches ) { + + const cache = caches[ key ]; + cache.deleteTexture( this.getTileKey( tile ) ); + + } + + } ); + + } + + getTileKey( tile ) { + + // TODO + return ''; + + } + + registerLayer( name ) { + + if ( name in this.caches ) { + + throw new Error(); + + } + + const cache = new TextureCache(); + cache.fetchOptions = this.fetchOptions; + cache.urlResolver = key => { + + return this.urlResolver( name, key ); + + }; + this.caches[ name ] = cache; + + this.activeTiles.forEach( tile => { + + const scene = tile.cached.scene; + cache + .loadTexture( this.getTileKey( tile ) ) + .then( texture => { + + this.dispatchEvent( { + event: 'load-layer-texture', + layer: name, + tile, + scene, + texture, + } ); + + } ) + .catch( () => {} ); + + } ); + + } + + unregisterLayer( name ) { + + const caches = this.caches; + if ( name in caches ) { + + const cache = caches[ name ]; + this.activeTiles.forEach( tile => { + + const texture = cache.getTexture( this.getTileKey( tile ) ); + if ( texture ) { + + const scene = tile.cached.scene; + this.dispatchEvent( { + type: 'delete-layer-texture', + layer: name, + tile, + scene, + texture, + } ); + + } + + } ); + + cache.dispose(); + delete caches[ name ]; + + } + + } + +}; From b497ca4a014bfc77eadb506e39eb0020d8d2aca1 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 18 Apr 2024 20:07:58 -0700 Subject: [PATCH 02/24] Updates --- example/src/AlternateTextureTilesRenderer.js | 56 +++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js index a022c50d..11f38701 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/AlternateTextureTilesRenderer.js @@ -1,11 +1,12 @@ import { TextureLoader } from 'three'; // TODO: Enable TilesRenderer to delay load model events until all textures have loaded -// TODO: Load textures while the tile geometry is loading +// TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? // TODO: Make sure we fire symmetrical events // TODO: Add basic overlay support for custom materials w/ opacity blending // TODO: Add support for toggling layers -// TODO: Make it easier / more clear to get all loaded tiles and associated scene data. Maybe a "forEach" function? +// TODO: What happens if a tile starts loading and then a layer is added, meaning it's not in the "loaded tiles" callback +// or active function and we haven't caught it in the parseTile function. Additional callback? Store the loading models? class TextureCache { constructor() { @@ -202,6 +203,16 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { } + getTexturesForTile( tile, order = null ) { + + const cacheArray = order ? order.map( name => this.caches[ name ] ).filter( c => c ) : Object.values( this.caches ); + const key = this.getTileKey( tile ); + return cacheArray + .map( c => c.getTexture( key ) ) + .filter( t => t ); + + } + registerLayer( name ) { if ( name in this.caches ) { @@ -219,9 +230,8 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { }; this.caches[ name ] = cache; - this.activeTiles.forEach( tile => { + this.forEachLoadedModel( ( scene, tile ) => { - const scene = tile.cached.scene; cache .loadTexture( this.getTileKey( tile ) ) .then( texture => { @@ -247,12 +257,11 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { if ( name in caches ) { const cache = caches[ name ]; - this.activeTiles.forEach( tile => { + this.forEachLoadedModel( ( scene, tile ) => { const texture = cache.getTexture( this.getTileKey( tile ) ); if ( texture ) { - const scene = tile.cached.scene; this.dispatchEvent( { type: 'delete-layer-texture', layer: name, @@ -273,3 +282,38 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { } }; + +function onBeforeCompileCallback( shader ) { + + shader.uniforms.textures = { + value: this.textures || [], + }; + + shader.fragmentShader = shader.fragmentShader + .replace( /#include /, m => /* glsl */` + + ${ m } + + #pragma unroll_loop_start + for ( int i = 0; i < ${ this.textures.length }; i ++ ) { + + vec4 v = texture( textures[ i ], vMapUv ); + diffuse = mix( diffuse, v, v.a ); + + } + #pragma unroll_loop_end + + ` ); + + this.onBeforeRender = () => { + + if ( this.textures !== this.defines.TEXTURE_COUNT ) { + + this.defines.TEXTURE_COUNT = this.textures.length; + this.needsUpdate = true; + + } + + }; + +} From f66c338489524d38ad94b510d1874b84869ead35 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 18 Apr 2024 20:47:44 -0700 Subject: [PATCH 03/24] Small fixes --- example/src/AlternateTextureTilesRenderer.js | 35 ++++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js index 11f38701..49a96433 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/AlternateTextureTilesRenderer.js @@ -7,6 +7,7 @@ import { TextureLoader } from 'three'; // TODO: Add support for toggling layers // TODO: What happens if a tile starts loading and then a layer is added, meaning it's not in the "loaded tiles" callback // or active function and we haven't caught it in the parseTile function. Additional callback? Store the loading models? +// TODO: Use image bitmap loader and / or LoadingManager callback class TextureCache { constructor() { @@ -46,7 +47,7 @@ class TextureCache { loadTexture( key ) { - const cache = cache; + const cache = this.cache; if ( key in cache ) { cache[ key ].refs ++; @@ -72,7 +73,7 @@ class TextureCache { } ); - this.cache = { + this.cache[ key ] = { refs: 1, texture: null, abortController, @@ -167,7 +168,7 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { .then( texture => { this.dispatchEvent( { - event: 'load-layer-texture', + type: 'load-layer-texture', layer: key, tile, scene, @@ -199,7 +200,7 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { getTileKey( tile ) { // TODO - return ''; + return tile.content.uri; } @@ -237,7 +238,7 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { .then( texture => { this.dispatchEvent( { - event: 'load-layer-texture', + type: 'load-layer-texture', layer: name, tile, scene, @@ -285,20 +286,31 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { function onBeforeCompileCallback( shader ) { + const textures = this.textures || []; + shader.defines = { + TEXTURE_COUNT: textures.length || 1, + }; + shader.uniforms.textures = { - value: this.textures || [], + value: textures, }; shader.fragmentShader = shader.fragmentShader + .replace( /void main/, m => /* glsl */` + uniform sampler2D textures[ TEXTURE_COUNT ]; + ${ m } + + ` ) .replace( /#include /, m => /* glsl */` ${ m } + vec4 col; #pragma unroll_loop_start - for ( int i = 0; i < ${ this.textures.length }; i ++ ) { + for ( int i = 0; i < ${ textures.length }; i ++ ) { - vec4 v = texture( textures[ i ], vMapUv ); - diffuse = mix( diffuse, v, v.a ); + col = texture( textures[ i ], vMapUv ); + diffuseColor = mix( diffuseColor, col, col.a ); } #pragma unroll_loop_end @@ -307,9 +319,10 @@ function onBeforeCompileCallback( shader ) { this.onBeforeRender = () => { - if ( this.textures !== this.defines.TEXTURE_COUNT ) { + const textures = this.textures || []; + if ( textures.length !== shader.defines.TEXTURE_COUNT ) { - this.defines.TEXTURE_COUNT = this.textures.length; + shader.defines.TEXTURE_COUNT = textures.length; this.needsUpdate = true; } From 1c927aa9f667bab5f3d54feff215fcf934fd5cea Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 18 Apr 2024 21:04:57 -0700 Subject: [PATCH 04/24] before compile functino --- example/src/AlternateTextureTilesRenderer.js | 53 +++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js index 49a96433..1b34f939 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/AlternateTextureTilesRenderer.js @@ -288,34 +288,49 @@ function onBeforeCompileCallback( shader ) { const textures = this.textures || []; shader.defines = { - TEXTURE_COUNT: textures.length || 1, + TEXTURE_COUNT: textures.length, }; - shader.uniforms.textures = { - value: textures, - }; + // WebGL does not seem to like empty texture arrays + if ( textures.length ) { - shader.fragmentShader = shader.fragmentShader - .replace( /void main/, m => /* glsl */` - uniform sampler2D textures[ TEXTURE_COUNT ]; - ${ m } + shader.uniforms.textures = { + value: textures, + }; - ` ) - .replace( /#include /, m => /* glsl */` + shader.fragmentShader = shader.fragmentShader + .replace( /void main/, m => /* glsl */` + #if TEXTURE_COUNT != 0 + uniform sampler2D textures[ TEXTURE_COUNT ]; + #endif + ${ m } - ${ m } + ` ) + .replace( /#include /, m => /* glsl */` - vec4 col; - #pragma unroll_loop_start - for ( int i = 0; i < ${ textures.length }; i ++ ) { + ${ m } - col = texture( textures[ i ], vMapUv ); - diffuseColor = mix( diffuseColor, col, col.a ); + vec4 col; + #if TEXTURE_COUNT != 0 + #pragma unroll_loop_start + for ( int i = 0; i < ${ textures.length }; i ++ ) { - } - #pragma unroll_loop_end + col = texture( textures[ i ], vMapUv ); + diffuseColor = mix( diffuseColor, col, col.a ); + + } + #pragma unroll_loop_end + #endif + + ` ); - ` ); + } + + this.customProgramCacheKey = () => { + + return this.textures.length; + + }; this.onBeforeRender = () => { From bc15ca4c318de43c23b8213918d69e53636a301c Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Thu, 18 Apr 2024 21:10:22 -0700 Subject: [PATCH 05/24] Add support for image bitmap loader --- example/src/AlternateTextureTilesRenderer.js | 47 +++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js index 1b34f939..bf711697 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/AlternateTextureTilesRenderer.js @@ -1,13 +1,28 @@ -import { TextureLoader } from 'three'; +import { Texture, TextureLoader, ImageBitmapLoader } from 'three'; // TODO: Enable TilesRenderer to delay load model events until all textures have loaded // TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? -// TODO: Make sure we fire symmetrical events -// TODO: Add basic overlay support for custom materials w/ opacity blending -// TODO: Add support for toggling layers // TODO: What happens if a tile starts loading and then a layer is added, meaning it's not in the "loaded tiles" callback // or active function and we haven't caught it in the parseTile function. Additional callback? Store the loading models? -// TODO: Use image bitmap loader and / or LoadingManager callback +function canUseImageBitmap() { + + let isSafari = false; + let isFirefox = false; + let firefoxVersion = - 1; + + if ( typeof navigator !== 'undefined' ) { + + isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; + isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; + firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; + + } + + + return ! ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ); + +} + class TextureCache { constructor() { @@ -21,7 +36,17 @@ class TextureCache { getTextureLoader() { const fetchOptions = this.fetchOptions; - const loader = new TextureLoader(); + + let loader; + if ( canUseImageBitmap() ) { + + loader = new ImageBitmapLoader(); + + } else { + + loader = new TextureLoader(); + + } if ( fetchOptions.credentials === 'include' && fetchOptions.mode === 'cors' ) { @@ -56,12 +81,20 @@ class TextureCache { } const abortController = new AbortController(); - const promise = this.getTextureLoader() + const loader = this.getTextureLoader(); + const promise = loader .loadAsync( this.urlResolver( key ) ) .then( tex => { if ( ! abortController.signal.aborted ) { + if ( loader.isImageBitmapLoader ) { + + tex = new Texture( tex ); + tex.needsUpdate = true; + + } + cache[ key ].texture = tex; return tex; From 2329c9db48f045d9f8aad82fb499a0693b06b378 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 19 Apr 2024 18:46:01 +0900 Subject: [PATCH 06/24] Add texture overlay test demo --- example/src/AlternateTextureTilesRenderer.js | 80 ++++++++-- example/textureOverlay.html | 19 +++ example/textureOverlay.js | 151 +++++++++++++++++++ 3 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 example/textureOverlay.html create mode 100644 example/textureOverlay.js diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/AlternateTextureTilesRenderer.js index bf711697..ea6b3bbf 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/AlternateTextureTilesRenderer.js @@ -4,6 +4,7 @@ import { Texture, TextureLoader, ImageBitmapLoader } from 'three'; // TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? // TODO: What happens if a tile starts loading and then a layer is added, meaning it's not in the "loaded tiles" callback // or active function and we haven't caught it in the parseTile function. Additional callback? Store the loading models? + function canUseImageBitmap() { let isSafari = false; @@ -25,12 +26,38 @@ function canUseImageBitmap() { class TextureCache { - constructor() { + constructor( loadTextureCallback = null ) { this.cache = {}; this.urlResolver = url => url; this.fetchOptions = {}; + if ( loadTextureCallback === null ) { + + this.loadTextureCallback = ( url, key ) => { + + const loader = this.getTextureLoader(); + return loader.loadAsync( url ).then( tex => { + + if ( loader.isImageBitmapLoader ) { + + tex = new Texture( tex ); + tex.needsUpdate = true; + + } + + return tex; + + } ); + + }; + + } else { + + this.loadTextureCallback = loadTextureCallback; + + } + } getTextureLoader() { @@ -81,20 +108,12 @@ class TextureCache { } const abortController = new AbortController(); - const loader = this.getTextureLoader(); - const promise = loader - .loadAsync( this.urlResolver( key ) ) + const promise = this + .loadTextureCallback( this.urlResolver( key ), key ) .then( tex => { if ( ! abortController.signal.aborted ) { - if ( loader.isImageBitmapLoader ) { - - tex = new Texture( tex ); - tex.needsUpdate = true; - - } - cache[ key ].texture = tex; return tex; @@ -190,6 +209,37 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { this.caches = {}; this.urlResolver = ( name, key ) => null; + this.addEventListener( 'delete-layer-texture', ( { scene, tile } ) => { + + const textures = this.getTexturesForTile( tile ); + scene.traverse( c => { + + if ( c.material ) { + + c.material.textures = textures; + + } + + } ); + + } ); + + this.addEventListener( 'load-layer-texture', ( { scene, tile } ) => { + + const textures = this.getTexturesForTile( tile ); + scene.traverse( c => { + + if ( c.material ) { + + c.material.onBeforeCompile = onBeforeCompileCallback; + c.material.textures = textures; + + } + + } ); + + } ); + this.addEventListener( 'load-model', ( { scene, tile } ) => { const caches = this.caches; @@ -241,13 +291,14 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { const cacheArray = order ? order.map( name => this.caches[ name ] ).filter( c => c ) : Object.values( this.caches ); const key = this.getTileKey( tile ); + return cacheArray .map( c => c.getTexture( key ) ) .filter( t => t ); } - registerLayer( name ) { + registerLayer( name, customTextureCallback = null ) { if ( name in this.caches ) { @@ -255,7 +306,7 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { } - const cache = new TextureCache(); + const cache = new TextureCache( customTextureCallback ); cache.fetchOptions = this.fetchOptions; cache.urlResolver = key => { @@ -291,6 +342,8 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { if ( name in caches ) { const cache = caches[ name ]; + delete caches[ name ]; + this.forEachLoadedModel( ( scene, tile ) => { const texture = cache.getTexture( this.getTileKey( tile ) ); @@ -309,7 +362,6 @@ export const AlternateTextureTilesRendererMixin = base => class extends base { } ); cache.dispose(); - delete caches[ name ]; } diff --git a/example/textureOverlay.html b/example/textureOverlay.html new file mode 100644 index 00000000..7f865526 --- /dev/null +++ b/example/textureOverlay.html @@ -0,0 +1,19 @@ + + + + + + + Texture Overlays + + + + + + + diff --git a/example/textureOverlay.js b/example/textureOverlay.js new file mode 100644 index 00000000..01cd126a --- /dev/null +++ b/example/textureOverlay.js @@ -0,0 +1,151 @@ +import { + TilesRenderer, +} from '../src/index.js'; +import { + Scene, + DirectionalLight, + AmbientLight, + WebGLRenderer, + PerspectiveCamera, + Group, +} from 'three'; +import { FlyOrbitControls } from './src/controls/FlyOrbitControls.js'; +import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; +import { Box3 } from 'three'; +import { Sphere } from 'three'; +import { AlternateTextureTilesRendererMixin } from './src/AlternateTextureTilesRenderer.js'; +import { DataTexture } from 'three'; + +let camera, controls, scene, renderer; +let tiles; + +const params = { + + errorTarget: 12, + +}; + +init(); +render(); + +function init() { + + 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 ).multiplyScalar( 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 ); + + const cons = AlternateTextureTilesRendererMixin( TilesRenderer ); + + const url = '../data/tileset.json'; + tiles = new cons( url ); + tiles.fetchOptions.mode = 'cors'; + tiles.lruCache.minSize = 900; + tiles.lruCache.maxSize = 1300; + tiles.errorTarget = 12; + + const db = new DataTexture( new Uint8Array( [ 0, 255, 0, 25 ] ) ); + db.needsUpdate = true; + + tiles.registerLayer( 'red', async () => { + + const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 25 ] ) ); + dt.needsUpdate = true; + return dt; + + } ); + + tiles.registerLayer( 'blue', async () => { + + const dt = new DataTexture( new Uint8Array( [ 0, 0, 255, 25 ] ) ); + dt.needsUpdate = true; + return dt; + + } ); + + window.UNREGISTER = () => tiles.unregisterLayer( 'red' ); + + + tilesParent.add( tiles.group ); + + onWindowResize(); + window.addEventListener( 'resize', onWindowResize, false ); + + const gui = new GUI(); + gui.add( params, 'errorTarget', 0, 100 ); + gui.open(); + +} + +function onWindowResize() { + + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setPixelRatio( window.devicePixelRatio ); + +} + +function render() { + + requestAnimationFrame( render ); + + camera.updateMatrixWorld(); + + tiles.errorTarget = params.errorTarget; + + tiles.setCamera( camera ); + tiles.setResolutionFromRenderer( camera, renderer ); + tiles.update(); + + tiles.group.rotation.x = - Math.PI / 2; + + const box = new Box3(); + const sphere = new Sphere(); + if ( tiles.getBoundingBox( box ) ) { + + box.getCenter( tiles.group.position ); + tiles.group.position.multiplyScalar( - 1 ); + + } else if ( tiles.getBoundingSphere( sphere ) ) { + + tiles.group.position.copy( sphere.center ); + tiles.group.position.multiplyScalar( - 1 ); + + } + + tiles.group.position.z -= - 150; + + renderer.render( scene, camera ); + +} From f32742ca821c7262985b4645e099a696ba72e442 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 19 Apr 2024 18:48:26 +0900 Subject: [PATCH 07/24] file rename --- ...tureTilesRenderer.js => TextureOverlayTilesRenderer.js} | 7 ++++++- example/textureOverlay.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) rename example/src/{AlternateTextureTilesRenderer.js => TextureOverlayTilesRenderer.js} (97%) diff --git a/example/src/AlternateTextureTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js similarity index 97% rename from example/src/AlternateTextureTilesRenderer.js rename to example/src/TextureOverlayTilesRenderer.js index ea6b3bbf..e127a118 100644 --- a/example/src/AlternateTextureTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -188,6 +188,11 @@ class TextureCache { if ( info.texture ) { info.texture.dispose(); + if ( info.texture.image instanceof ImageBitmap ) { + + info.texture.image.close(); + + } } else if ( info.abortController ) { @@ -201,7 +206,7 @@ class TextureCache { } -export const AlternateTextureTilesRendererMixin = base => class extends base { +export const TextureOverlayTilesRendererMixin = base => class extends base { constructor( ...args ) { diff --git a/example/textureOverlay.js b/example/textureOverlay.js index 01cd126a..27579052 100644 --- a/example/textureOverlay.js +++ b/example/textureOverlay.js @@ -13,7 +13,7 @@ import { FlyOrbitControls } from './src/controls/FlyOrbitControls.js'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { Box3 } from 'three'; import { Sphere } from 'three'; -import { AlternateTextureTilesRendererMixin } from './src/AlternateTextureTilesRenderer.js'; +import { TextureOverlayTilesRendererMixin } from './src/TextureOverlayTilesRenderer.js'; import { DataTexture } from 'three'; let camera, controls, scene, renderer; @@ -65,7 +65,7 @@ function init() { tilesParent.rotation.set( Math.PI / 2, 0, 0 ); scene.add( tilesParent ); - const cons = AlternateTextureTilesRendererMixin( TilesRenderer ); + const cons = TextureOverlayTilesRendererMixin( TilesRenderer ); const url = '../data/tileset.json'; tiles = new cons( url ); From ed14611130fd0def4e370fc2981eb3a65aa5be0c Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Fri, 19 Apr 2024 23:22:04 +0900 Subject: [PATCH 08/24] small fixes --- example/src/TextureOverlayTilesRenderer.js | 33 ++++++------ example/textureOverlay.js | 58 +++++++++++++++++----- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index e127a118..3aca78c1 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -237,6 +237,8 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { if ( c.material ) { c.material.onBeforeCompile = onBeforeCompileCallback; + c.material.onBeforeRender = onBeforeRender; + c.material.customProgramCacheKey = customProgramCacheKey; c.material.textures = textures; } @@ -377,12 +379,9 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { function onBeforeCompileCallback( shader ) { const textures = this.textures || []; - shader.defines = { - TEXTURE_COUNT: textures.length, - }; // WebGL does not seem to like empty texture arrays - if ( textures.length ) { + if ( textures.length !== 0 ) { shader.uniforms.textures = { value: textures, @@ -390,9 +389,7 @@ function onBeforeCompileCallback( shader ) { shader.fragmentShader = shader.fragmentShader .replace( /void main/, m => /* glsl */` - #if TEXTURE_COUNT != 0 - uniform sampler2D textures[ TEXTURE_COUNT ]; - #endif + uniform sampler2D textures[ ${ textures.length } ]; ${ m } ` ) @@ -401,7 +398,6 @@ function onBeforeCompileCallback( shader ) { ${ m } vec4 col; - #if TEXTURE_COUNT != 0 #pragma unroll_loop_start for ( int i = 0; i < ${ textures.length }; i ++ ) { @@ -410,28 +406,27 @@ function onBeforeCompileCallback( shader ) { } #pragma unroll_loop_end - #endif ` ); } - this.customProgramCacheKey = () => { +} - return this.textures.length; +function customProgramCacheKey() { - }; + return this.textures.length + onBeforeCompileCallback.toString(); - this.onBeforeRender = () => { +} - const textures = this.textures || []; - if ( textures.length !== shader.defines.TEXTURE_COUNT ) { +function onBeforeRender() { - shader.defines.TEXTURE_COUNT = textures.length; - this.needsUpdate = true; + const textures = this.textures || []; + if ( textures.length !== this.lastTextureCount ) { - } + this.lastTextureCount = textures.length; + this.needsUpdate = true; - }; + } } diff --git a/example/textureOverlay.js b/example/textureOverlay.js index 27579052..5cf5a462 100644 --- a/example/textureOverlay.js +++ b/example/textureOverlay.js @@ -21,7 +21,8 @@ let tiles; const params = { - errorTarget: 12, + layer1: true, + layer2: true, }; @@ -74,35 +75,68 @@ function init() { tiles.lruCache.maxSize = 1300; tiles.errorTarget = 12; - const db = new DataTexture( new Uint8Array( [ 0, 255, 0, 25 ] ) ); - db.needsUpdate = true; + window.tiles = tiles; - tiles.registerLayer( 'red', async () => { + tiles.registerLayer( 'layer1', async () => { - const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 25 ] ) ); + const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 50 ] ) ); dt.needsUpdate = true; return dt; } ); - tiles.registerLayer( 'blue', async () => { + tiles.registerLayer( 'layer2', async () => { - const dt = new DataTexture( new Uint8Array( [ 0, 0, 255, 25 ] ) ); + const dt = new DataTexture( new Uint8Array( [ 0, 0, 255, 50 ] ) ); dt.needsUpdate = true; return dt; } ); - window.UNREGISTER = () => tiles.unregisterLayer( 'red' ); - - tilesParent.add( tiles.group ); onWindowResize(); window.addEventListener( 'resize', onWindowResize, false ); const gui = new GUI(); - gui.add( params, 'errorTarget', 0, 100 ); + gui.add( params, 'layer1' ).onChange( v => { + + if ( v ) { + + tiles.registerLayer( 'layer1', async () => { + + const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 50 ] ) ); + dt.needsUpdate = true; + return dt; + + } ); + + } else { + + tiles.unregisterLayer( 'layer1' ); + + } + + } ); + gui.add( params, 'layer2' ).onChange( v => { + + if ( v ) { + + tiles.registerLayer( 'layer2', async () => { + + const dt = new DataTexture( new Uint8Array( [ 0, 0, 255, 50 ] ) ); + dt.needsUpdate = true; + return dt; + + } ); + + } else { + + tiles.unregisterLayer( 'layer2' ); + + } + + } ); gui.open(); } @@ -122,8 +156,6 @@ function render() { camera.updateMatrixWorld(); - tiles.errorTarget = params.errorTarget; - tiles.setCamera( camera ); tiles.setResolutionFromRenderer( camera, renderer ); tiles.update(); From 42ec27667eb95c0dae77940c77b5201369dc6be9 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 20 Apr 2024 05:28:22 +0900 Subject: [PATCH 09/24] Uniform fixes --- example/src/TextureOverlayTilesRenderer.js | 12 +++++++++--- example/textureOverlay.js | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 3aca78c1..27dd6abd 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -379,13 +379,19 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { function onBeforeCompileCallback( shader ) { const textures = this.textures || []; + const material = this; + + shader.uniforms.textures = { + get value() { + + return material.textures || []; + + }, + }; // WebGL does not seem to like empty texture arrays if ( textures.length !== 0 ) { - shader.uniforms.textures = { - value: textures, - }; shader.fragmentShader = shader.fragmentShader .replace( /void main/, m => /* glsl */` diff --git a/example/textureOverlay.js b/example/textureOverlay.js index 5cf5a462..e30328d4 100644 --- a/example/textureOverlay.js +++ b/example/textureOverlay.js @@ -118,6 +118,7 @@ function init() { } } ); + gui.add( params, 'layer2' ).onChange( v => { if ( v ) { From ee1db5a49dbbe3228153987d6535790bf5077786 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 30 Jun 2024 22:59:26 +0900 Subject: [PATCH 10/24] Update demo --- example/landformSite.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index 43387281..34ccd437 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -4,14 +4,14 @@ import { } from '..'; import { Scene, - DirectionalLight, - AmbientLight, WebGLRenderer, PerspectiveCamera, Group, + DataTexture, } from 'three'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { JPLLandformSiteSceneLoader } from './src/JPLLandformSceneLoader.js'; +import { TextureOverlayTilesRendererMixin } from './src/TextureOverlayTilesRenderer.js'; const URLS = [ @@ -51,6 +51,7 @@ let camera, controls, scene, renderer; const params = { errorTarget: 12, + layer1: false, }; @@ -89,6 +90,15 @@ function init() { let downloadQueue = null; let parseQueue = null; let lruCache = null; + const layerFunction = async () => { + + const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 50 ] ) ); + dt.needsUpdate = true; + + return dt; + + }; + URLS.forEach( async url => { const scene = await new JPLLandformSiteSceneLoader().load( url ); @@ -98,7 +108,8 @@ function init() { scene.tilesets.forEach( info => { const url = [ ...tokens, `${ info.id }_tileset.json` ].join( '/' ); - const tiles = new TilesRenderer( url ); + const TextureOverlayTilesRenderer = TextureOverlayTilesRendererMixin( TilesRenderer ); + const tiles = new TextureOverlayTilesRenderer( url ); lruCache = lruCache || tiles.lruCache; parseQueue = parseQueue || tiles.parseQueue; @@ -123,6 +134,19 @@ function init() { const gui = new GUI(); gui.add( params, 'errorTarget', 0, 100 ); + gui.add( params, 'layer1' ).onChange( v => { + + if ( v ) { + + tileSets.forEach( t => t.registerLayer( 'layer1', layerFunction ) ); + + } else { + + tileSets.forEach( t => t.unregisterLayer( 'layer1' ) ); + + } + + } ); gui.open(); } From 84d5400dcd897353aa382c62125f4b2ef6f27217 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 30 Jun 2024 23:08:01 +0900 Subject: [PATCH 11/24] Remove cache --- example/src/TextureOverlayTilesRenderer.js | 56 +++++++--------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 27dd6abd..6588ec72 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -1,4 +1,4 @@ -import { Texture, TextureLoader, ImageBitmapLoader } from 'three'; +import { TextureLoader, ImageBitmapLoader } from 'three'; // TODO: Enable TilesRenderer to delay load model events until all textures have loaded // TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? @@ -26,37 +26,11 @@ function canUseImageBitmap() { class TextureCache { - constructor( loadTextureCallback = null ) { + constructor( loadTextureCallback ) { this.cache = {}; - this.urlResolver = url => url; this.fetchOptions = {}; - - if ( loadTextureCallback === null ) { - - this.loadTextureCallback = ( url, key ) => { - - const loader = this.getTextureLoader(); - return loader.loadAsync( url ).then( tex => { - - if ( loader.isImageBitmapLoader ) { - - tex = new Texture( tex ); - tex.needsUpdate = true; - - } - - return tex; - - } ); - - }; - - } else { - - this.loadTextureCallback = loadTextureCallback; - - } + this.loadTextureCallback = loadTextureCallback; } @@ -102,14 +76,13 @@ class TextureCache { const cache = this.cache; if ( key in cache ) { - cache[ key ].refs ++; return cache[ key ].promise; } const abortController = new AbortController(); const promise = this - .loadTextureCallback( this.urlResolver( key ), key ) + .loadTextureCallback( key ) .then( tex => { if ( ! abortController.signal.aborted ) { @@ -126,7 +99,6 @@ class TextureCache { } ); this.cache[ key ] = { - refs: 1, texture: null, abortController, promise, @@ -212,7 +184,6 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { super( ...args ); this.caches = {}; - this.urlResolver = ( name, key ) => null; this.addEventListener( 'delete-layer-texture', ( { scene, tile } ) => { @@ -305,7 +276,7 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } - registerLayer( name, customTextureCallback = null ) { + registerLayer( name, customTextureCallback ) { if ( name in this.caches ) { @@ -315,11 +286,6 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { const cache = new TextureCache( customTextureCallback ); cache.fetchOptions = this.fetchOptions; - cache.urlResolver = key => { - - return this.urlResolver( name, key ); - - }; this.caches[ name ] = cache; this.forEachLoadedModel( ( scene, tile ) => { @@ -374,6 +340,18 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } + enableLayer( name ) { + + // TODO + + } + + disableLayer( name ) { + + // TODO + + } + }; function onBeforeCompileCallback( shader ) { From abf8fdf90bd7f74a28e2024d912bcb82487a240e Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 30 Jun 2024 23:24:38 +0900 Subject: [PATCH 12/24] Load textures --- example/landformSite.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/example/landformSite.js b/example/landformSite.js index 34ccd437..cb8f4353 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -8,6 +8,7 @@ import { PerspectiveCamera, Group, DataTexture, + TextureLoader, } from 'three'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { JPLLandformSiteSceneLoader } from './src/JPLLandformSceneLoader.js'; @@ -90,7 +91,18 @@ function init() { let downloadQueue = null; let parseQueue = null; let lruCache = null; - const layerFunction = async () => { + const layerFunction = async tileUrl => { + + const url = tileUrl.replace( '/tilesets/', '/textures/SMG/' ).replace( /\.[0-9a-z]+$/i, '.png' ); + + return new TextureLoader() + .loadAsync( url ) + .then( tex => { + + tex.flipY = false; + return tex; + + } ); const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 50 ] ) ); dt.needsUpdate = true; From 60c7a4417bb503ba2f5a7b018b59cb1dc3dea106 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 30 Jun 2024 23:29:28 +0900 Subject: [PATCH 13/24] slope update --- example/landformSite.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index cb8f4353..54795d32 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -52,7 +52,7 @@ let camera, controls, scene, renderer; const params = { errorTarget: 12, - layer1: false, + slopeLayer: false, }; @@ -146,15 +146,15 @@ function init() { const gui = new GUI(); gui.add( params, 'errorTarget', 0, 100 ); - gui.add( params, 'layer1' ).onChange( v => { + gui.add( params, 'slopeLayer' ).onChange( v => { if ( v ) { - tileSets.forEach( t => t.registerLayer( 'layer1', layerFunction ) ); + tileSets.forEach( t => t.registerLayer( 'slopeLayer', layerFunction ) ); } else { - tileSets.forEach( t => t.unregisterLayer( 'layer1' ) ); + tileSets.forEach( t => t.unregisterLayer( 'slopeLayer' ) ); } From d31fae1b295894059eb9048bfaf8c1f366c79c10 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 30 Jun 2024 23:30:39 +0900 Subject: [PATCH 14/24] remove unused lines --- example/landformSite.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index 54795d32..49cc068e 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -104,11 +104,6 @@ function init() { } ); - const dt = new DataTexture( new Uint8Array( [ 255, 0, 0, 50 ] ) ); - dt.needsUpdate = true; - - return dt; - }; URLS.forEach( async url => { From c266d3756cc0ffdbb79e46284c24349b37e7d4bd Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 10:50:50 +0900 Subject: [PATCH 15/24] Limit the data that can be downloaded at once with a priority queue --- example/src/TextureOverlayTilesRenderer.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 6588ec72..13b61f4c 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -1,4 +1,5 @@ import { TextureLoader, ImageBitmapLoader } from 'three'; +import { PriorityQueue } from '../..'; // TODO: Enable TilesRenderer to delay load model events until all textures have loaded // TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? @@ -26,11 +27,12 @@ function canUseImageBitmap() { class TextureCache { - constructor( loadTextureCallback ) { + constructor( loadTextureCallback, queue ) { this.cache = {}; this.fetchOptions = {}; this.loadTextureCallback = loadTextureCallback; + this.queue = queue; } @@ -81,8 +83,12 @@ class TextureCache { } const abortController = new AbortController(); - const promise = this - .loadTextureCallback( key ) + const promise = this.queue + .add( key, () => { + + return this.loadTextureCallback( key ); + + } ) .then( tex => { if ( ! abortController.signal.aborted ) { @@ -140,6 +146,7 @@ class TextureCache { } else if ( info.abortController ) { info.abortController.abort(); + this.queue.remove( key ); } @@ -184,6 +191,12 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { super( ...args ); this.caches = {}; + this.queue = new PriorityQueue(); + this.queue.priorityCallback = ( a, b ) => { + + return this.downloadQueue.priorityCallback( a, b ); + + }; this.addEventListener( 'delete-layer-texture', ( { scene, tile } ) => { @@ -284,7 +297,7 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } - const cache = new TextureCache( customTextureCallback ); + const cache = new TextureCache( customTextureCallback, this.queue ); cache.fetchOptions = this.fetchOptions; this.caches[ name ] = cache; From 58fb3a605f934d9e3fded03f04ef24d37fc97c54 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 11:18:04 +0900 Subject: [PATCH 16/24] Update textures result --- example/src/TextureOverlayTilesRenderer.js | 25 +++++++++++++++------- src/three/controls/EnvironmentControls.js | 4 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 13b61f4c..36654fe0 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -200,7 +200,7 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { this.addEventListener( 'delete-layer-texture', ( { scene, tile } ) => { - const textures = this.getTexturesForTile( tile ); + const textures = Object.values( this.getTexturesForTile( tile ) ); scene.traverse( c => { if ( c.material ) { @@ -215,7 +215,7 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { this.addEventListener( 'load-layer-texture', ( { scene, tile } ) => { - const textures = this.getTexturesForTile( tile ); + const textures = Object.values( this.getTexturesForTile( tile ) ); scene.traverse( c => { if ( c.material ) { @@ -278,14 +278,23 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } - getTexturesForTile( tile, order = null ) { + getTexturesForTile( tile, target = {} ) { - const cacheArray = order ? order.map( name => this.caches[ name ] ).filter( c => c ) : Object.values( this.caches ); - const key = this.getTileKey( tile ); + const tileKey = this.getTileKey( tile ); + const caches = this.caches; + for ( const key in target ) { + + if ( ! ( key in caches ) ) delete target[ key ]; + + } + + for ( const key in caches ) { + + target[ key ] = caches[ key ].getTexture( tileKey ); + + } - return cacheArray - .map( c => c.getTexture( key ) ) - .filter( t => t ); + return target; } diff --git a/src/three/controls/EnvironmentControls.js b/src/three/controls/EnvironmentControls.js index c00ca4c4..ec469ac3 100644 --- a/src/three/controls/EnvironmentControls.js +++ b/src/three/controls/EnvironmentControls.js @@ -657,8 +657,8 @@ export class EnvironmentControls extends EventDispatcher { // track the zoom direction we're going to use const finalZoomDirection = _vec.copy( zoomDirection ); - // always update the zoom target point in case the tiles are changing - if ( this._updateZoomPoint() ) { + // TODO: always update the zoom target point in case the tiles are changing? + if ( this.zoomPointSet || this._updateZoomPoint() ) { const dist = zoomPoint.distanceTo( camera.position ); From a5fa26eb1ee36c70988290ea98f24607e634bd34 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 11:38:03 +0900 Subject: [PATCH 17/24] Unset zoom point when tiles change --- src/three/controls/EnvironmentControls.js | 31 +++++++++++++++++++++-- src/three/controls/GlobeControls.js | 20 +++------------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/three/controls/EnvironmentControls.js b/src/three/controls/EnvironmentControls.js index ec469ac3..c0686051 100644 --- a/src/three/controls/EnvironmentControls.js +++ b/src/three/controls/EnvironmentControls.js @@ -63,13 +63,14 @@ export class EnvironmentControls extends EventDispatcher { } - constructor( scene = null, camera = null, domElement = null ) { + constructor( scene = null, camera = null, domElement = null, tilesRenderer = null ) { super(); this.domElement = null; this.camera = null; this.scene = null; + this.tilesRenderer = null; // settings this._enabled = true; @@ -116,10 +117,14 @@ export class EnvironmentControls extends EventDispatcher { this._detachCallback = null; this._upInitialized = false; + // always update the zoom target point in case the tiles are changing + this._tilesOnChangeCallback = () => this.zoomPointSet = false; + // init if ( domElement ) this.attach( domElement ); if ( camera ) this.setCamera( camera ); if ( scene ) this.setScene( scene ); + if ( tilesRenderer ) this.setTilesRenderer( tilesRenderer ); } @@ -135,6 +140,29 @@ export class EnvironmentControls extends EventDispatcher { } + setTilesRenderer( tilesRenderer ) { + + if ( this.tilesRenderer ) { + + this.tilesRenderer.removeEventListener( 'tile-visibility-change', this._tilesOnChangeCallback ); + + } + + this.tilesRenderer = tilesRenderer; + if ( this.tilesRenderer !== null ) { + + this.tilesRenderer.addEventListener( 'tile-visibility-change', this._tilesOnChangeCallback ); + + if ( this.scene === null ) { + + this.setScene( this.tilesRenderer.group ); + + } + + } + + } + attach( domElement ) { if ( this.domElement ) { @@ -657,7 +685,6 @@ export class EnvironmentControls extends EventDispatcher { // track the zoom direction we're going to use const finalZoomDirection = _vec.copy( zoomDirection ); - // TODO: always update the zoom target point in case the tiles are changing? if ( this.zoomPointSet || this._updateZoomPoint() ) { const dist = zoomPoint.distanceTo( camera.position ); diff --git a/src/three/controls/GlobeControls.js b/src/three/controls/GlobeControls.js index ee0e738e..b8e9b684 100644 --- a/src/three/controls/GlobeControls.js +++ b/src/three/controls/GlobeControls.js @@ -34,13 +34,13 @@ export class GlobeControls extends EnvironmentControls { get ellipsoid() { - return this._tilesRenderer ? this._tilesRenderer.ellipsoid : null; + return this.tilesRenderer ? this.tilesRenderer.ellipsoid : null; } get tilesGroup() { - return this._tilesRenderer ? this._tilesRenderer.group : null; + return this.tilesRenderer ? this.tilesRenderer.group : null; } @@ -48,7 +48,6 @@ export class GlobeControls extends EnvironmentControls { // store which mode the drag stats are in super( scene, camera, domElement ); - this._tilesRenderer = null; this._dragMode = 0; this._rotationMode = 0; this.useFallbackPlane = false; @@ -57,22 +56,11 @@ export class GlobeControls extends EnvironmentControls { } - setTilesRenderer( tilesRenderer ) { - - this._tilesRenderer = tilesRenderer; - if ( this.scene === null && this._tilesRenderer !== null ) { - - this.setScene( this._tilesRenderer.group ); - - } - - } - setScene( scene ) { - if ( scene === null && this._tilesRenderer !== null ) { + if ( scene === null && this.tilesRenderer !== null ) { - super.setScene( this._tilesRenderer.group ); + super.setScene( this.tilesRenderer.group ); } else { From 1f8e1d49a53358ba7d9a37fbd51f761685603dc5 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 11:46:42 +0900 Subject: [PATCH 18/24] Move material application to demo page --- example/landformSite.js | 19 +++++- example/src/TextureOverlayMaterial.js | 59 +++++++++++++++++ example/src/TextureOverlayTilesRenderer.js | 77 +--------------------- 3 files changed, 79 insertions(+), 76 deletions(-) create mode 100644 example/src/TextureOverlayMaterial.js diff --git a/example/landformSite.js b/example/landformSite.js index 49cc068e..372f9888 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -7,12 +7,13 @@ import { WebGLRenderer, PerspectiveCamera, Group, - DataTexture, TextureLoader, + MeshBasicMaterial, } from 'three'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { JPLLandformSiteSceneLoader } from './src/JPLLandformSceneLoader.js'; import { TextureOverlayTilesRendererMixin } from './src/TextureOverlayTilesRenderer.js'; +import { TextureOverlayMaterialMixin } from './src/TextureOverlayMaterial.js'; const URLS = [ @@ -112,11 +113,27 @@ function init() { const tokens = url.split( /[\\/]/g ); tokens.pop(); + const TextureOverlayMaterial = TextureOverlayMaterialMixin( MeshBasicMaterial ); scene.tilesets.forEach( info => { const url = [ ...tokens, `${ info.id }_tileset.json` ].join( '/' ); const TextureOverlayTilesRenderer = TextureOverlayTilesRendererMixin( TilesRenderer ); const tiles = new TextureOverlayTilesRenderer( url ); + tiles.addEventListener( 'load-model', ( { scene } )=> { + + scene.traverse( c => { + + if ( c.material ) { + + const newMaterial = new TextureOverlayMaterial(); + newMaterial.copy( c.material ); + c.material = newMaterial; + + } + + } ); + + } ); lruCache = lruCache || tiles.lruCache; parseQueue = parseQueue || tiles.parseQueue; diff --git a/example/src/TextureOverlayMaterial.js b/example/src/TextureOverlayMaterial.js new file mode 100644 index 00000000..9e5fc826 --- /dev/null +++ b/example/src/TextureOverlayMaterial.js @@ -0,0 +1,59 @@ +export const TextureOverlayMaterialMixin = base => class extends base { + + constructor( ...args ) { + + super( ...args ); + this.textures = []; + + } + + onBeforeCompile( shader ) { + + const textures = this.textures; + const material = this; + + shader.uniforms.textures = { + get value() { + + return material.textures; + + }, + }; + + // WebGL does not seem to like empty texture arrays + if ( textures.length !== 0 ) { + + shader.fragmentShader = shader.fragmentShader + .replace( /void main/, m => /* glsl */` + uniform sampler2D textures[ ${ textures.length } ]; + ${ m } + + ` ) + .replace( /#include /, m => /* glsl */` + + ${ m } + + vec4 col; + #pragma unroll_loop_start + for ( int i = 0; i < ${ textures.length }; i ++ ) { + + col = texture( textures[ i ], vMapUv ); + col = vec4( 0, 0, 1, col.r ); + diffuseColor = mix( diffuseColor, vec4( 0.35, 0.65, 1, 1 ), col.a ); + + } + #pragma unroll_loop_end + + ` ); + + } + + } + + customProgramCacheKey() { + + return this.textures.length + this.onBeforeCompile.toString(); + + } + +}; diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 36654fe0..95ff6190 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -206,6 +206,7 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { if ( c.material ) { c.material.textures = textures; + c.material.needsUpdate = true; } @@ -220,10 +221,8 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { if ( c.material ) { - c.material.onBeforeCompile = onBeforeCompileCallback; - c.material.onBeforeRender = onBeforeRender; - c.material.customProgramCacheKey = customProgramCacheKey; c.material.textures = textures; + c.material.needsUpdate = true; } @@ -362,77 +361,5 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } - enableLayer( name ) { - - // TODO - - } - - disableLayer( name ) { - - // TODO - - } - }; -function onBeforeCompileCallback( shader ) { - - const textures = this.textures || []; - const material = this; - - shader.uniforms.textures = { - get value() { - - return material.textures || []; - - }, - }; - - // WebGL does not seem to like empty texture arrays - if ( textures.length !== 0 ) { - - - shader.fragmentShader = shader.fragmentShader - .replace( /void main/, m => /* glsl */` - uniform sampler2D textures[ ${ textures.length } ]; - ${ m } - - ` ) - .replace( /#include /, m => /* glsl */` - - ${ m } - - vec4 col; - #pragma unroll_loop_start - for ( int i = 0; i < ${ textures.length }; i ++ ) { - - col = texture( textures[ i ], vMapUv ); - diffuseColor = mix( diffuseColor, col, col.a ); - - } - #pragma unroll_loop_end - - ` ); - - } - -} - -function customProgramCacheKey() { - - return this.textures.length + onBeforeCompileCallback.toString(); - -} - -function onBeforeRender() { - - const textures = this.textures || []; - if ( textures.length !== this.lastTextureCount ) { - - this.lastTextureCount = textures.length; - this.needsUpdate = true; - - } - -} From c765c3dc218f20bffe54fdfe55a88ec6dcb6219d Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 11:55:25 +0900 Subject: [PATCH 19/24] Clean up material handling --- example/landformSite.js | 15 ++++++++ example/src/TextureOverlayTilesRenderer.js | 44 ++++++---------------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index 372f9888..ce853386 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -135,6 +135,21 @@ function init() { } ); + tiles.addEventListener( 'layer-textures-change', ( { tile, scene } ) => { + + scene.traverse( c => { + + if ( c.material ) { + + c.material.textures = Object.values( tiles.getTexturesForTile( tile ) ); + c.material.needsUpdate = true; + + } + + } ); + + } ); + lruCache = lruCache || tiles.lruCache; parseQueue = parseQueue || tiles.parseQueue; downloadQueue = downloadQueue || tiles.downloadQueue; diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/TextureOverlayTilesRenderer.js index 95ff6190..ed88bfe8 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/TextureOverlayTilesRenderer.js @@ -198,38 +198,6 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { }; - this.addEventListener( 'delete-layer-texture', ( { scene, tile } ) => { - - const textures = Object.values( this.getTexturesForTile( tile ) ); - scene.traverse( c => { - - if ( c.material ) { - - c.material.textures = textures; - c.material.needsUpdate = true; - - } - - } ); - - } ); - - this.addEventListener( 'load-layer-texture', ( { scene, tile } ) => { - - const textures = Object.values( this.getTexturesForTile( tile ) ); - scene.traverse( c => { - - if ( c.material ) { - - c.material.textures = textures; - c.material.needsUpdate = true; - - } - - } ); - - } ); - this.addEventListener( 'load-model', ( { scene, tile } ) => { const caches = this.caches; @@ -323,6 +291,12 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { texture, } ); + this.dispatchEvent( { + type: 'layer-textures-change', + tile, + scene, + } ); + } ) .catch( () => {} ); @@ -351,6 +325,12 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { texture, } ); + this.dispatchEvent( { + type: 'layer-textures-change', + tile, + scene, + } ); + } } ); From f12078246d9333f6a2c382f97919b852971d6f7f Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 11:59:26 +0900 Subject: [PATCH 20/24] Reorganize foldrs --- example/fadingTiles.js | 4 ++-- example/googleMapsExample.js | 2 +- example/landformSite.js | 13 +++++++++---- example/src/{ => camera}/CameraTransitionManager.js | 0 example/src/{ => jpl}/JPLLandformSceneLoader.js | 0 example/src/{ => plugins/fade}/FadeManager.js | 0 example/src/{ => plugins/fade}/FadeTilesRenderer.js | 0 .../overlays}/TextureOverlayMaterial.js | 0 .../overlays}/TextureOverlayTilesRenderer.js | 2 +- 9 files changed, 13 insertions(+), 8 deletions(-) rename example/src/{ => camera}/CameraTransitionManager.js (100%) rename example/src/{ => jpl}/JPLLandformSceneLoader.js (100%) rename example/src/{ => plugins/fade}/FadeManager.js (100%) rename example/src/{ => plugins/fade}/FadeTilesRenderer.js (100%) rename example/src/{ => plugins/overlays}/TextureOverlayMaterial.js (100%) rename example/src/{ => plugins/overlays}/TextureOverlayTilesRenderer.js (99%) diff --git a/example/fadingTiles.js b/example/fadingTiles.js index 8ef8a157..bbb541b9 100644 --- a/example/fadingTiles.js +++ b/example/fadingTiles.js @@ -5,10 +5,10 @@ import { OrthographicCamera, Group, } from 'three'; -import { FadeTilesRenderer, } from './src/FadeTilesRenderer.js'; +import { FadeTilesRenderer, } from './src/plugins/fade/FadeTilesRenderer.js'; import { EnvironmentControls } from '../src/index.js'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; -import { CameraTransitionManager } from './src/CameraTransitionManager.js'; +import { CameraTransitionManager } from './src/camera/CameraTransitionManager.js'; let controls, scene, renderer; let groundTiles, skyTiles, tilesParent, transition; diff --git a/example/googleMapsExample.js b/example/googleMapsExample.js index 11870818..b34a15e9 100644 --- a/example/googleMapsExample.js +++ b/example/googleMapsExample.js @@ -16,7 +16,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { estimateBytesUsed } from 'three/examples/jsm/utils/BufferGeometryUtils.js'; import Stats from 'three/examples/jsm/libs/stats.module.js'; -import { CameraTransitionManager } from './src/CameraTransitionManager.js'; +import { CameraTransitionManager } from './src/camera/CameraTransitionManager.js'; let controls, scene, renderer, tiles, transition; let statsContainer, stats; diff --git a/example/landformSite.js b/example/landformSite.js index ce853386..7b94923c 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -11,9 +11,9 @@ import { MeshBasicMaterial, } from 'three'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; -import { JPLLandformSiteSceneLoader } from './src/JPLLandformSceneLoader.js'; -import { TextureOverlayTilesRendererMixin } from './src/TextureOverlayTilesRenderer.js'; -import { TextureOverlayMaterialMixin } from './src/TextureOverlayMaterial.js'; +import { JPLLandformSiteSceneLoader } from './src/jpl/JPLLandformSceneLoader.js'; +import { TextureOverlayTilesRendererMixin } from './src/plugins/overlays/TextureOverlayTilesRenderer.js'; +import { TextureOverlayMaterialMixin } from './src/plugins/overlays/TextureOverlayMaterial.js'; const URLS = [ @@ -113,12 +113,14 @@ function init() { const tokens = url.split( /[\\/]/g ); tokens.pop(); + const TextureOverlayTilesRenderer = TextureOverlayTilesRendererMixin( TilesRenderer ); const TextureOverlayMaterial = TextureOverlayMaterialMixin( MeshBasicMaterial ); scene.tilesets.forEach( info => { const url = [ ...tokens, `${ info.id }_tileset.json` ].join( '/' ); - const TextureOverlayTilesRenderer = TextureOverlayTilesRendererMixin( TilesRenderer ); const tiles = new TextureOverlayTilesRenderer( url ); + + // ensure all materials support overlay textures tiles.addEventListener( 'load-model', ( { scene } )=> { scene.traverse( c => { @@ -135,6 +137,7 @@ function init() { } ); + // assign the texture layers tiles.addEventListener( 'layer-textures-change', ( { tile, scene } ) => { scene.traverse( c => { @@ -150,6 +153,7 @@ function init() { } ); + // assign a common cache and data lruCache = lruCache || tiles.lruCache; parseQueue = parseQueue || tiles.parseQueue; downloadQueue = downloadQueue || tiles.downloadQueue; @@ -159,6 +163,7 @@ function init() { tiles.parseQueue = parseQueue; tiles.setCamera( camera ); + // update the scene const frame = scene.frames.find( f => f.id === info.frame_id ); frame.sceneMatrix.decompose( tiles.group.position, tiles.group.quaternion, tiles.group.scale ); tilesParent.add( tiles.group ); diff --git a/example/src/CameraTransitionManager.js b/example/src/camera/CameraTransitionManager.js similarity index 100% rename from example/src/CameraTransitionManager.js rename to example/src/camera/CameraTransitionManager.js diff --git a/example/src/JPLLandformSceneLoader.js b/example/src/jpl/JPLLandformSceneLoader.js similarity index 100% rename from example/src/JPLLandformSceneLoader.js rename to example/src/jpl/JPLLandformSceneLoader.js diff --git a/example/src/FadeManager.js b/example/src/plugins/fade/FadeManager.js similarity index 100% rename from example/src/FadeManager.js rename to example/src/plugins/fade/FadeManager.js diff --git a/example/src/FadeTilesRenderer.js b/example/src/plugins/fade/FadeTilesRenderer.js similarity index 100% rename from example/src/FadeTilesRenderer.js rename to example/src/plugins/fade/FadeTilesRenderer.js diff --git a/example/src/TextureOverlayMaterial.js b/example/src/plugins/overlays/TextureOverlayMaterial.js similarity index 100% rename from example/src/TextureOverlayMaterial.js rename to example/src/plugins/overlays/TextureOverlayMaterial.js diff --git a/example/src/TextureOverlayTilesRenderer.js b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js similarity index 99% rename from example/src/TextureOverlayTilesRenderer.js rename to example/src/plugins/overlays/TextureOverlayTilesRenderer.js index ed88bfe8..3a635bad 100644 --- a/example/src/TextureOverlayTilesRenderer.js +++ b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js @@ -1,5 +1,5 @@ import { TextureLoader, ImageBitmapLoader } from 'three'; -import { PriorityQueue } from '../..'; +import { PriorityQueue } from '../../../../src'; // TODO: Enable TilesRenderer to delay load model events until all textures have loaded // TODO: Load textures while the tile geometry is loading - can we start this sooner than parse tile? From cf4bd5466e3d26744d4e35747024e5ae0db68975 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 12:05:57 +0900 Subject: [PATCH 21/24] update --- example/src/plugins/fade/FadeTilesRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/plugins/fade/FadeTilesRenderer.js b/example/src/plugins/fade/FadeTilesRenderer.js index 500d299b..85dcf611 100644 --- a/example/src/plugins/fade/FadeTilesRenderer.js +++ b/example/src/plugins/fade/FadeTilesRenderer.js @@ -1,5 +1,5 @@ import { Group, Matrix4, Vector3, Quaternion } from 'three'; -import { TilesRenderer } from '../../src/index.js'; +import { TilesRenderer } from '../../../../src/index.js'; import { FadeManager } from './FadeManager.js'; const _fromPos = new Vector3(); From 3404d25eee0347d824a006e5d6140901508fba26 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 12:19:30 +0900 Subject: [PATCH 22/24] Stable loading --- example/landformSite.js | 3 +- .../overlays/TextureOverlayTilesRenderer.js | 39 ++--- src/three/TilesRenderer.js | 141 ++++++++++-------- 3 files changed, 93 insertions(+), 90 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index 7b94923c..ccea90c2 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -121,7 +121,7 @@ function init() { const tiles = new TextureOverlayTilesRenderer( url ); // ensure all materials support overlay textures - tiles.addEventListener( 'load-model', ( { scene } )=> { + tiles.addEventListener( 'load-model', ( { tile, scene } )=> { scene.traverse( c => { @@ -129,6 +129,7 @@ function init() { const newMaterial = new TextureOverlayMaterial(); newMaterial.copy( c.material ); + newMaterial.textures = Object.values( tiles.getTexturesForTile( tile ) ); c.material = newMaterial; } diff --git a/example/src/plugins/overlays/TextureOverlayTilesRenderer.js b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js index 3a635bad..a5f7d18f 100644 --- a/example/src/plugins/overlays/TextureOverlayTilesRenderer.js +++ b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js @@ -198,43 +198,36 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { }; - this.addEventListener( 'load-model', ( { scene, tile } ) => { + this.addEventListener( 'dispose-model', ( { tile } ) => { const caches = this.caches; for ( const key in caches ) { const cache = caches[ key ]; - cache - .loadTexture( this.getTileKey( tile ) ) - .then( texture => { - - this.dispatchEvent( { - type: 'load-layer-texture', - layer: key, - tile, - scene, - texture, - } ); - - } ) - .catch( () => {} ); - + cache.deleteTexture( this.getTileKey( tile ) ); } } ); - this.addEventListener( 'dispose-model', ( { tile } ) => { + } - const caches = this.caches; - for ( const key in caches ) { + _pluginProcessTileModel( scene, tile ) { - const cache = caches[ key ]; - cache.deleteTexture( this.getTileKey( tile ) ); + const caches = this.caches; + const promises = []; + for ( const key in caches ) { - } + const cache = caches[ key ]; + const pr = cache + .loadTexture( this.getTileKey( tile ) ) + .catch( () => {} ); - } ); + promises.push( pr ); + + } + + return Promise.all( promises ); } diff --git a/src/three/TilesRenderer.js b/src/three/TilesRenderer.js index 7d2e7cd7..0fb88405 100644 --- a/src/three/TilesRenderer.js +++ b/src/three/TilesRenderer.js @@ -536,7 +536,7 @@ export class TilesRenderer extends TilesRendererBase { } - parseTile( buffer, tile, extension ) { + async parseTile( buffer, tile, extension ) { tile._loadIndex = tile._loadIndex || 0; tile._loadIndex ++; @@ -641,112 +641,117 @@ export class TilesRenderer extends TilesRendererBase { } - return promise.then( result => { + // wait for the tile to load + const result = await promise; - let scene; - let metadata; - if ( result.isObject3D ) { + // get the scene data + let scene; + let metadata; + if ( result.isObject3D ) { - scene = result; - metadata = null; + scene = result; + metadata = null; - } else { + } else { - scene = result.scene; - metadata = result; + scene = result.scene; + metadata = result; - } + } - if ( tile._loadIndex !== loadIndex ) { + // wait for extra processing if needed + // TODO: this should be handled by a plugin + await this._pluginProcessTileModel( scene, tile ); - return; + // exit early if a new request has already started + if ( tile._loadIndex !== loadIndex ) { - } + return; - // ensure the matrix is up to date in case the scene has a transform applied - scene.updateMatrix(); + } - // apply the local up-axis correction rotation - // GLTFLoader seems to never set a transformation on the root scene object so - // any transformations applied to it can be assumed to be applied after load - // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up - // rotation fix which is why "multiply" happens here. - if ( fileType === 'glb' || fileType === 'gltf' ) { + // ensure the matrix is up to date in case the scene has a transform applied + scene.updateMatrix(); - scene.matrix.multiply( upAdjustment ); + // apply the local up-axis correction rotation + // GLTFLoader seems to never set a transformation on the root scene object so + // any transformations applied to it can be assumed to be applied after load + // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up + // rotation fix which is why "multiply" happens here. + if ( fileType === 'glb' || fileType === 'gltf' ) { - } + scene.matrix.multiply( upAdjustment ); - scene.matrix.premultiply( cachedTransform ); - scene.matrix.decompose( scene.position, scene.quaternion, scene.scale ); - scene.traverse( c => { + } - c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled; + scene.matrix.premultiply( cachedTransform ); + scene.matrix.decompose( scene.position, scene.quaternion, scene.scale ); + scene.traverse( c => { - } ); - updateFrustumCulled( scene, ! this.autoDisableRendererCulling ); + c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled; - if ( REVISION_165 ) { + } ); + updateFrustumCulled( scene, ! this.autoDisableRendererCulling ); - // We handle raycasting in a custom way so remove it from here - scene.traverse( c => { + if ( REVISION_165 ) { - c.raycast = this._overridenRaycast; + // We handle raycasting in a custom way so remove it from here + scene.traverse( c => { - } ); + c.raycast = this._overridenRaycast; - } + } ); - const materials = []; - const geometry = []; - const textures = []; - scene.traverse( c => { + } - if ( c.geometry ) { + const materials = []; + const geometry = []; + const textures = []; + scene.traverse( c => { - geometry.push( c.geometry ); + if ( c.geometry ) { - } + geometry.push( c.geometry ); - if ( c.material ) { + } - const material = c.material; - materials.push( c.material ); + if ( c.material ) { - for ( const key in material ) { + const material = c.material; + materials.push( c.material ); - const value = material[ key ]; - if ( value && value.isTexture ) { + for ( const key in material ) { - textures.push( value ); + const value = material[ key ]; + if ( value && value.isTexture ) { - } + textures.push( value ); } } - } ); + } - cached.materials = materials; - cached.geometry = geometry; - cached.textures = textures; - cached.scene = scene; - cached.metadata = metadata; + } ); - this.dispatchEvent( { - type: 'load-model', - scene, - tile, - } ); + cached.materials = materials; + cached.geometry = geometry; + cached.textures = textures; + cached.scene = scene; + cached.metadata = metadata; - if ( this.onLoadModel ) { + this.dispatchEvent( { + type: 'load-model', + scene, + tile, + } ); - this.onLoadModel( scene, tile ); + if ( this.onLoadModel ) { - } + this.onLoadModel( scene, tile ); - } ); + } } @@ -962,4 +967,8 @@ export class TilesRenderer extends TilesRendererBase { } + /* private */ + // TODO: this should leverage plugin system in the future + async _pluginProcessTileModel( scene, tile ) {} + } From a4c47eaf1709895d85afd1df090ef42968c370aa Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 12:24:35 +0900 Subject: [PATCH 23/24] Re-add parse tile adjustments --- src/three/TilesRenderer.js | 158 +++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 75 deletions(-) diff --git a/src/three/TilesRenderer.js b/src/three/TilesRenderer.js index 86aa904c..a6fe77ce 100644 --- a/src/three/TilesRenderer.js +++ b/src/three/TilesRenderer.js @@ -541,7 +541,7 @@ export class TilesRenderer extends TilesRendererBase { } - parseTile( buffer, tile, extension ) { + async parseTile( buffer, tile, extension ) { tile._loadIndex = tile._loadIndex || 0; tile._loadIndex ++; @@ -648,130 +648,133 @@ export class TilesRenderer extends TilesRendererBase { // check if this is the beginning of a new set of tiles to load and dispatch and event const stats = this.stats; - const currentlyLoading = stats.parsing + stats.downloading; - if ( this._loadingTiles === false && currentlyLoading > 0 ) { + if ( this._loadingTiles === false && stats.parsing + stats.downloading > 0 ) { this.dispatchEvent( { type: 'tiles-load-start' } ); this._loadingTiles = true; } - return promise.then( result => { + // wait for the tile to load + const result = await promise; - let scene; - let metadata; - if ( result.isObject3D ) { + // get the scene data + let scene; + let metadata; + if ( result.isObject3D ) { - scene = result; - metadata = null; + scene = result; + metadata = null; - } else { + } else { - scene = result.scene; - metadata = result; + scene = result.scene; + metadata = result; - } + } - if ( tile._loadIndex !== loadIndex ) { + // wait for extra processing if needed + // TODO: this should be handled by a plugin + await this._pluginProcessTileModel( scene, tile ); - return; + // exit early if a new request has already started + if ( tile._loadIndex !== loadIndex ) { - } + return; - // ensure the matrix is up to date in case the scene has a transform applied - scene.updateMatrix(); + } - // apply the local up-axis correction rotation - // GLTFLoader seems to never set a transformation on the root scene object so - // any transformations applied to it can be assumed to be applied after load - // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up - // rotation fix which is why "multiply" happens here. - if ( fileType === 'glb' || fileType === 'gltf' ) { + // ensure the matrix is up to date in case the scene has a transform applied + scene.updateMatrix(); - scene.matrix.multiply( upAdjustment ); + // apply the local up-axis correction rotation + // GLTFLoader seems to never set a transformation on the root scene object so + // any transformations applied to it can be assumed to be applied after load + // (such as applying RTC_CENTER) meaning they should happen _after_ the z-up + // rotation fix which is why "multiply" happens here. + if ( fileType === 'glb' || fileType === 'gltf' ) { - } + scene.matrix.multiply( upAdjustment ); - scene.matrix.premultiply( cachedTransform ); - scene.matrix.decompose( scene.position, scene.quaternion, scene.scale ); - scene.traverse( c => { + } - c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled; + scene.matrix.premultiply( cachedTransform ); + scene.matrix.decompose( scene.position, scene.quaternion, scene.scale ); + scene.traverse( c => { - } ); - updateFrustumCulled( scene, ! this.autoDisableRendererCulling ); + c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled; - if ( REVISION_165 ) { + } ); + updateFrustumCulled( scene, ! this.autoDisableRendererCulling ); - // We handle raycasting in a custom way so remove it from here - scene.traverse( c => { + if ( REVISION_165 ) { - c.raycast = this._overridenRaycast; + // We handle raycasting in a custom way so remove it from here + scene.traverse( c => { - } ); + c.raycast = this._overridenRaycast; - } + } ); - const materials = []; - const geometry = []; - const textures = []; - scene.traverse( c => { + } - if ( c.geometry ) { + const materials = []; + const geometry = []; + const textures = []; + scene.traverse( c => { - geometry.push( c.geometry ); + if ( c.geometry ) { - } + geometry.push( c.geometry ); - if ( c.material ) { + } - const material = c.material; - materials.push( c.material ); + if ( c.material ) { - for ( const key in material ) { + const material = c.material; + materials.push( c.material ); - const value = material[ key ]; - if ( value && value.isTexture ) { + for ( const key in material ) { - textures.push( value ); + const value = material[ key ]; + if ( value && value.isTexture ) { - } + textures.push( value ); } } - } ); + } - cached.materials = materials; - cached.geometry = geometry; - cached.textures = textures; - cached.scene = scene; - cached.metadata = metadata; + } ); - // dispatch an event indicating that this model has completed - this.dispatchEvent( { - type: 'load-model', - scene, - tile, - } ); + cached.materials = materials; + cached.geometry = geometry; + cached.textures = textures; + cached.scene = scene; + cached.metadata = metadata; - if ( this.onLoadModel ) { + // dispatch an event indicating that this model has completed + this.dispatchEvent( { + type: 'load-model', + scene, + tile, + } ); - this.onLoadModel( scene, tile ); + if ( this.onLoadModel ) { - } + this.onLoadModel( scene, tile ); - // dispatch an "end" event if all tiles have finished loading - const currentlyLoading = stats.parsing + stats.downloading; - if ( this._loadingTiles === true && currentlyLoading === 1 ) { + } - this.dispatchEvent( { type: 'tiles-load-end' } ); - this._loadingTiles = false; + // dispatch an "end" event if all tiles have finished loading + if ( this._loadingTiles === true && stats.parsing + stats.downloading === 1 ) { - } + this.dispatchEvent( { type: 'tiles-load-end' } ); + this._loadingTiles = false; - } ); + } } @@ -987,4 +990,9 @@ export class TilesRenderer extends TilesRendererBase { } + /* private */ + // TODO: this should leverage plugin system in the future + async _pluginProcessTileModel( scene, tile ) {} + + } From 692e2db5cf559df1097929dceb726cb003bb2343 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sat, 6 Jul 2024 13:25:28 +0900 Subject: [PATCH 24/24] Finish demo --- example/landformSite.js | 32 ++++++++++++++++--- .../overlays/TextureOverlayMaterial.js | 21 ++++++++++-- .../overlays/TextureOverlayTilesRenderer.js | 6 ++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/example/landformSite.js b/example/landformSite.js index ccea90c2..df5a889e 100644 --- a/example/landformSite.js +++ b/example/landformSite.js @@ -53,7 +53,7 @@ let camera, controls, scene, renderer; const params = { errorTarget: 12, - slopeLayer: false, + slopeDisplay: 'NONE', }; @@ -130,6 +130,7 @@ function init() { const newMaterial = new TextureOverlayMaterial(); newMaterial.copy( c.material ); newMaterial.textures = Object.values( tiles.getTexturesForTile( tile ) ); + newMaterial.displayAsOverlay = params.slopeDisplay === 'OVERLAY'; c.material = newMaterial; } @@ -179,11 +180,34 @@ function init() { const gui = new GUI(); gui.add( params, 'errorTarget', 0, 100 ); - gui.add( params, 'slopeLayer' ).onChange( v => { + gui.add( params, 'slopeDisplay', [ 'NONE', 'OVERLAY', 'SOLID' ] ).onChange( v => { - if ( v ) { + if ( v !== 'NONE' ) { - tileSets.forEach( t => t.registerLayer( 'slopeLayer', layerFunction ) ); + tileSets.forEach( t => { + + if ( ! t.hasLayer( 'slopeLayer' ) ) { + + t.registerLayer( 'slopeLayer', layerFunction ); + + } + + t.forEachLoadedModel( scene => { + + scene.traverse( c => { + + if ( c.material ) { + + c.material.displayAsOverlay = v === 'OVERLAY'; + c.material.needsUpdate = true; + + } + + } ); + + } ); + + } ); } else { diff --git a/example/src/plugins/overlays/TextureOverlayMaterial.js b/example/src/plugins/overlays/TextureOverlayMaterial.js index 9e5fc826..a30dae81 100644 --- a/example/src/plugins/overlays/TextureOverlayMaterial.js +++ b/example/src/plugins/overlays/TextureOverlayMaterial.js @@ -4,6 +4,7 @@ export const TextureOverlayMaterialMixin = base => class extends base { super( ...args ); this.textures = []; + this.displayAsOverlay = false; } @@ -20,6 +21,12 @@ export const TextureOverlayMaterialMixin = base => class extends base { }, }; + shader.defines = { + DISPLAY_AS_OVERLAY: Number( this.displayAsOverlay ), + }; + + console.log('UPDATE') + // WebGL does not seem to like empty texture arrays if ( textures.length !== 0 ) { @@ -38,8 +45,16 @@ export const TextureOverlayMaterialMixin = base => class extends base { for ( int i = 0; i < ${ textures.length }; i ++ ) { col = texture( textures[ i ], vMapUv ); - col = vec4( 0, 0, 1, col.r ); - diffuseColor = mix( diffuseColor, vec4( 0.35, 0.65, 1, 1 ), col.a ); + + #if DISPLAY_AS_OVERLAY + + diffuseColor = mix( diffuseColor, vec4( 0.22, 0.73, 0.82, 1 ), col.r * 0.5 ); + + #else + + diffuseColor = mix( diffuseColor, col, col.a ); + + #endif } #pragma unroll_loop_end @@ -52,7 +67,7 @@ export const TextureOverlayMaterialMixin = base => class extends base { customProgramCacheKey() { - return this.textures.length + this.onBeforeCompile.toString(); + return String( this.displayAsOverlay ) + String( this.textures.length ) + this.onBeforeCompile.toString(); } diff --git a/example/src/plugins/overlays/TextureOverlayTilesRenderer.js b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js index a5f7d18f..3bbb2bec 100644 --- a/example/src/plugins/overlays/TextureOverlayTilesRenderer.js +++ b/example/src/plugins/overlays/TextureOverlayTilesRenderer.js @@ -258,6 +258,12 @@ export const TextureOverlayTilesRendererMixin = base => class extends base { } + hasLayer( name ) { + + return name in this.caches; + + } + registerLayer( name, customTextureCallback ) { if ( name in this.caches ) {