Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/allow empty chunks #62

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
28 changes: 24 additions & 4 deletions src/lib/physics/voxelmap-collider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand All @@ -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;
Expand Down Expand Up @@ -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<Uint8Array>('compactChunk', chunk.data).then(data => {
if (this.chunkCollidersMap[patchId.asString] === rawChunkCollider) {
this.chunkCollidersMap[patchId.asString] = {
isEmpty: false,
isFull: false,
type: 'compacted',
data,
};
Expand All @@ -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),
};
Expand All @@ -179,6 +197,8 @@ class VoxelmapCollider implements IVoxelmapCollider {

if (chunk.isEmpty) {
return EVoxelStatus.EMPTY;
} else if (chunk.isFull) {
return EVoxelStatus.FULL;
}

const localVoxelCoords = {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/terrain/voxelmap/board/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]!;
};
Expand Down
8 changes: 6 additions & 2 deletions src/lib/terrain/voxelmap/board/voxelmap-wrapper.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -52,11 +52,15 @@ class VoxelmapWrapper implements IVoxelMap {
this.includeBoard = includeBoard;
}

public getLocalMapData(blockStart: THREE.Vector3Like, blockEnd: THREE.Vector3Like): ILocalMapData | Promise<ILocalMapData> {
public getLocalMapData(blockStart: THREE.Vector3Like, blockEnd: THREE.Vector3Like): LocalMapData | Promise<LocalMapData> {
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++) {
Expand Down
39 changes: 19 additions & 20 deletions src/lib/terrain/voxelmap/i-voxelmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<ILocalMapData>;
getLocalMapData(from: Vector3Like, to: Vector3Like): LocalMapData | Promise<LocalMapData>;
}

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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 3 additions & 7 deletions src/test/map/voxel-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -133,7 +133,7 @@ class VoxelMap implements IVoxelMap, IHeightmap {
}
}

public getLocalMapData(blockStart: THREE.Vector3, blockEnd: THREE.Vector3): ILocalMapData | Promise<ILocalMapData> {
public getLocalMapData(blockStart: THREE.Vector3, blockEnd: THREE.Vector3): LocalMapData | Promise<LocalMapData> {
const blockSize = new THREE.Vector3().subVectors(blockEnd, blockStart);
const data = new Uint16Array(blockSize.x * blockSize.y * blockSize.z);

Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 5 additions & 1 deletion src/test/test-physics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
},
Expand Down
Loading