From b7de64eee32f3ab6678b8f1765d86439ef6448c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 21 Nov 2024 17:47:34 +0100 Subject: [PATCH] feat: add VoxelmapViewer.setAdaptativeQuality() API --- .../patch/patch-factory/patch-factory-base.ts | 1 + .../voxelmap/viewer/simple/voxelmap-viewer.ts | 49 +++++++++++++++++-- .../voxelsRenderable/voxels-material.ts | 9 +++- .../voxelsRenderable/voxels-renderable.ts | 38 ++++++++++++-- .../merged/voxels-renderable-factory.ts | 29 +++++++---- .../voxels-renderable-factory-base.ts | 12 ++--- src/test/test-terrain.ts | 6 +++ 7 files changed, 118 insertions(+), 26 deletions(-) diff --git a/src/lib/terrain/voxelmap/patch/patch-factory/patch-factory-base.ts b/src/lib/terrain/voxelmap/patch/patch-factory/patch-factory-base.ts index f30edb46..e2aa8340 100644 --- a/src/lib/terrain/voxelmap/patch/patch-factory/patch-factory-base.ts +++ b/src/lib/terrain/voxelmap/patch/patch-factory/patch-factory-base.ts @@ -113,6 +113,7 @@ abstract class PatchFactoryBase { voxelsRenderable.container.name = `Voxels patch ${patchId.asString}`; voxelsRenderable.container.position.set(patchStart.x, patchStart.y, patchStart.z); voxelsRenderable.container.updateWorldMatrix(false, true); + voxelsRenderable.boundingBox.translate(new THREE.Vector3().copy(patchStart)); } return voxelsRenderable; } diff --git a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts index 9f98efcc..56beaf01 100644 --- a/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts +++ b/src/lib/terrain/voxelmap/viewer/simple/voxelmap-viewer.ts @@ -8,6 +8,7 @@ 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'; @@ -65,10 +66,17 @@ type EnqueuedPatchRenderable = { 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; @@ -221,9 +229,13 @@ class VoxelmapViewer extends VoxelmapViewerBase { invalidated: enqueuedPatch.invalidated, }; - if (voxelsRenderable && storedPatch.isVisible) { - this.container.add(voxelsRenderable.container); - this.notifyChange(); + if (voxelsRenderable) { + this.updateVoxelsRenderableQuality(voxelsRenderable); + + if (storedPatch.isVisible) { + this.container.add(voxelsRenderable.container); + this.notifyChange(); + } } resolve('success'); @@ -321,6 +333,37 @@ class VoxelmapViewer extends VoxelmapViewerBase { return new THREE.Box3(voxelFrom, voxelTo); } + 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); + } + } + } + + 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)) { diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts index 40d9ea22..dfdad37b 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-material.ts @@ -25,9 +25,14 @@ type VoxelsMaterial = THREE.Material & { }; }; +enum EVoxelMaterialQuality { + LOW = 0, + HIGH = 1, +} + type VoxelsMaterials = { - readonly material: VoxelsMaterial; + readonly materials: Record; readonly shadowMaterial: THREE.Material; }; -export { EVoxelsDisplayMode, type VoxelsMaterial, type VoxelsMaterialUniforms, type VoxelsMaterials }; +export { EVoxelMaterialQuality, EVoxelsDisplayMode, type VoxelsMaterial, type VoxelsMaterialUniforms, type VoxelsMaterials }; diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts index ff63ec65..8ba5a97d 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxels-renderable.ts @@ -1,6 +1,6 @@ import * as THREE from '../../../libs/three-usage'; -import { EVoxelsDisplayMode, type VoxelsMaterials } from './voxels-material'; +import { EVoxelMaterialQuality, EVoxelsDisplayMode, type VoxelsMaterials } from './voxels-material'; type PatchMesh = { readonly mesh: THREE.Mesh; @@ -46,6 +46,21 @@ class VoxelsRenderable { public readonly trianglesCount: number; public readonly gpuMemoryBytes: number; + public readonly boundingBox: THREE.Box3; + + private currentQuality: EVoxelMaterialQuality = EVoxelMaterialQuality.LOW; + public get quality(): EVoxelMaterialQuality { + return this.currentQuality; + } + + public set quality(value: EVoxelMaterialQuality) { + if (this.currentQuality !== value) { + this.currentQuality = value; + this.enforceMaterials(); + this.updateUniforms(); + } + } + public constructor(patchMeshes: PatchMesh[]) { this.gpuResources = { patchMeshes }; @@ -68,13 +83,19 @@ class VoxelsRenderable { this.gpuMemoryBytes = gpuMemoryBytes; } + this.boundingBox = new THREE.Box3(); + for (const patchMesh of patchMeshes) { + this.boundingBox.union(patchMesh.mesh.geometry.boundingBox!); + } + + this.enforceMaterials(); this.updateUniforms(); } public updateUniforms(): void { if (this.gpuResources) { for (const patchMesh of this.gpuResources.patchMeshes) { - const material = patchMesh.materials.material; + const material = patchMesh.materials.materials[this.currentQuality]; const uniforms = material.userData.uniforms; uniforms.uAoStrength.value = +this.parameters.ao.enabled * this.parameters.ao.strength; @@ -101,13 +122,24 @@ class VoxelsRenderable { for (const patchMesh of this.gpuResources.patchMeshes) { patchMesh.mesh.removeFromParent(); patchMesh.mesh.geometry.dispose(); - patchMesh.materials.material.dispose(); + + for (const material of Object.values(patchMesh.materials.materials)) { + material.dispose(); + } patchMesh.materials.shadowMaterial.dispose(); } this.gpuResources = null; } } + + private enforceMaterials(): void { + if (this.gpuResources) { + for (const patchMesh of this.gpuResources.patchMeshes) { + patchMesh.mesh.material = patchMesh.materials.materials[this.currentQuality]; + } + } + } } export { EVoxelsDisplayMode, VoxelsRenderable }; 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 dc64090c..8eba5862 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 @@ -329,14 +329,23 @@ void main() { } private buildVoxelsMaterials(): VoxelsMaterials { - const material = this.buildThreeJsVoxelsMaterial({ - enableAo: true, - enableNoise: true, - enableRoundedCorners: true, - enableGrid: false, - }); - const shadowMaterial = this.buildShadowMaterial(); - return { material, shadowMaterial }; + return { + materials: { + 0: this.buildThreeJsVoxelsMaterial({ + enableAo: false, + enableNoise: false, + enableRoundedCorners: false, + enableGrid: false, + }), + 1: this.buildThreeJsVoxelsMaterial({ + enableAo: true, + enableNoise: true, + enableRoundedCorners: true, + enableGrid: false, + }), + }, + shadowMaterial: this.buildShadowMaterial(), + }; } public constructor(params: Parameters) { @@ -361,7 +370,9 @@ void main() { public override dispose(): void { super.dispose(); - this.materialsTemplates.material.dispose(); + for (const material of Object.values(this.materialsTemplates.materials)) { + material.dispose(); + } this.materialsTemplates.shadowMaterial.dispose(); } 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 5262c3ef..7e7f1d1b 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 @@ -129,20 +129,14 @@ abstract class VoxelsRenderableFactoryBase { geometry.boundingBox = boundingBox.clone(); geometry.boundingSphere = boundingSphere.clone(); - const material = geometryAndMaterial.materials.material; - const shadowMaterial = geometryAndMaterial.materials.shadowMaterial; - const mesh = new THREE.Mesh(geometryAndMaterial.geometry, material); + const mesh = new THREE.Mesh(geometryAndMaterial.geometry); mesh.name = geometryAndMaterial.id; - mesh.customDepthMaterial = shadowMaterial; + mesh.customDepthMaterial = geometryAndMaterial.materials.shadowMaterial; mesh.castShadow = true; mesh.receiveShadow = true; mesh.frustumCulled = true; - const materials = { - material, - shadowMaterial, - }; - return { mesh, materials, trianglesCount, gpuMemoryBytes }; + return { mesh, materials: geometryAndMaterial.materials, trianglesCount, gpuMemoryBytes }; }) ); return voxelsRenderable; diff --git a/src/test/test-terrain.ts b/src/test/test-terrain.ts index 54b2aa2a..f3444a65 100644 --- a/src/test/test-terrain.ts +++ b/src/test/test-terrain.ts @@ -141,6 +141,12 @@ class TestTerrain extends TestTerrainBase { voxelsChunkOrdering: 'zyx', }); this.voxelmapViewer.parameters.faces.checkerboardContrast = 0.01; + setInterval(() => { + this.voxelmapViewer.setAdaptativeQuality({ + distanceThreshold: 100, + cameraPosition: this.camera.getWorldPosition(new THREE.Vector3()), + }); + }, 150); const heightmapViewer = new HeightmapViewer(map, { basePatchSize: chunkSize.xz,