From ddcf7a8e50c3acd90e6a20f08cf323732be69d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 26 Jan 2025 21:16:02 +0100 Subject: [PATCH 01/17] refactor: use THREE.IUniform --- .../voxelsRenderable/voxels-material.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts index 3cd668de..ec77f484 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts @@ -7,17 +7,17 @@ enum EVoxelsDisplayMode { } type VoxelsMaterialUniforms = { - readonly uDisplayMode: { value: EVoxelsDisplayMode }; - readonly uTexture: { value: THREE.Texture }; - readonly uNoiseTexture: { value: THREE.Texture }; - readonly uNoiseStrength: { value: number }; - readonly uCheckerboardStrength: { value: number }; - readonly uAoStrength: { value: number }; - readonly uAoSpread: { value: number }; - readonly uSmoothEdgeRadius: { value: number }; - readonly uGridThickness: { value: number }; - readonly uGridColor: { value: THREE.Vector3 }; - readonly uShininessStrength: { value: number }; + readonly uDisplayMode: THREE.IUniform; + readonly uTexture: THREE.IUniform; + readonly uNoiseTexture: THREE.IUniform; + readonly uNoiseStrength: THREE.IUniform; + readonly uCheckerboardStrength: THREE.IUniform; + readonly uAoStrength: THREE.IUniform; + readonly uAoSpread: THREE.IUniform; + readonly uSmoothEdgeRadius: THREE.IUniform; + readonly uGridThickness: THREE.IUniform; + readonly uGridColor: THREE.IUniform; + readonly uShininessStrength: THREE.IUniform; }; type VoxelsMaterial = THREE.Material & { From 142c5b1fcdd1466735cdbea6545b598ee854cfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 26 Jan 2025 21:58:58 +0100 Subject: [PATCH 02/17] feat: add shader uniform to dissolve chunks --- .../voxelmap/voxelsRenderable/voxels-material.ts | 1 + .../voxelmap/voxelsRenderable/voxels-renderable.ts | 4 ++++ .../merged/voxels-renderable-factory.ts | 12 +++++++++++- .../voxels-renderable-factory-base.ts | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts index ec77f484..47998f2e 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts @@ -9,6 +9,7 @@ enum EVoxelsDisplayMode { type VoxelsMaterialUniforms = { readonly uDisplayMode: THREE.IUniform; readonly uTexture: THREE.IUniform; + readonly uDissolveRatio: THREE.IUniform; readonly uNoiseTexture: THREE.IUniform; readonly uNoiseStrength: THREE.IUniform; readonly uCheckerboardStrength: THREE.IUniform; diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts index f55011b4..a427b532 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts @@ -13,6 +13,8 @@ class VoxelsRenderable { public readonly container: THREE.Object3D; public readonly parameters = { + dissolveRatio: 0, + shadows: { cast: true, receive: true, @@ -106,6 +108,8 @@ class VoxelsRenderable { uniforms.uSmoothEdgeRadius.value = +this.parameters.smoothEdges.enabled * this.parameters.smoothEdges.radius; uniforms.uDisplayMode.value = this.parameters.voxels.displayMode; + uniforms.uDissolveRatio.value = this.parameters.dissolveRatio; + uniforms.uNoiseStrength.value = this.parameters.voxels.noiseStrength; uniforms.uCheckerboardStrength.value = this.parameters.voxels.checkerboardStrength; diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts index 026e8c6f..38b944a4 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts @@ -148,8 +148,10 @@ void main() {`, 'void main() {': ` uniform sampler2D uTexture; -#ifdef ${cstVoxelNoise} +uniform float uDissolveRatio; uniform sampler2D uNoiseTexture; + +#ifdef ${cstVoxelNoise} uniform float uNoiseStrength; uniform float uCheckerboardStrength; #endif // ${cstVoxelNoise} @@ -268,6 +270,14 @@ VoxelMaterial getVoxelMaterial(const vec3 modelNormal) { } void main() { + if (uDissolveRatio > 0.0 && uDissolveRatio < 1.0) { + vec2 dissolveUv = mod(gl_FragCoord.xy, vec2(${this.noiseTextureSize})) / vec2(${this.noiseTextureSize}); + float dissolveSample = texture(uNoiseTexture, dissolveUv, 0.0).r; + if (dissolveSample <= uDissolveRatio) { + discard; + } + } + vec3 modelNormal = computeModelNormal(); VoxelMaterial voxelMaterial = getVoxelMaterial(modelNormal); `, diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts index 916f27ad..adff0332 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts @@ -77,6 +77,7 @@ abstract class VoxelsRenderableFactoryBase { this.uniformsTemplate = { uDisplayMode: { value: 0 }, uTexture: { value: this.texture }, + uDissolveRatio: { value: 0 }, uNoiseTexture: { value: this.noiseTexture }, uNoiseStrength: { value: 0 }, uCheckerboardStrength: { value: 0 }, From 62886cc26b885487245174a0505493d840fec591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 26 Jan 2025 22:57:13 +0100 Subject: [PATCH 03/17] refactor: readonlify --- src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 56beaf01..35f73e9b 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -80,8 +80,8 @@ class VoxelmapViewer extends VoxelmapViewerBase { private readonly promiseThrottler: PromisesQueue; private readonly patchFactory: PatchFactoryBase; - private patchesStore: Record = {}; - private enqueuedPatchesStore: Record = {}; + private readonly patchesStore: Record = {}; + private readonly enqueuedPatchesStore: Record = {}; public constructor( minChunkIdY: number, From 877988af804df51c274902fc46ec274e9b61355f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 10:29:10 +0100 Subject: [PATCH 04/17] test: add gui to dynamically change view distance --- src/test/test-terrain-base.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/test-terrain-base.ts b/src/test/test-terrain-base.ts index dce6bc4d..6568113f 100644 --- a/src/test/test-terrain-base.ts +++ b/src/test/test-terrain-base.ts @@ -39,7 +39,9 @@ abstract class TestTerrainBase extends TestBase { ); }, 0); } else { - const playerViewRadius = 10; + const viewParams = { + playerViewRadius: 10, + }; const playerContainer = new THREE.Group(); playerContainer.position.x = 0; @@ -85,10 +87,12 @@ abstract class TestTerrainBase extends TestBase { setInterval(() => { this.showMapAroundPosition( playerContainer.position, - playerViewRadius, + viewParams.playerViewRadius, this.playerVisibility?.visibilityFrustum ?? undefined ); }, 200); + + this.gui.add(viewParams, 'playerViewRadius', 1, 1000); } setInterval(() => { From 1e812ab5bf5eb777433214afcb69b92919da0a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 11:07:23 +0100 Subject: [PATCH 05/17] test: add control to hide voxels --- src/test/test-terrain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/test-terrain.ts b/src/test/test-terrain.ts index 168d5c6f..6f1081f6 100644 --- a/src/test/test-terrain.ts +++ b/src/test/test-terrain.ts @@ -193,6 +193,7 @@ return vec4(sampled.rgb / sampled.a, 1); ao: { ...this.voxelmapViewer.parameters.ao }, specular: { ...this.voxelmapViewer.parameters.specular }, }; + voxelsFolder.add(this.voxelmapViewer.container, "visible").name("Show voxels"); voxelsFolder .add(parameters, 'shadows') .name('Enable shadows') From 603a74116267fa84303d071697564ee9af78e2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 11:10:38 +0100 Subject: [PATCH 06/17] refactor: rewrite VoxelmapViewer For more flexibility --- src/lib/index.ts | 2 +- .../voxelmap/viewer/simple/stored-patch.ts | 175 ++++++++++ .../voxelmap/viewer/simple/voxelmap-viewer.ts | 319 ++++-------------- 3 files changed, 249 insertions(+), 247 deletions(-) create mode 100644 src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts diff --git a/src/lib/index.ts b/src/lib/index.ts index 3132fa45..4b1f811f 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -21,9 +21,9 @@ export { } from './terrain/voxelmap/viewer/autonomous/voxelmap-viewer-autonomous'; export { EComputationMethod, + EComputationResult, VoxelmapViewer, type ComputationOptions, - type ComputationStatus, type VoxelmapViewerOptions, type VoxelsChunkData, } from './terrain/voxelmap/viewer/simple/voxelmap-viewer'; diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts new file mode 100644 index 00000000..0b44fd9a --- /dev/null +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -0,0 +1,175 @@ +import type * as THREE from '../../../../libs/three-usage'; +import { type PatchId } from '../../patch/patch-id'; +import { EVoxelMaterialQuality } from '../../voxelsRenderable/voxels-material'; +import { type VoxelsRenderable } from '../../voxelsRenderable/voxels-renderable'; + +enum EComputationResult { + SKIPPED = 'skipped', + CANCELLED = 'cancelled', + FINISHED = 'finished', +} + +type UnknownFunc = () => unknown; +type AsyncTask = () => Promise; +type TaskRunner = { + run(task: UnknownFunc, onCancel: UnknownFunc): Promise; +}; + +type AdaptativeQualityParameters = { + readonly distanceThreshold: number; + readonly cameraPosition: THREE.Vector3; +}; + +class StoredPatch { + public readonly id: PatchId; + private readonly parent: THREE.Object3D; + + private hasLatestData: boolean = false; // TODO à travailler + private latestComputationId: Symbol | null = null; + private voxelsRenderable: VoxelsRenderable | null = null; + + private disposed: boolean = false; + private shouldBeVisible: boolean = false; + private invisibleSince: number | null = performance.now(); + + private latestAdaptativeQualityParameters: AdaptativeQualityParameters | null = null; + + public constructor(parent: THREE.Object3D, id: PatchId) { + this.parent = parent; + this.id = id; + } + + public needsNewData(): boolean { + return !this.hasLatestData; + } + + public flagAsObsolete(): void { + this.hasLatestData = false; + } + + public setVisible(visible: boolean): void { + if (this.shouldBeVisible !== visible) { + this.shouldBeVisible = visible; + + if (this.shouldBeVisible) { + this.invisibleSince = null; + } else { + this.invisibleSince = performance.now(); + } + + if (this.voxelsRenderable) { + if (this.shouldBeVisible) { + this.parent.add(this.voxelsRenderable.container); + } else { + this.voxelsRenderable.container.removeFromParent(); + } + } + } + } + + public getInvisibleSinceTimestamp(): number | null { + return this.invisibleSince; + } + + public isMeshInScene(): boolean { + return !!this.voxelsRenderable?.container.parent; + } + + public tryGetVoxelsRenderable(): VoxelsRenderable | null { + return this.voxelsRenderable; + } + + public updateDisplayQuality(params: AdaptativeQualityParameters | null): void { + this.latestAdaptativeQualityParameters = params; + + if (this.voxelsRenderable) { + StoredPatch.enforceDisplayQuality(this.voxelsRenderable, this.latestAdaptativeQualityParameters); + } + } + + public scheduleNewComputation( + computationTask: AsyncTask, + taskRunner: TaskRunner + ): Promise { + if (this.disposed) { + throw new Error(`Cannot compute disposed patch "${this.id.asString}".`); + } + + const computationId = Symbol('stored-patch-computation'); + this.latestComputationId = computationId; + this.hasLatestData = true; + + return new Promise(resolve => { + const resolveAsCancelled = () => resolve(EComputationResult.CANCELLED); + const resolveAsFinished = () => resolve(EComputationResult.FINISHED); + + taskRunner.run(async () => { + if (computationId !== this.latestComputationId) { + // a more recent computation has been requested before this one started + resolveAsCancelled(); + return; + } + + const computationResult = await computationTask(); + + if (computationId !== this.latestComputationId) { + // a more recent computation has been requested while this one was running + if (computationResult) { + computationResult.dispose(); + } + resolveAsCancelled(); + return; + } + + this.voxelsRenderable = computationResult; + if (this.voxelsRenderable) { + StoredPatch.enforceDisplayQuality(this.voxelsRenderable, this.latestAdaptativeQualityParameters); + if (this.shouldBeVisible) { + this.parent.add(this.voxelsRenderable.container); + } + } + + resolveAsFinished(); + }, resolveAsCancelled); + }); + } + + public cancelScheduledComputation(): void { + this.latestComputationId = null; + this.hasLatestData = false; + } + + public deleteComputationResults(): void { + if (this.voxelsRenderable) { + this.voxelsRenderable.container.removeFromParent(); + this.voxelsRenderable.dispose(); + this.voxelsRenderable = null; + this.hasLatestData = false; + } + } + + public dispose(): void { + if (this.disposed) { + throw new Error(`Patch "${this.id.asString}" was disposed twice.`); + } + this.disposed = true; + + this.cancelScheduledComputation(); + this.deleteComputationResults(); + } + + private static enforceDisplayQuality(voxelsRenderable: VoxelsRenderable, params: AdaptativeQualityParameters | null): void { + let quality = EVoxelMaterialQuality.HIGH; + + if (params) { + const distance = voxelsRenderable.boundingBox.distanceToPoint(params.cameraPosition); + if (distance > params.distanceThreshold) { + quality = EVoxelMaterialQuality.LOW; + } + } + + voxelsRenderable.quality = quality; + } +} + +export { EComputationResult, StoredPatch, type AdaptativeQualityParameters }; diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 35f73e9b..f4bdae55 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -1,5 +1,5 @@ -import { AsyncTask } from '../../../../helpers/async/async-task'; import { PromisesQueue } from '../../../../helpers/async/promises-queue'; +import { logger } from '../../../../helpers/logger'; import { vec3ToString } from '../../../../helpers/string'; import * as THREE from '../../../../libs/three-usage'; import { type IVoxelMaterial, type VoxelsChunkOrdering, type VoxelsChunkSize } from '../../i-voxelmap'; @@ -8,11 +8,11 @@ import { PatchFactoryCpuWorker } from '../../patch/patch-factory/merged/patch-fa import { PatchFactoryGpuSequential } from '../../patch/patch-factory/merged/patch-factory-gpu-sequential'; import { type PatchFactoryBase } from '../../patch/patch-factory/patch-factory-base'; import { PatchId } from '../../patch/patch-id'; -import { EVoxelMaterialQuality } from '../../voxelsRenderable/voxels-material'; -import { type VoxelsRenderable } from '../../voxelsRenderable/voxels-renderable'; import { type CheckerboardType, type VoxelsChunkData } from '../../voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base'; import { VoxelmapViewerBase, type ComputedPatch, type PatchRenderable } from '../voxelmap-viewer-base'; +import { EComputationResult, StoredPatch, type AdaptativeQualityParameters } from './stored-patch'; + enum EComputationMethod { CPU_MONOTHREADED, CPU_MULTITHREADED, @@ -40,48 +40,14 @@ type VoxelmapViewerOptions = { readonly voxelsChunkOrdering?: VoxelsChunkOrdering; }; -type ComputationStatus = 'success' | 'skipped' | 'aborted'; - -type StoredPatchRenderable = - | { - readonly id: PatchId; - readonly status: 'pending'; - isVisible: boolean; - isInvisibleSince: number; - } - | { - readonly id: PatchId; - readonly status: 'ready'; - isVisible: boolean; - isInvisibleSince: number; - readonly renderable: VoxelsRenderable | null; - invalidated: boolean; - }; - -type EnqueuedPatchRenderable = { - readonly id: PatchId; - readonly status: 'in-queue'; - readonly computationTask: AsyncTask; - cancelled: boolean; - invalidated: boolean; -}; - -type AdaptativeQualityParameters = { - readonly distanceThreshold: number; - readonly cameraPosition: THREE.Vector3Like; -}; - class VoxelmapViewer extends VoxelmapViewerBase { public readonly computationOptions: ComputationOptions; public readonly maxPatchesComputedInParallel: number; - private adaptativeQuality: AdaptativeQualityParameters | null = null; - private readonly promiseThrottler: PromisesQueue; private readonly patchFactory: PatchFactoryBase; - private readonly patchesStore: Record = {}; - private readonly enqueuedPatchesStore: Record = {}; + private readonly storedPatches = new Map(); public constructor( minChunkIdY: number, @@ -138,109 +104,37 @@ class VoxelmapViewer extends VoxelmapViewerBase { public doesPatchRequireVoxelsData(id: THREE.Vector3Like): boolean { const patchId = new PatchId(id); - const storedPatch = this.getOrBuildStoredPatch(patchId); - return storedPatch.status === 'pending' || (storedPatch.status === 'ready' && storedPatch.invalidated); + const storedPatch = this.storedPatches.get(patchId.asString); + return !storedPatch || storedPatch.needsNewData(); } - public async enqueuePatch(id: THREE.Vector3Like, voxelsChunkData: VoxelsChunkData): Promise { + public async enqueuePatch(id: THREE.Vector3Like, voxelsChunkData: VoxelsChunkData): Promise { const voxelsChunkInnerSize = voxelsChunkData.size.clone().subScalar(2); if (!voxelsChunkInnerSize.equals(this.patchSize)) { throw new Error(`Invalid voxels chunk size ${vec3ToString(voxelsChunkData.size)}.`); } const patchId = new PatchId(id); - const storedPatch = this.getOrBuildStoredPatch(patchId); - if (storedPatch.status === 'ready' && !storedPatch.invalidated) { - return Promise.resolve('skipped'); + let storedPatch = this.storedPatches.get(patchId.asString); + if (!storedPatch) { + storedPatch = new StoredPatch(this.container, patchId); + this.storedPatches.set(patchId.asString, storedPatch); + } + if (!storedPatch.needsNewData()) { + logger.debug(`Skipping unnecessary computation of up-do-date patch "${patchId.asString}".`); + return Promise.resolve(EComputationResult.SKIPPED); } - const existingEnqueuedPatch = this.enqueuedPatchesStore[patchId.asString]; - if (existingEnqueuedPatch) { - if (existingEnqueuedPatch.invalidated) { - existingEnqueuedPatch.cancelled = true; - } else { - return Promise.resolve('skipped'); + const computationTask = async () => { + if (voxelsChunkData.isEmpty) { + return null; } - } + const patchStart = new THREE.Vector3().multiplyVectors(patchId, this.patchSize); + const patchEnd = new THREE.Vector3().addVectors(patchStart, this.patchSize); + return await this.patchFactory.buildPatchFromVoxelsChunk(patchId, patchStart, patchEnd, voxelsChunkData); + }; - return new Promise(resolve => { - const resolveAsAborted = () => resolve('aborted'); - - const enqueuedPatch: EnqueuedPatchRenderable = { - id: patchId, - status: 'in-queue', - computationTask: new AsyncTask(async () => { - if (voxelsChunkData.isEmpty) { - return null; - } - const patchStart = new THREE.Vector3().multiplyVectors(patchId, this.patchSize); - const patchEnd = new THREE.Vector3().addVectors(patchStart, this.patchSize); - return await this.patchFactory.buildPatchFromVoxelsChunk(patchId, patchStart, patchEnd, voxelsChunkData); - }), - cancelled: false, - invalidated: false, - }; - this.enqueuedPatchesStore[patchId.asString] = enqueuedPatch; - - this.promiseThrottler.run(async () => { - if (enqueuedPatch.cancelled) { - resolveAsAborted(); - return; - } - - const voxelsRenderable = await enqueuedPatch.computationTask.start(); - - if (enqueuedPatch.cancelled) { - if (voxelsRenderable) { - voxelsRenderable.dispose(); - } - resolveAsAborted(); - return; - } - - if (this.enqueuedPatchesStore[patchId.asString] !== enqueuedPatch) { - throw new Error(); - } - delete this.enqueuedPatchesStore[patchId.asString]; - - const storedPatch = this.patchesStore[patchId.asString]; - if (!storedPatch) { - throw new Error(); - } - if (storedPatch.status === 'ready') { - if (storedPatch.invalidated) { - if (storedPatch.renderable) { - if (storedPatch.isVisible) { - storedPatch.renderable.container.removeFromParent(); - } - storedPatch.renderable.dispose(); - } - } else { - throw new Error(); - } - } - - this.patchesStore[patchId.asString] = { - id: storedPatch.id, - isVisible: storedPatch.isVisible, - isInvisibleSince: storedPatch.isInvisibleSince, - status: 'ready', - renderable: voxelsRenderable, - invalidated: enqueuedPatch.invalidated, - }; - - if (voxelsRenderable) { - this.updateVoxelsRenderableQuality(voxelsRenderable); - - if (storedPatch.isVisible) { - this.container.add(voxelsRenderable.container); - this.notifyChange(); - } - } - - resolve('success'); - }, resolveAsAborted); - }); + return storedPatch.scheduleNewComputation(computationTask, this.promiseThrottler); } public purgeQueue(): void { @@ -249,74 +143,35 @@ class VoxelmapViewer extends VoxelmapViewerBase { public dequeuePatch(id: THREE.Vector3Like): void { const patchId = new PatchId(id); - const enqueuedPatch = this.enqueuedPatchesStore[patchId.asString]; - if (enqueuedPatch) { - enqueuedPatch.cancelled = true; - delete this.enqueuedPatchesStore[patchId.asString]; - } + const storedPatch = this.storedPatches.get(patchId.asString); + storedPatch?.cancelScheduledComputation(); } public invalidatePatch(id: THREE.Vector3Like): void { const patchId = new PatchId(id); - - const patch = this.patchesStore[patchId.asString]; - if (patch && patch.status === 'ready') { - patch.invalidated = true; - } - - const enqueuedPatch = this.enqueuedPatchesStore[patchId.asString]; - if (enqueuedPatch) { - enqueuedPatch.invalidated = true; - } + const storedPatch = this.storedPatches.get(patchId.asString); + storedPatch?.flagAsObsolete(); } public deletePatch(id: THREE.Vector3Like): void { const patchId = new PatchId(id); - - const patch = this.patchesStore[patchId.asString]; - if (patch && patch.status === 'ready') { - if (patch.renderable) { - if (patch.isVisible) { - patch.renderable.container.removeFromParent(); - } - patch.renderable.dispose(); - } - this.patchesStore[patchId.asString] = { - id: patch.id, - isVisible: patch.isVisible, - isInvisibleSince: patch.isInvisibleSince, - status: 'pending', - }; - } - - const enqueuedPatch = this.enqueuedPatchesStore[patchId.asString]; - if (enqueuedPatch) { - enqueuedPatch.cancelled = true; - delete this.enqueuedPatchesStore[patchId.asString]; + const storedPatch = this.storedPatches.get(patchId.asString); + if (storedPatch) { + storedPatch.cancelScheduledComputation(); + storedPatch.deleteComputationResults(); } } public setVisibility(visiblePatchesId: ReadonlyArray): void { - for (const patch of Object.values(this.patchesStore)) { - if (patch.isVisible) { - if (patch.status === 'ready' && patch.renderable) { - patch.renderable.container.removeFromParent(); - } - patch.isVisible = false; - patch.isInvisibleSince = performance.now(); - } + const visiblePatchesIdsSet = new Set(); + for (const visiblePatchId of visiblePatchesId) { + const patchId = new PatchId(visiblePatchId); + visiblePatchesIdsSet.add(patchId.asString); } - for (const id of visiblePatchesId) { - const visiblePatchId = new PatchId(id); - const patch = this.getOrBuildStoredPatch(visiblePatchId); - if (!patch.isVisible) { - if (patch.status === 'ready' && patch.renderable) { - this.container.add(patch.renderable.container); - } - patch.isVisible = true; - patch.isInvisibleSince = -1; - } + for (const [patchId, storedPatch] of this.storedPatches.entries()) { + const shouldBeVisible = visiblePatchesIdsSet.has(patchId); + storedPatch.setVisible(shouldBeVisible); } this.notifyChange(); @@ -334,43 +189,19 @@ class VoxelmapViewer extends VoxelmapViewerBase { } public setAdaptativeQuality(parameters: AdaptativeQualityParameters): void { - this.adaptativeQuality = { - distanceThreshold: parameters.distanceThreshold, - cameraPosition: { - x: parameters.cameraPosition.x, - y: parameters.cameraPosition.y, - z: parameters.cameraPosition.z, - }, - }; - - for (const storedPatch of Object.values(this.patchesStore)) { - if (storedPatch.status === 'ready' && storedPatch.renderable) { - this.updateVoxelsRenderableQuality(storedPatch.renderable); - } + for (const storedPatch of this.storedPatches.values()) { + storedPatch.updateDisplayQuality(parameters); } } - private updateVoxelsRenderableQuality(voxelsRenderable: VoxelsRenderable): void { - let quality = EVoxelMaterialQuality.HIGH; - - if (this.adaptativeQuality) { - const cameraPosition = new THREE.Vector3().copy(this.adaptativeQuality.cameraPosition); - const distance = voxelsRenderable.boundingBox.distanceToPoint(cameraPosition); - if (distance > this.adaptativeQuality.distanceThreshold) { - quality = EVoxelMaterialQuality.LOW; - } - } - - voxelsRenderable.quality = quality; - } - protected override get allLoadedPatches(): ComputedPatch[] { const result: ComputedPatch[] = []; - for (const patch of Object.values(this.patchesStore)) { - if (patch.status === 'ready' && patch.renderable) + for (const storedPatch of this.storedPatches.values()) { + const voxelsRenderable = storedPatch.tryGetVoxelsRenderable(); + if (voxelsRenderable) result.push({ - isVisible: patch.isVisible, - voxelsRenderable: patch.renderable, + isVisible: storedPatch.isMeshInScene(), + voxelsRenderable, }); } return result; @@ -379,9 +210,10 @@ class VoxelmapViewer extends VoxelmapViewerBase { protected override get allVisiblePatches(): PatchRenderable[] { const result: PatchRenderable[] = []; - for (const storedPatch of Object.values(this.patchesStore)) { - if (storedPatch.status === 'ready' && storedPatch.isVisible && storedPatch.renderable) { - result.push({ id: storedPatch.id, voxelsRenderable: storedPatch.renderable }); + for (const storedPatch of this.storedPatches.values()) { + const voxelsRenderable = storedPatch.tryGetVoxelsRenderable(); + if (voxelsRenderable) { + result.push({ id: storedPatch.id, voxelsRenderable }); } } @@ -389,49 +221,44 @@ class VoxelmapViewer extends VoxelmapViewerBase { } protected override isPatchAttached(patchId: PatchId): boolean { - const storedPatch = this.patchesStore[patchId.asString]; - return storedPatch?.status === 'ready' && storedPatch.isVisible; + const storedPatch = this.storedPatches.get(patchId.asString); + if (storedPatch) { + return storedPatch.isMeshInScene(); + } + return false; } protected override garbageCollectPatches(maxInvisiblePatchesInPatch: number): void { - const storedPatchesList = Object.values(this.patchesStore); - const elligibleStoredPatchesList = storedPatchesList.filter(storedPatch => { - return !storedPatch.isVisible && (storedPatch.status === 'pending' || storedPatch.status === 'ready'); - }); - elligibleStoredPatchesList.sort((patch1, patch2) => patch1.isInvisibleSince - patch2.isInvisibleSince); - - while (elligibleStoredPatchesList.length > maxInvisiblePatchesInPatch) { - const nextPatchToDelete = elligibleStoredPatchesList.shift(); - if (!nextPatchToDelete) { - break; - } - if (nextPatchToDelete.status === 'ready') { - nextPatchToDelete.renderable?.dispose(); + type InvisiblePatch = { + readonly storedPatch: StoredPatch; + readonly invisibleSinceTimestamp: number; + }; + + const invisiblePatchesList: InvisiblePatch[] = []; + for (const storedPatch of this.storedPatches.values()) { + const invisibleSinceTimestamp = storedPatch.getInvisibleSinceTimestamp(); + if (invisibleSinceTimestamp !== null) { + invisiblePatchesList.push({ storedPatch, invisibleSinceTimestamp }); } - delete this.patchesStore[nextPatchToDelete.id.asString]; } - } + // oldest last + invisiblePatchesList.sort((ip1: InvisiblePatch, ip2: InvisiblePatch) => ip2.invisibleSinceTimestamp - ip1.invisibleSinceTimestamp); - private getOrBuildStoredPatch(patchId: PatchId): StoredPatchRenderable { - let storedPatch = this.patchesStore[patchId.asString]; - if (!storedPatch) { - storedPatch = { - id: patchId, - status: 'pending', - isVisible: false, - isInvisibleSince: performance.now(), - }; - this.patchesStore[patchId.asString] = storedPatch; + let nextPatchToDelete = invisiblePatchesList.pop(); + while (nextPatchToDelete && invisiblePatchesList.length > maxInvisiblePatchesInPatch) { + nextPatchToDelete.storedPatch.dispose(); + this.storedPatches.delete(nextPatchToDelete.storedPatch.id.asString); + + nextPatchToDelete = invisiblePatchesList.pop(); } - return storedPatch; } } export { EComputationMethod, + EComputationResult, VoxelmapViewer, type ComputationOptions, - type ComputationStatus, type VoxelmapViewerOptions, type VoxelsChunkData, }; From b3e752f52c0b4931af87e7cb0ec5b921405c5b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 12:18:35 +0100 Subject: [PATCH 07/17] fix: fix refactor bug where the lod was displayed on top of the voxels --- .../voxelmap/viewer/simple/stored-patch.ts | 78 ++++++++++++++----- .../voxelmap/viewer/simple/voxelmap-viewer.ts | 4 +- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 0b44fd9a..1489670d 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -26,7 +26,9 @@ class StoredPatch { private hasLatestData: boolean = false; // TODO à travailler private latestComputationId: Symbol | null = null; - private voxelsRenderable: VoxelsRenderable | null = null; + private computationResult: { + readonly voxelsRenderable: VoxelsRenderable | null; + } | null = null; private disposed: boolean = false; private shouldBeVisible: boolean = false; @@ -57,11 +59,12 @@ class StoredPatch { this.invisibleSince = performance.now(); } - if (this.voxelsRenderable) { + const voxelsRenderable = this.tryGetVoxelsRenderable(); + if (voxelsRenderable) { if (this.shouldBeVisible) { - this.parent.add(this.voxelsRenderable.container); + this.parent.add(voxelsRenderable.container); } else { - this.voxelsRenderable.container.removeFromParent(); + voxelsRenderable.container.removeFromParent(); } } } @@ -72,18 +75,39 @@ class StoredPatch { } public isMeshInScene(): boolean { - return !!this.voxelsRenderable?.container.parent; + return !!this.tryGetVoxelsRenderable()?.container.parent; + } + + public isPatchInScene(): boolean { + if (!this.computationResult) { + return false; + } + const voxelsRenderable = this.computationResult.voxelsRenderable; + if (voxelsRenderable) { + return !!voxelsRenderable.container.parent; + } else { + // the patch was computed, but there is no mesh -> it is as if it was in the scene + return true; + } + } + + public isFullyDisplayed(): boolean { + return this.isMeshInScene(); } public tryGetVoxelsRenderable(): VoxelsRenderable | null { - return this.voxelsRenderable; + if (this.computationResult) { + return this.computationResult.voxelsRenderable; + } + return null; } public updateDisplayQuality(params: AdaptativeQualityParameters | null): void { this.latestAdaptativeQualityParameters = params; - if (this.voxelsRenderable) { - StoredPatch.enforceDisplayQuality(this.voxelsRenderable, this.latestAdaptativeQualityParameters); + const voxelsRenerable = this.tryGetVoxelsRenderable(); + if (voxelsRenerable) { + StoredPatch.enforceDisplayQuality(voxelsRenerable, this.latestAdaptativeQualityParameters); } } @@ -110,22 +134,34 @@ class StoredPatch { return; } - const computationResult = await computationTask(); + const computedVoxelsRenderable = await computationTask(); if (computationId !== this.latestComputationId) { // a more recent computation has been requested while this one was running - if (computationResult) { - computationResult.dispose(); + if (computedVoxelsRenderable) { + computedVoxelsRenderable.dispose(); } resolveAsCancelled(); return; } - this.voxelsRenderable = computationResult; - if (this.voxelsRenderable) { - StoredPatch.enforceDisplayQuality(this.voxelsRenderable, this.latestAdaptativeQualityParameters); + if (this.computationResult) { + // we are overwriting a previous computation result + if (this.computationResult.voxelsRenderable) { + // properly remove the obsolete computation that we are overwriting + this.computationResult.voxelsRenderable.container.removeFromParent(); + this.computationResult.voxelsRenderable.dispose(); + } + } + + this.computationResult = { + voxelsRenderable: computedVoxelsRenderable, + }; + + if (computedVoxelsRenderable) { + StoredPatch.enforceDisplayQuality(computedVoxelsRenderable, this.latestAdaptativeQualityParameters); if (this.shouldBeVisible) { - this.parent.add(this.voxelsRenderable.container); + this.parent.add(computedVoxelsRenderable.container); } } @@ -140,10 +176,14 @@ class StoredPatch { } public deleteComputationResults(): void { - if (this.voxelsRenderable) { - this.voxelsRenderable.container.removeFromParent(); - this.voxelsRenderable.dispose(); - this.voxelsRenderable = null; + if (this.computationResult) { + const voxelsRenderable = this.tryGetVoxelsRenderable(); + if (voxelsRenderable) { + voxelsRenderable.container.removeFromParent(); + voxelsRenderable.dispose(); + } + this.computationResult = null; + this.hasLatestData = false; } } diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index f4bdae55..017f9313 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -212,7 +212,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { for (const storedPatch of this.storedPatches.values()) { const voxelsRenderable = storedPatch.tryGetVoxelsRenderable(); - if (voxelsRenderable) { + if (voxelsRenderable && storedPatch.isFullyDisplayed()) { result.push({ id: storedPatch.id, voxelsRenderable }); } } @@ -223,7 +223,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { protected override isPatchAttached(patchId: PatchId): boolean { const storedPatch = this.storedPatches.get(patchId.asString); if (storedPatch) { - return storedPatch.isMeshInScene(); + return storedPatch.isPatchInScene(); } return false; } From fdeaec3c82838f39d95be3cf52561396368d2e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 12:35:45 +0100 Subject: [PATCH 08/17] refactor: renamings, simplifications --- .../voxelmap/viewer/simple/stored-patch.ts | 28 ++++++++----------- .../voxelmap/viewer/simple/voxelmap-viewer.ts | 7 +++-- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 1489670d..94a58dc6 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -31,8 +31,8 @@ class StoredPatch { } | null = null; private disposed: boolean = false; - private shouldBeVisible: boolean = false; - private invisibleSince: number | null = performance.now(); + private shouldBeAttached: boolean = false; + private detachedSince: number | null = performance.now(); private latestAdaptativeQualityParameters: AdaptativeQualityParameters | null = null; @@ -50,18 +50,18 @@ class StoredPatch { } public setVisible(visible: boolean): void { - if (this.shouldBeVisible !== visible) { - this.shouldBeVisible = visible; + if (this.shouldBeAttached !== visible) { + this.shouldBeAttached = visible; - if (this.shouldBeVisible) { - this.invisibleSince = null; + if (this.shouldBeAttached) { + this.detachedSince = null; } else { - this.invisibleSince = performance.now(); + this.detachedSince = performance.now(); } const voxelsRenderable = this.tryGetVoxelsRenderable(); if (voxelsRenderable) { - if (this.shouldBeVisible) { + if (this.shouldBeAttached) { this.parent.add(voxelsRenderable.container); } else { voxelsRenderable.container.removeFromParent(); @@ -70,15 +70,15 @@ class StoredPatch { } } - public getInvisibleSinceTimestamp(): number | null { - return this.invisibleSince; + public isDetachedSince(): number | null { + return this.detachedSince; } public isMeshInScene(): boolean { return !!this.tryGetVoxelsRenderable()?.container.parent; } - public isPatchInScene(): boolean { + public isAttached(): boolean { if (!this.computationResult) { return false; } @@ -91,10 +91,6 @@ class StoredPatch { } } - public isFullyDisplayed(): boolean { - return this.isMeshInScene(); - } - public tryGetVoxelsRenderable(): VoxelsRenderable | null { if (this.computationResult) { return this.computationResult.voxelsRenderable; @@ -160,7 +156,7 @@ class StoredPatch { if (computedVoxelsRenderable) { StoredPatch.enforceDisplayQuality(computedVoxelsRenderable, this.latestAdaptativeQualityParameters); - if (this.shouldBeVisible) { + if (this.shouldBeAttached) { this.parent.add(computedVoxelsRenderable.container); } } diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 017f9313..029d12e3 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -175,6 +175,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { } this.notifyChange(); + // add patches that are not computed } public override dispose(): void { @@ -212,7 +213,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { for (const storedPatch of this.storedPatches.values()) { const voxelsRenderable = storedPatch.tryGetVoxelsRenderable(); - if (voxelsRenderable && storedPatch.isFullyDisplayed()) { + if (voxelsRenderable && storedPatch.isMeshInScene()) { result.push({ id: storedPatch.id, voxelsRenderable }); } } @@ -223,7 +224,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { protected override isPatchAttached(patchId: PatchId): boolean { const storedPatch = this.storedPatches.get(patchId.asString); if (storedPatch) { - return storedPatch.isPatchInScene(); + return storedPatch.isAttached(); } return false; } @@ -236,7 +237,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { const invisiblePatchesList: InvisiblePatch[] = []; for (const storedPatch of this.storedPatches.values()) { - const invisibleSinceTimestamp = storedPatch.getInvisibleSinceTimestamp(); + const invisibleSinceTimestamp = storedPatch.isDetachedSince(); if (invisibleSinceTimestamp !== null) { invisiblePatchesList.push({ storedPatch, invisibleSinceTimestamp }); } From 2c6e99dfb76ad27d22060a580f71c289dadc7674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 12:42:42 +0100 Subject: [PATCH 09/17] refactor: let StoredPatch handle its visibility events --- .../voxelmap/viewer/simple/stored-patch.ts | 15 +++++++++++++++ .../voxelmap/viewer/simple/voxelmap-viewer.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 94a58dc6..94fbf886 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -22,6 +22,8 @@ type AdaptativeQualityParameters = { class StoredPatch { public readonly id: PatchId; + public readonly onVisibilityChange: VoidFunction[] = []; + private readonly parent: THREE.Object3D; private hasLatestData: boolean = false; // TODO à travailler @@ -66,6 +68,7 @@ class StoredPatch { } else { voxelsRenderable.container.removeFromParent(); } + this.notifyVisibilityChange(); } } } @@ -141,6 +144,8 @@ class StoredPatch { return; } + const wasMeshInScene = this.isMeshInScene(); + if (this.computationResult) { // we are overwriting a previous computation result if (this.computationResult.voxelsRenderable) { @@ -158,6 +163,10 @@ class StoredPatch { StoredPatch.enforceDisplayQuality(computedVoxelsRenderable, this.latestAdaptativeQualityParameters); if (this.shouldBeAttached) { this.parent.add(computedVoxelsRenderable.container); + + if (!wasMeshInScene) { + this.notifyVisibilityChange(); + } } } @@ -194,6 +203,12 @@ class StoredPatch { this.deleteComputationResults(); } + private notifyVisibilityChange(): void { + for (const callback of this.onVisibilityChange) { + callback(); + } + } + private static enforceDisplayQuality(voxelsRenderable: VoxelsRenderable, params: AdaptativeQualityParameters | null): void { let quality = EVoxelMaterialQuality.HIGH; diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 029d12e3..91eb7201 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -118,6 +118,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { let storedPatch = this.storedPatches.get(patchId.asString); if (!storedPatch) { storedPatch = new StoredPatch(this.container, patchId); + storedPatch.onVisibilityChange.push(() => this.notifyChange()); this.storedPatches.set(patchId.asString, storedPatch); } if (!storedPatch.needsNewData()) { @@ -174,7 +175,6 @@ class VoxelmapViewer extends VoxelmapViewerBase { storedPatch.setVisible(shouldBeVisible); } - this.notifyChange(); // add patches that are not computed } From 9eec0280441691f1579cdcd45ee5157fc0feffea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 12:55:29 +0100 Subject: [PATCH 10/17] fix: remamber the visibility of non-computed patches --- src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 91eb7201..c7768141 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -168,14 +168,16 @@ class VoxelmapViewer extends VoxelmapViewerBase { for (const visiblePatchId of visiblePatchesId) { const patchId = new PatchId(visiblePatchId); visiblePatchesIdsSet.add(patchId.asString); + + if (!this.storedPatches.has(patchId.asString)) { + this.storedPatches.set(patchId.asString, new StoredPatch(this.container, patchId)); + } } for (const [patchId, storedPatch] of this.storedPatches.entries()) { const shouldBeVisible = visiblePatchesIdsSet.has(patchId); storedPatch.setVisible(shouldBeVisible); } - - // add patches that are not computed } public override dispose(): void { From 8638cd0a646141e6f645b83eb9ab08fd773223c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 16:35:15 +0100 Subject: [PATCH 11/17] feat: allow VoxelRenderable to have distinct uniforms --- .../merged/voxels-renderable-factory.ts | 16 ++------- .../voxels-renderable-factory-base.ts | 35 +++++++++---------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts index 38b944a4..07067d81 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts @@ -40,8 +40,6 @@ abstract class VoxelsRenderableFactory extends VoxelsRenderableFactoryBase { public readonly maxVoxelsChunkSize: THREE.Vector3; - private readonly materialsTemplates: VoxelsMaterials; - private buildThreeJsVoxelsMaterial(parameters: VoxelsMaterialParameters): VoxelsMaterial { const cstVoxelAo = 'VOXELS_AO'; const cstVoxelNoise = 'VOXELS_NOISE'; @@ -51,7 +49,7 @@ abstract class VoxelsRenderableFactory extends VoxelsRenderableFactoryBase { const phongMaterial = new THREE.MeshPhongMaterial(); phongMaterial.shininess = 0; const material = phongMaterial as unknown as VoxelsMaterialTemp; - material.userData.uniforms = this.uniformsTemplate; + material.userData.uniforms = this.buildDefaultUniforms(); material.customProgramCacheKey = () => `voxels-factory-merged_${this.instanceId}`; material.defines = material.defines || {}; if (parameters.enableAo) { @@ -398,19 +396,9 @@ void main() { params.maxVoxelsChunkSize.xz ); - this.materialsTemplates = this.buildVoxelsMaterials(); - this.instanceId = VoxelsRenderableFactory.instancesCount++; } - public override dispose(): void { - super.dispose(); - for (const material of Object.values(this.materialsTemplates.materials)) { - material.dispose(); - } - this.materialsTemplates.shadowMaterial.dispose(); - } - protected assembleGeometryAndMaterials(buffer: Uint32Array): GeometryAndMaterial[] { const verticesCount = buffer.length / 2; if (verticesCount === 0) { @@ -428,7 +416,7 @@ void main() { const trianglesCount = verticesCount / 3; const gpuMemoryBytes = interleavedBuffer.array.byteLength; - return [{ id: 'merged', materials: this.materialsTemplates, geometry, trianglesCount, gpuMemoryBytes }]; + return [{ id: 'merged', materials: this.buildVoxelsMaterials(), geometry, trianglesCount, gpuMemoryBytes }]; } } diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts index adff0332..a0632b08 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/voxels-renderable-factory-base.ts @@ -55,8 +55,6 @@ abstract class VoxelsRenderableFactoryBase { protected readonly noiseTextureSize: number = 64; protected readonly checkerboardType: CheckerboardType = 'xyz'; - protected readonly uniformsTemplate: VoxelsMaterialUniforms; - protected constructor(params: Parameters) { if (typeof params.noiseResolution !== 'undefined') { this.noiseResolution = params.noiseResolution; @@ -73,22 +71,6 @@ abstract class VoxelsRenderableFactoryBase { this.noiseTexture.needsUpdate = true; this.texture = VoxelsRenderableFactoryBase.buildMaterialsTexture(params.voxelMaterialsList, params.voxelTypeEncoder); - - this.uniformsTemplate = { - uDisplayMode: { value: 0 }, - uTexture: { value: this.texture }, - uDissolveRatio: { value: 0 }, - uNoiseTexture: { value: this.noiseTexture }, - uNoiseStrength: { value: 0 }, - uCheckerboardStrength: { value: 0 }, - uAoStrength: { value: 0 }, - uAoSpread: { value: 0 }, - uSmoothEdgeRadius: { value: 0 }, - uGridThickness: { value: 0.02 }, - uGridColor: { value: new THREE.Vector3(-0.2, -0.2, -0.2) }, - uShininessStrength: { value: 1 }, - }; - this.uniformsTemplate.uTexture.value = this.texture; } public buildVoxelsRenderable(voxelsChunkData: VoxelsChunkData): null | Promise { @@ -147,6 +129,23 @@ abstract class VoxelsRenderableFactoryBase { public abstract buildGeometryAndMaterials(voxelsChunkData: VoxelsChunkDataNotEmpty): Promise; + protected buildDefaultUniforms(): VoxelsMaterialUniforms { + return { + uDisplayMode: { value: 0 }, + uTexture: { value: this.texture }, + uDissolveRatio: { value: 0 }, + uNoiseTexture: { value: this.noiseTexture }, + uNoiseStrength: { value: 0 }, + uCheckerboardStrength: { value: 0 }, + uAoStrength: { value: 0 }, + uAoSpread: { value: 0 }, + uSmoothEdgeRadius: { value: 0 }, + uGridThickness: { value: 0.02 }, + uGridColor: { value: new THREE.Vector3(-0.2, -0.2, -0.2) }, + uShininessStrength: { value: 1 }, + }; + } + private static buildMaterialsTexture( voxelMaterials: ReadonlyArray, voxelTypeEncoder: PackedUintFragment From 05a6b5fe39e90d220a2b74abd73175bdc89ce606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 19:54:40 +0100 Subject: [PATCH 12/17] test: improve ui --- src/test/test-terrain-base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/test-terrain-base.ts b/src/test/test-terrain-base.ts index 6568113f..f4f7b2b9 100644 --- a/src/test/test-terrain-base.ts +++ b/src/test/test-terrain-base.ts @@ -92,7 +92,7 @@ abstract class TestTerrainBase extends TestBase { ); }, 200); - this.gui.add(viewParams, 'playerViewRadius', 1, 1000); + this.gui.add(viewParams, 'playerViewRadius', 1, 1000, 1); } setInterval(() => { From 0b11e5f4a275b52ef501cd12f7dc43d15950ee69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 20:23:40 +0100 Subject: [PATCH 13/17] feat: fadein / fadeout voxel meshes --- src/lib/terrain/terrain-viewer.ts | 2 +- .../voxelmap/viewer/simple/stored-patch.ts | 65 +++++++++++++++++-- .../voxelmap/viewer/simple/voxelmap-viewer.ts | 8 +++ .../voxelmap/viewer/voxelmap-viewer-base.ts | 2 +- src/test/test-terrain.ts | 2 +- 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/lib/terrain/terrain-viewer.ts b/src/lib/terrain/terrain-viewer.ts index 413f4ab6..6053898c 100644 --- a/src/lib/terrain/terrain-viewer.ts +++ b/src/lib/terrain/terrain-viewer.ts @@ -52,7 +52,7 @@ class TerrainViewer { * Call this method before rendering. * */ public update(): void { - this.voxelmapViewer.applyParameters(); + this.voxelmapViewer.update(); this.updateHeightmap(); } diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 94fbf886..99c93a93 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -26,7 +26,7 @@ class StoredPatch { private readonly parent: THREE.Object3D; - private hasLatestData: boolean = false; // TODO à travailler + private hasLatestData: boolean = false; private latestComputationId: Symbol | null = null; private computationResult: { readonly voxelsRenderable: VoxelsRenderable | null; @@ -35,6 +35,11 @@ class StoredPatch { private disposed: boolean = false; private shouldBeAttached: boolean = false; private detachedSince: number | null = performance.now(); + private transition: { + readonly startTimestamp: number; + readonly fromDissolve: number; + readonly toDissolve: number; + } | null = null; private latestAdaptativeQualityParameters: AdaptativeQualityParameters | null = null; @@ -43,6 +48,33 @@ class StoredPatch { this.id = id; } + public update(): void { + const transitionTime = 2000; + + const voxelRenderable = this.tryGetVoxelsRenderable(); + if (voxelRenderable) { + if (this.transition) { + const now = performance.now(); + const progression = (now - this.transition.startTimestamp) / transitionTime; + if (progression <= 0) { + voxelRenderable.parameters.dissolveRatio = this.transition.fromDissolve; + } else if (progression >= 1) { + voxelRenderable.parameters.dissolveRatio = this.transition.toDissolve; + this.transition = null; + + if (this.shouldBeAttached) { + this.notifyVisibilityChange(); + } else { + voxelRenderable.container.removeFromParent(); + } + } else { + voxelRenderable.parameters.dissolveRatio = + this.transition.fromDissolve * (1 - progression) + this.transition.toDissolve * progression; + } + } + } + } + public needsNewData(): boolean { return !this.hasLatestData; } @@ -65,10 +97,18 @@ class StoredPatch { if (voxelsRenderable) { if (this.shouldBeAttached) { this.parent.add(voxelsRenderable.container); + this.transition = { + startTimestamp: performance.now(), + fromDissolve: 1, + toDissolve: 0, + }; } else { - voxelsRenderable.container.removeFromParent(); + this.transition = { + startTimestamp: performance.now(), + fromDissolve: 0, + toDissolve: 1, + }; } - this.notifyVisibilityChange(); } } } @@ -87,7 +127,11 @@ class StoredPatch { } const voxelsRenderable = this.computationResult.voxelsRenderable; if (voxelsRenderable) { - return !!voxelsRenderable.container.parent; + if (this.transition) { + return false; + } else { + return !!voxelsRenderable.container.parent; + } } else { // the patch was computed, but there is no mesh -> it is as if it was in the scene return true; @@ -143,6 +187,7 @@ class StoredPatch { resolveAsCancelled(); return; } + this.latestComputationId = null; const wasMeshInScene = this.isMeshInScene(); @@ -165,7 +210,11 @@ class StoredPatch { this.parent.add(computedVoxelsRenderable.container); if (!wasMeshInScene) { - this.notifyVisibilityChange(); + this.transition = { + startTimestamp: performance.now(), + fromDissolve: 1, + toDissolve: 0, + }; } } } @@ -176,8 +225,10 @@ class StoredPatch { } public cancelScheduledComputation(): void { - this.latestComputationId = null; - this.hasLatestData = false; + if (this.latestComputationId) { + this.latestComputationId = null; + this.hasLatestData = false; + } } public deleteComputationResults(): void { diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index c7768141..26c55a6f 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -102,6 +102,14 @@ class VoxelmapViewer extends VoxelmapViewerBase { this.promiseThrottler = new PromisesQueue(this.maxPatchesComputedInParallel); } + public override update(): void { + for (const storedPatch of this.storedPatches.values()) { + storedPatch.update(); + } + + super.update(); + } + public doesPatchRequireVoxelsData(id: THREE.Vector3Like): boolean { const patchId = new PatchId(id); const storedPatch = this.storedPatches.get(patchId.asString); diff --git a/src/lib/terrain/voxelmap/viewer/voxelmap-viewer-base.ts b/src/lib/terrain/voxelmap/viewer/voxelmap-viewer-base.ts index eef40505..d709f219 100644 --- a/src/lib/terrain/voxelmap/viewer/voxelmap-viewer-base.ts +++ b/src/lib/terrain/voxelmap/viewer/voxelmap-viewer-base.ts @@ -79,7 +79,7 @@ abstract class VoxelmapViewerBase { this.garbageCollectionHandle = window.setInterval(() => this.garbageCollectPatches(this.maxPatchesInCache), 5000); } - public applyParameters(): void { + public update(): void { const voxelsSettings = this.parameters; for (const patch of this.allVisiblePatches) { const voxelsRenderable = patch.voxelsRenderable; diff --git a/src/test/test-terrain.ts b/src/test/test-terrain.ts index 6f1081f6..790a3838 100644 --- a/src/test/test-terrain.ts +++ b/src/test/test-terrain.ts @@ -193,7 +193,7 @@ return vec4(sampled.rgb / sampled.a, 1); ao: { ...this.voxelmapViewer.parameters.ao }, specular: { ...this.voxelmapViewer.parameters.specular }, }; - voxelsFolder.add(this.voxelmapViewer.container, "visible").name("Show voxels"); + voxelsFolder.add(this.voxelmapViewer.container, 'visible').name('Show voxels'); voxelsFolder .add(parameters, 'shadows') .name('Enable shadows') From fe267d04e6308f43562f30e700c9cd4a30a75a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 20:35:15 +0100 Subject: [PATCH 14/17] refactor: simplification --- .../voxelmap/viewer/simple/stored-patch.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 99c93a93..aeb751ed 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -54,23 +54,34 @@ class StoredPatch { const voxelRenderable = this.tryGetVoxelsRenderable(); if (voxelRenderable) { if (this.transition) { - const now = performance.now(); - const progression = (now - this.transition.startTimestamp) / transitionTime; + let progression: number; + if (transitionTime > 0) { + const now = performance.now(); + progression = (now - this.transition.startTimestamp) / transitionTime; + } else { + progression = 1; + } + + const wasFullyVisible = voxelRenderable.parameters.dissolveRatio === 0; + if (progression <= 0) { voxelRenderable.parameters.dissolveRatio = this.transition.fromDissolve; } else if (progression >= 1) { voxelRenderable.parameters.dissolveRatio = this.transition.toDissolve; this.transition = null; - if (this.shouldBeAttached) { - this.notifyVisibilityChange(); - } else { + if (!this.shouldBeAttached) { voxelRenderable.container.removeFromParent(); } } else { voxelRenderable.parameters.dissolveRatio = this.transition.fromDissolve * (1 - progression) + this.transition.toDissolve * progression; } + + const isFullyVisible = voxelRenderable.parameters.dissolveRatio === 0; + if (wasFullyVisible !== isFullyVisible) { + this.notifyVisibilityChange(); + } } } } @@ -127,11 +138,7 @@ class StoredPatch { } const voxelsRenderable = this.computationResult.voxelsRenderable; if (voxelsRenderable) { - if (this.transition) { - return false; - } else { - return !!voxelsRenderable.container.parent; - } + return !!voxelsRenderable.container.parent && voxelsRenderable.parameters.dissolveRatio === 0; } else { // the patch was computed, but there is no mesh -> it is as if it was in the scene return true; From 9c1ff89778b4b94f4d9499f55bf3420182fd856c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 27 Jan 2025 21:09:25 +0100 Subject: [PATCH 15/17] feat: better handling of fade in/out transition --- src/lib/helpers/transition.ts | 35 +++++++++++ .../voxelmap/viewer/simple/stored-patch.ts | 58 +++++++------------ 2 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 src/lib/helpers/transition.ts diff --git a/src/lib/helpers/transition.ts b/src/lib/helpers/transition.ts new file mode 100644 index 00000000..63c8f281 --- /dev/null +++ b/src/lib/helpers/transition.ts @@ -0,0 +1,35 @@ +class Transition { + private readonly startTimestamp: number = performance.now(); + + private duration: number; + private from: number; + private to: number; + + public constructor(duration: number, from: number, to: number) { + this.duration = duration; + this.from = from; + this.to = to; + } + + public get currentValue(): number { + const progress = this.progress; + return this.from * (1 - progress) + this.to * progress; + } + + public isFinished(): boolean { + return this.progress === 1; + } + + public get progress(): number { + const progress = (performance.now() - this.startTimestamp) / this.duration; + if (progress < 0) { + return 0; + } else if (progress > 1) { + return 1; + } else { + return progress; + } + } +} + +export { Transition }; diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index aeb751ed..009e5c79 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -1,3 +1,4 @@ +import { Transition } from '../../../../helpers/transition'; import type * as THREE from '../../../../libs/three-usage'; import { type PatchId } from '../../patch/patch-id'; import { EVoxelMaterialQuality } from '../../voxelsRenderable/voxels-material'; @@ -20,6 +21,8 @@ type AdaptativeQualityParameters = { readonly cameraPosition: THREE.Vector3; }; +const transitionTime = 200; + class StoredPatch { public readonly id: PatchId; public readonly onVisibilityChange: VoidFunction[] = []; @@ -35,11 +38,7 @@ class StoredPatch { private disposed: boolean = false; private shouldBeAttached: boolean = false; private detachedSince: number | null = performance.now(); - private transition: { - readonly startTimestamp: number; - readonly fromDissolve: number; - readonly toDissolve: number; - } | null = null; + private transition: Transition | null = null; private latestAdaptativeQualityParameters: AdaptativeQualityParameters | null = null; @@ -49,33 +48,18 @@ class StoredPatch { } public update(): void { - const transitionTime = 2000; - const voxelRenderable = this.tryGetVoxelsRenderable(); if (voxelRenderable) { if (this.transition) { - let progression: number; - if (transitionTime > 0) { - const now = performance.now(); - progression = (now - this.transition.startTimestamp) / transitionTime; - } else { - progression = 1; - } - const wasFullyVisible = voxelRenderable.parameters.dissolveRatio === 0; + voxelRenderable.parameters.dissolveRatio = this.transition.currentValue; - if (progression <= 0) { - voxelRenderable.parameters.dissolveRatio = this.transition.fromDissolve; - } else if (progression >= 1) { - voxelRenderable.parameters.dissolveRatio = this.transition.toDissolve; + if (this.transition.isFinished()) { this.transition = null; if (!this.shouldBeAttached) { voxelRenderable.container.removeFromParent(); } - } else { - voxelRenderable.parameters.dissolveRatio = - this.transition.fromDissolve * (1 - progression) + this.transition.toDissolve * progression; } const isFullyVisible = voxelRenderable.parameters.dissolveRatio === 0; @@ -108,17 +92,9 @@ class StoredPatch { if (voxelsRenderable) { if (this.shouldBeAttached) { this.parent.add(voxelsRenderable.container); - this.transition = { - startTimestamp: performance.now(), - fromDissolve: 1, - toDissolve: 0, - }; + this.transitionToDissolved(false); } else { - this.transition = { - startTimestamp: performance.now(), - fromDissolve: 0, - toDissolve: 1, - }; + this.transitionToDissolved(true); } } } @@ -217,11 +193,7 @@ class StoredPatch { this.parent.add(computedVoxelsRenderable.container); if (!wasMeshInScene) { - this.transition = { - startTimestamp: performance.now(), - fromDissolve: 1, - toDissolve: 0, - }; + this.transitionToDissolved(false); } } } @@ -261,6 +233,18 @@ class StoredPatch { this.deleteComputationResults(); } + private transitionToDissolved(dissolved: boolean): void { + let from: number; + if (this.transition) { + from = this.transition.currentValue; + } else { + from = dissolved ? 0 : 1; + } + const to = dissolved ? 1 : 0; + + this.transition = new Transition(transitionTime * Math.abs(to - from), from, to); + } + private notifyVisibilityChange(): void { for (const callback of this.onVisibilityChange) { callback(); From 522601932def39b464e4823641ece84e724bd99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Tue, 28 Jan 2025 19:01:16 +0100 Subject: [PATCH 16/17] feat: expose API to customize transition time --- .../voxelmap/viewer/simple/stored-patch.ts | 16 +++++++++++----- .../voxelmap/viewer/simple/voxelmap-viewer.ts | 12 ++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts index 009e5c79..c91eeb57 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/stored-patch.ts @@ -21,12 +21,17 @@ type AdaptativeQualityParameters = { readonly cameraPosition: THREE.Vector3; }; -const transitionTime = 200; +type Parameters = { + readonly parent: THREE.Object3D; + readonly id: PatchId; + readonly transitionTime: number; +}; class StoredPatch { public readonly id: PatchId; public readonly onVisibilityChange: VoidFunction[] = []; + private readonly transitionTime: number; private readonly parent: THREE.Object3D; private hasLatestData: boolean = false; @@ -42,9 +47,10 @@ class StoredPatch { private latestAdaptativeQualityParameters: AdaptativeQualityParameters | null = null; - public constructor(parent: THREE.Object3D, id: PatchId) { - this.parent = parent; - this.id = id; + public constructor(params: Parameters) { + this.parent = params.parent; + this.id = params.id; + this.transitionTime = params.transitionTime; } public update(): void { @@ -242,7 +248,7 @@ class StoredPatch { } const to = dissolved ? 1 : 0; - this.transition = new Transition(transitionTime * Math.abs(to - from), from, to); + this.transition = new Transition(this.transitionTime * Math.abs(to - from), from, to); } private notifyVisibilityChange(): void { diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 26c55a6f..2b24a3ad 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -34,6 +34,7 @@ type ComputationOptions = }; type VoxelmapViewerOptions = { + readonly transitionTime?: number; readonly patchSize?: VoxelsChunkSize; readonly computationOptions?: ComputationOptions; readonly checkerboardType?: CheckerboardType; @@ -49,6 +50,8 @@ class VoxelmapViewer extends VoxelmapViewerBase { private readonly storedPatches = new Map(); + private readonly transitionTime: number; + public constructor( minChunkIdY: number, maxChunkIdY: number, @@ -100,6 +103,8 @@ class VoxelmapViewer extends VoxelmapViewerBase { } this.promiseThrottler = new PromisesQueue(this.maxPatchesComputedInParallel); + + this.transitionTime = options?.transitionTime ?? 250; } public override update(): void { @@ -125,7 +130,7 @@ class VoxelmapViewer extends VoxelmapViewerBase { const patchId = new PatchId(id); let storedPatch = this.storedPatches.get(patchId.asString); if (!storedPatch) { - storedPatch = new StoredPatch(this.container, patchId); + storedPatch = new StoredPatch({ parent: this.container, id: patchId, transitionTime: this.transitionTime }); storedPatch.onVisibilityChange.push(() => this.notifyChange()); this.storedPatches.set(patchId.asString, storedPatch); } @@ -178,7 +183,10 @@ class VoxelmapViewer extends VoxelmapViewerBase { visiblePatchesIdsSet.add(patchId.asString); if (!this.storedPatches.has(patchId.asString)) { - this.storedPatches.set(patchId.asString, new StoredPatch(this.container, patchId)); + this.storedPatches.set( + patchId.asString, + new StoredPatch({ parent: this.container, id: patchId, transitionTime: this.transitionTime }) + ); } } From a5cab9503ec299f2ed00ace5bc636af0c2802998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Tue, 28 Jan 2025 20:26:53 +0100 Subject: [PATCH 17/17] test: control the heightmap Y scale --- src/test/test-terrain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/test-terrain.ts b/src/test/test-terrain.ts index 790a3838..fd1384f3 100644 --- a/src/test/test-terrain.ts +++ b/src/test/test-terrain.ts @@ -255,6 +255,7 @@ return vec4(sampled.rgb / sampled.a, 1); { const lodFolder = this.gui.addFolder('LOD'); lodFolder.add(this.terrainViewer.parameters.lod, 'enabled'); + lodFolder.add(heightmapViewer.container.scale, 'y', 0.00001, 1).name('Y scale'); lodFolder.add(this.terrainViewer.parameters.lod, 'wireframe'); lodFolder.open(); }