From c9abf4315440ae73f8d0ff38733705e5cc7b2acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 13 Jan 2025 16:59:54 +0100 Subject: [PATCH 1/2] feat: allow empty LocalMapData --- src/lib/index.ts | 2 +- src/lib/terrain/voxelmap/board/board.ts | 3 ++ .../voxelmap/board/voxelmap-wrapper.ts | 8 +++- src/lib/terrain/voxelmap/i-voxelmap.ts | 39 +++++++++---------- .../patch/patch-factory/patch-factory-base.ts | 7 ++++ src/test/map/voxel-map.ts | 10 ++--- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/lib/index.ts b/src/lib/index.ts index 679e173e..3132fa45 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -9,9 +9,9 @@ export { BoardOverlaysHandler } from './terrain/voxelmap/board/overlay/board-ove export { VoxelmapWrapper } from './terrain/voxelmap/board/voxelmap-wrapper'; export { voxelmapDataPacking, - type ILocalMapData, type IVoxelMap, type IVoxelMaterial, + type LocalMapData, type VoxelsChunkOrdering, type VoxelsChunkSize, } from './terrain/voxelmap/i-voxelmap'; diff --git a/src/lib/terrain/voxelmap/board/board.ts b/src/lib/terrain/voxelmap/board/board.ts index 2a8e37f2..4dfdf676 100644 --- a/src/lib/terrain/voxelmap/board/board.ts +++ b/src/lib/terrain/voxelmap/board/board.ts @@ -99,6 +99,9 @@ async function computeBoard(map: IVoxelMap, originWorld: THREE.Vector3Like, radi ) { throw new Error(); } + if (data.isEmpty) { + return 0; + } const index = dataPos.x + dataPos.y * dataSize.x + dataPos.z * dataSize.x * dataSize.y; return data.data[index]!; }; diff --git a/src/lib/terrain/voxelmap/board/voxelmap-wrapper.ts b/src/lib/terrain/voxelmap/board/voxelmap-wrapper.ts index c3b06825..33988c1b 100644 --- a/src/lib/terrain/voxelmap/board/voxelmap-wrapper.ts +++ b/src/lib/terrain/voxelmap/board/voxelmap-wrapper.ts @@ -1,6 +1,6 @@ import * as THREE from '../../../libs/three-usage'; import { processAsap } from '../../../helpers/async/async-sync'; -import { voxelmapDataPacking, type ILocalMapData, type IVoxelMap, type IVoxelMaterial, type VoxelsChunkSize } from '../i-voxelmap'; +import { voxelmapDataPacking, type IVoxelMap, type IVoxelMaterial, type LocalMapData, type VoxelsChunkSize } from '../i-voxelmap'; import { PatchId } from '../patch/patch-id'; import { EBoardSquareType, type Board } from './board'; @@ -52,11 +52,15 @@ class VoxelmapWrapper implements IVoxelMap { this.includeBoard = includeBoard; } - public getLocalMapData(blockStart: THREE.Vector3Like, blockEnd: THREE.Vector3Like): ILocalMapData | Promise { + public getLocalMapData(blockStart: THREE.Vector3Like, blockEnd: THREE.Vector3Like): LocalMapData | Promise { const blockSize = new THREE.Vector3().subVectors(blockEnd, blockStart); const originalLocalMapData = this.originGetLocalMapData(blockStart, blockEnd); return processAsap(originalLocalMapData, localMapData => { + if (localMapData.isEmpty) { + return localMapData; + } + const columnWorld = { x: 0, y: 0, z: 0 }; for (columnWorld.z = blockStart.z; columnWorld.z < blockEnd.z; columnWorld.z++) { for (columnWorld.x = blockStart.x; columnWorld.x < blockEnd.x; columnWorld.x++) { diff --git a/src/lib/terrain/voxelmap/i-voxelmap.ts b/src/lib/terrain/voxelmap/i-voxelmap.ts index ef7a91ff..8208f1d3 100644 --- a/src/lib/terrain/voxelmap/i-voxelmap.ts +++ b/src/lib/terrain/voxelmap/i-voxelmap.ts @@ -24,24 +24,23 @@ type VoxelsChunkSize = { type VoxelsChunkOrdering = 'xyz' | 'xzy' | 'yxz' | 'yzx' | 'zxy' | 'zyx'; /** Compact object storing a portion of the map data */ -interface ILocalMapData { - /** Compact array storing the voxel data. - * Each element in the array represent a coordinate in the map and stores the data of the voxel at these coordinates. - * Each element should be encoded as follows: - * - bit 0: 0 if the voxel is empty, 1 otherwise - * - bit 1: 1 if the voxel should be displayed as checkerboard, 0 otherwise - * - bits 2-13: ID of the material - * Use the helper "voxelmapDataPacking" to do this encoding and be future-proof. - */ - readonly data: Uint16Array; - readonly dataOrdering: VoxelsChunkOrdering; - - /** Should be: - * - true if there are no voxels in the data - * - false if there is at least one voxel in the data - */ - readonly isEmpty: boolean; -} +type LocalMapData = + | { + /** Compact array storing the voxel data. + * Each element in the array represent a coordinate in the map and stores the data of the voxel at these coordinates. + * Each element should be encoded as follows: + * - bit 0: 0 if the voxel is empty, 1 otherwise + * - bit 1: 1 if the voxel should be displayed as checkerboard, 0 otherwise + * - bits 2-13: ID of the material + * Use the helper "voxelmapDataPacking" to do this encoding and be future-proof. + */ + readonly data: Uint16Array; + readonly dataOrdering: VoxelsChunkOrdering; + readonly isEmpty: false; + } + | { + readonly isEmpty: true; + }; /** * Interface for a class storing a 3D voxel map. @@ -62,9 +61,9 @@ interface IVoxelMap { * @param from Lower limit (inclusive) for the voxels coordinates * @param to Upper limit (exclusive) for the voxels coordinates */ - getLocalMapData(from: Vector3Like, to: Vector3Like): ILocalMapData | Promise; + getLocalMapData(from: Vector3Like, to: Vector3Like): LocalMapData | Promise; } const voxelmapDataPacking = new VoxelmapDataPacking(); -export { voxelmapDataPacking, type ILocalMapData, type IVoxelMap, type IVoxelMaterial, type VoxelsChunkOrdering, type VoxelsChunkSize }; +export { voxelmapDataPacking, type LocalMapData, type IVoxelMap, type IVoxelMaterial, type VoxelsChunkOrdering, type VoxelsChunkSize }; 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 e2aa8340..75f2ad9c 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 @@ -91,6 +91,13 @@ abstract class PatchFactoryBase { const queriedLocalMapData = map.getLocalMapData(cacheStart, cacheEnd); return processAsap(queriedLocalMapData, localMapData => { + if (localMapData.isEmpty) { + return { + size: cacheSize, + isEmpty: true, + }; + } + const expectedCacheItemsCount = cacheSize.x * cacheSize.y * cacheSize.z; if (localMapData.data.length !== expectedCacheItemsCount) { throw new Error( diff --git a/src/test/map/voxel-map.ts b/src/test/map/voxel-map.ts index d23f87ed..1a9cec21 100644 --- a/src/test/map/voxel-map.ts +++ b/src/test/map/voxel-map.ts @@ -8,8 +8,8 @@ import { type IHeightmap, type IHeightmapCoords, type IHeightmapSample, - type ILocalMapData, type IVoxelMap, + type LocalMapData, } from '../../lib/index'; import { colorMapping } from './color-mapping'; @@ -133,7 +133,7 @@ class VoxelMap implements IVoxelMap, IHeightmap { } } - public getLocalMapData(blockStart: THREE.Vector3, blockEnd: THREE.Vector3): ILocalMapData | Promise { + public getLocalMapData(blockStart: THREE.Vector3, blockEnd: THREE.Vector3): LocalMapData | Promise { const blockSize = new THREE.Vector3().subVectors(blockEnd, blockStart); const data = new Uint16Array(blockSize.x * blockSize.y * blockSize.z); @@ -224,11 +224,7 @@ class VoxelMap implements IVoxelMap, IHeightmap { } } - const result: ILocalMapData = { - data, - dataOrdering: 'zyx', - isEmpty, - }; + const result: LocalMapData = isEmpty ? { isEmpty } : { data, dataOrdering: 'zyx', isEmpty }; const synchronous = false; if (synchronous) { From 464bcd9efdbfcd95051e23e323e46ffb62a022d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 13 Jan 2025 17:49:56 +0100 Subject: [PATCH 2/2] feat: physics, allow full chunks without detailed data --- src/lib/physics/voxelmap-collider.ts | 28 ++++++++++++++++++++++++---- src/test/test-physics.ts | 6 +++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/lib/physics/voxelmap-collider.ts b/src/lib/physics/voxelmap-collider.ts index 8278454d..881ee82b 100644 --- a/src/lib/physics/voxelmap-collider.ts +++ b/src/lib/physics/voxelmap-collider.ts @@ -12,14 +12,21 @@ import { EVoxelStatus, type IVoxelmapCollider } from './i-voxelmap-collider'; type ChunkCollider = | { readonly isEmpty: true; + readonly isFull: false; } | { readonly isEmpty: false; + readonly isFull: true; + } + | { + readonly isEmpty: false; + readonly isFull: false; readonly type: 'raw'; readonly data: Uint16Array; // one uint16 per voxel } | { readonly isEmpty: false; + readonly isFull: false; readonly type: 'compacted'; readonly data: Uint8Array; // one bit per voxel }; @@ -42,6 +49,13 @@ type VoxelmapColliderStatistics = { }; }; +type VoxelsChunkDataForCollisions = + | (VoxelsChunkData & { readonly isFull: false }) + | { + readonly isEmpty: false; + readonly isFull: true; + }; + class VoxelmapCollider implements IVoxelmapCollider { private readonly chunkSize: THREE.Vector3Like; private readonly voxelsChunkOrdering: VoxelsChunkOrdering; @@ -128,26 +142,29 @@ class VoxelmapCollider implements IVoxelmapCollider { } } - public setChunk(chunkId: THREE.Vector3Like, chunk: VoxelsChunkData): void { + public setChunk(chunkId: THREE.Vector3Like, chunk: VoxelsChunkDataForCollisions): void { const patchId = new PatchId(chunkId); if (this.chunkCollidersMap[patchId.asString]) { logger.debug(`Chunk "${patchId.asString}" already exists.`); } if (chunk.isEmpty) { - this.chunkCollidersMap[patchId.asString] = { isEmpty: true }; + this.chunkCollidersMap[patchId.asString] = { isEmpty: true, isFull: false }; + } else if (chunk.isFull) { + this.chunkCollidersMap[patchId.asString] = { isEmpty: false, isFull: true }; } else { if (chunk.dataOrdering !== this.voxelsChunkOrdering) { throw new Error(`Invalid voxels chunk ordering: expected "${this.voxelsChunkOrdering}", received "${chunk.dataOrdering}".`); } if (this.compactionWorkersPool) { - const rawChunkCollider: ChunkCollider = { isEmpty: false, type: 'raw', data: chunk.data }; + const rawChunkCollider: ChunkCollider = { isEmpty: false, isFull: false, type: 'raw', data: chunk.data }; this.chunkCollidersMap[patchId.asString] = rawChunkCollider; this.compactionWorkersPool.submitTask('compactChunk', chunk.data).then(data => { if (this.chunkCollidersMap[patchId.asString] === rawChunkCollider) { this.chunkCollidersMap[patchId.asString] = { isEmpty: false, + isFull: false, type: 'compacted', data, }; @@ -158,6 +175,7 @@ class VoxelmapCollider implements IVoxelmapCollider { } else { this.chunkCollidersMap[patchId.asString] = { isEmpty: false, + isFull: false, type: 'compacted', data: this.compactor.compactChunk(chunk.data), }; @@ -179,6 +197,8 @@ class VoxelmapCollider implements IVoxelmapCollider { if (chunk.isEmpty) { return EVoxelStatus.EMPTY; + } else if (chunk.isFull) { + return EVoxelStatus.FULL; } const localVoxelCoords = { @@ -232,7 +252,7 @@ class VoxelmapCollider implements IVoxelmapCollider { for (const chunk of Object.values(this.chunkCollidersMap)) { statistics.totalChunksCount++; - if (!chunk.isEmpty) { + if (!chunk.isEmpty && !chunk.isFull) { if (chunk.type === 'compacted') { statistics.compactedChunks.count++; statistics.compactedChunks.totalMemoryBytes += chunk.data.byteLength; diff --git a/src/test/test-physics.ts b/src/test/test-physics.ts index 43647796..b2541e48 100644 --- a/src/test/test-physics.ts +++ b/src/test/test-physics.ts @@ -328,7 +328,11 @@ class TestPhysics extends TestBase { const voxelsChunkData = Object.assign(patchMapData, { size: new THREE.Vector3().subVectors(blockEnd, blockStart), }); - this.voxelmapCollider.setChunk(patchId, voxelsChunkData); + + this.voxelmapCollider.setChunk(patchId, { + ...voxelsChunkData, + isFull: false, + }); await this.voxelmapViewer.enqueuePatch(patchId, voxelsChunkData); } },