@manycore/aholo-splat-transform 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +33 -0
- package/bin/cli.js +118 -0
- package/dist/SplatData.d.ts +67 -0
- package/dist/SplatData.js +156 -0
- package/dist/constant.d.ts +3 -0
- package/dist/constant.js +13 -0
- package/dist/file/IFile.d.ts +5 -0
- package/dist/file/IFile.js +1 -0
- package/dist/file/index.d.ts +7 -0
- package/dist/file/index.js +6 -0
- package/dist/file/ksplat.d.ts +12 -0
- package/dist/file/ksplat.js +232 -0
- package/dist/file/lcc.d.ts +11 -0
- package/dist/file/lcc.js +157 -0
- package/dist/file/ply.d.ts +13 -0
- package/dist/file/ply.js +388 -0
- package/dist/file/sog.d.ts +80 -0
- package/dist/file/sog.js +504 -0
- package/dist/file/splat.d.ts +6 -0
- package/dist/file/splat.js +99 -0
- package/dist/file/spz.d.ts +8 -0
- package/dist/file/spz.js +400 -0
- package/dist/file/voxel.d.ts +37 -0
- package/dist/file/voxel.js +280 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +54 -0
- package/dist/native/cpp/bin/linux/binding.node +0 -0
- package/dist/native/cpp/bin/windows/binding.node +0 -0
- package/dist/native/index.d.ts +54 -0
- package/dist/native/index.js +128 -0
- package/dist/tasks/AutoChunkLodTask.d.ts +13 -0
- package/dist/tasks/AutoChunkLodTask.js +117 -0
- package/dist/tasks/AutoLodTask.d.ts +10 -0
- package/dist/tasks/AutoLodTask.js +20 -0
- package/dist/tasks/BaseTask.d.ts +15 -0
- package/dist/tasks/BaseTask.js +5 -0
- package/dist/tasks/FlexLodTask.d.ts +12 -0
- package/dist/tasks/FlexLodTask.js +44 -0
- package/dist/tasks/ModifyTask.d.ts +9 -0
- package/dist/tasks/ModifyTask.js +156 -0
- package/dist/tasks/ReadTask.d.ts +8 -0
- package/dist/tasks/ReadTask.js +29 -0
- package/dist/tasks/SkeletonLodTask.d.ts +10 -0
- package/dist/tasks/SkeletonLodTask.js +156 -0
- package/dist/tasks/VoxelTask.d.ts +30 -0
- package/dist/tasks/VoxelTask.js +37 -0
- package/dist/tasks/WriteTask.d.ts +11 -0
- package/dist/tasks/WriteTask.js +70 -0
- package/dist/utils/BufferReader.d.ts +12 -0
- package/dist/utils/BufferReader.js +47 -0
- package/dist/utils/Logger.d.ts +11 -0
- package/dist/utils/Logger.js +38 -0
- package/dist/utils/StreamChunkDecoder.d.ts +16 -0
- package/dist/utils/StreamChunkDecoder.js +36 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/index.js +101 -0
- package/dist/utils/k-means.d.ts +4 -0
- package/dist/utils/k-means.js +350 -0
- package/dist/utils/math.d.ts +46 -0
- package/dist/utils/math.js +351 -0
- package/dist/utils/quantize-1d.d.ts +4 -0
- package/dist/utils/quantize-1d.js +164 -0
- package/dist/utils/sh-rotate.d.ts +2 -0
- package/dist/utils/sh-rotate.js +175 -0
- package/dist/utils/splat.d.ts +20 -0
- package/dist/utils/splat.js +378 -0
- package/dist/utils/voxel/common.d.ts +162 -0
- package/dist/utils/voxel/common.js +1700 -0
- package/dist/utils/voxel/coplanar-merge.d.ts +63 -0
- package/dist/utils/voxel/coplanar-merge.js +819 -0
- package/dist/utils/voxel/gpu-dilation.d.ts +2 -0
- package/dist/utils/voxel/gpu-dilation.js +665 -0
- package/dist/utils/voxel/marching-cubes.d.ts +42 -0
- package/dist/utils/voxel/marching-cubes.js +1657 -0
- package/dist/utils/voxel/mesh.d.ts +3 -0
- package/dist/utils/voxel/mesh.js +130 -0
- package/dist/utils/voxel/nav.d.ts +29 -0
- package/dist/utils/voxel/nav.js +1043 -0
- package/dist/utils/voxel/postprocess.d.ts +23 -0
- package/dist/utils/voxel/postprocess.js +375 -0
- package/dist/utils/voxel/voxel-faces.d.ts +18 -0
- package/dist/utils/voxel/voxel-faces.js +663 -0
- package/dist/utils/voxel/voxelize.d.ts +33 -0
- package/dist/utils/voxel/voxelize.js +1193 -0
- package/dist/utils/webgpu.d.ts +8 -0
- package/dist/utils/webgpu.js +122 -0
- package/package.json +32 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BlockMaskBuffer, SparseVoxelGrid, type Bounds } from './common.js';
|
|
2
|
+
/**
|
|
3
|
+
* Block cleanup pass:
|
|
4
|
+
* - remove voxels that have no supporting 6-neighborhood occupancy
|
|
5
|
+
* - fill single-voxel holes fully enclosed by 6 neighbors
|
|
6
|
+
* Includes cross-block neighbor propagation for face-adjacent blocks.
|
|
7
|
+
*/
|
|
8
|
+
export declare const filterAndFillBlocks: (blocks: BlockMaskBuffer, nbx?: number, nby?: number, nbz?: number) => BlockMaskBuffer;
|
|
9
|
+
export type { Bounds } from './common.js';
|
|
10
|
+
/** Crop blocks into [min, max) block range and rebase linear block coordinates. */
|
|
11
|
+
export declare const cropBlocksToRange: (blocks: BlockMaskBuffer, sourceNbx: number, sourceNby: number, cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number) => BlockMaskBuffer;
|
|
12
|
+
/** Compute world-space bounds corresponding to a cropped block range. */
|
|
13
|
+
export declare const cropBounds: (gridBounds: Bounds, voxelResolution: number, cropMinBx: number, cropMinBy: number, cropMinBz: number, cropMaxBx: number, cropMaxBy: number, cropMaxBz: number) => Bounds;
|
|
14
|
+
/** Tight crop to occupied block bounds. */
|
|
15
|
+
export declare const cropToOccupied: (grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number) => {
|
|
16
|
+
grid: SparseVoxelGrid;
|
|
17
|
+
gridBounds: Bounds;
|
|
18
|
+
};
|
|
19
|
+
/** Tight crop to navigable (non-fully-solid) block bounds. */
|
|
20
|
+
export declare const cropToNavigable: (grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number) => {
|
|
21
|
+
grid: SparseVoxelGrid;
|
|
22
|
+
gridBounds: Bounds;
|
|
23
|
+
};
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { BlockMaskBuffer, SOLID_HI, SOLID_LO } from './common.js';
|
|
2
|
+
import { logger } from '../Logger.js';
|
|
3
|
+
const FACE_X0 = 0x11111111;
|
|
4
|
+
const FACE_X3 = 0x88888888;
|
|
5
|
+
const FACE_Y0 = 0x000F000F;
|
|
6
|
+
const FACE_Y3 = 0xF000F000;
|
|
7
|
+
const FACE_Z0_LO = 0x0000FFFF;
|
|
8
|
+
const FACE_Z3_HI = 0xFFFF0000 >>> 0;
|
|
9
|
+
/** Count set bits in a 32-bit unsigned integer. */
|
|
10
|
+
const popcount = (n) => {
|
|
11
|
+
n >>>= 0;
|
|
12
|
+
n -= (n >>> 1) & 0x55555555;
|
|
13
|
+
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
|
|
14
|
+
return (((n + (n >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
15
|
+
};
|
|
16
|
+
const sortedUint32Has = (sorted, value) => {
|
|
17
|
+
let lo = 0;
|
|
18
|
+
let hi = sorted.length - 1;
|
|
19
|
+
while (lo <= hi) {
|
|
20
|
+
const mid = (lo + hi) >> 1;
|
|
21
|
+
const v = sorted[mid];
|
|
22
|
+
if (v < value) {
|
|
23
|
+
lo = mid + 1;
|
|
24
|
+
}
|
|
25
|
+
else if (v > value) {
|
|
26
|
+
hi = mid - 1;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
};
|
|
34
|
+
const findMixedBlockIndex = (sortedBlockIdx, target) => {
|
|
35
|
+
let lo = 0;
|
|
36
|
+
let hi = sortedBlockIdx.length - 1;
|
|
37
|
+
while (lo <= hi) {
|
|
38
|
+
const mid = (lo + hi) >> 1;
|
|
39
|
+
const v = sortedBlockIdx[mid];
|
|
40
|
+
if (v < target) {
|
|
41
|
+
lo = mid + 1;
|
|
42
|
+
}
|
|
43
|
+
else if (v > target) {
|
|
44
|
+
hi = mid - 1;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return mid;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
const sortMixedByBlockIdx = (blockIdx, masks) => {
|
|
53
|
+
const n = blockIdx.length;
|
|
54
|
+
if (n <= 1) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const stackLo = [0];
|
|
58
|
+
const stackHi = [n - 1];
|
|
59
|
+
const swap = (a, b) => {
|
|
60
|
+
const k = blockIdx[a];
|
|
61
|
+
blockIdx[a] = blockIdx[b];
|
|
62
|
+
blockIdx[b] = k;
|
|
63
|
+
const alo = masks[a * 2];
|
|
64
|
+
const ahi = masks[a * 2 + 1];
|
|
65
|
+
masks[a * 2] = masks[b * 2];
|
|
66
|
+
masks[a * 2 + 1] = masks[b * 2 + 1];
|
|
67
|
+
masks[b * 2] = alo;
|
|
68
|
+
masks[b * 2 + 1] = ahi;
|
|
69
|
+
};
|
|
70
|
+
while (stackLo.length > 0) {
|
|
71
|
+
const lo = stackLo.pop();
|
|
72
|
+
const hi = stackHi.pop();
|
|
73
|
+
if (hi - lo < 16) {
|
|
74
|
+
for (let i = lo + 1; i <= hi; i++) {
|
|
75
|
+
const k = blockIdx[i];
|
|
76
|
+
const m0 = masks[i * 2];
|
|
77
|
+
const m1 = masks[i * 2 + 1];
|
|
78
|
+
let j = i - 1;
|
|
79
|
+
while (j >= lo && blockIdx[j] > k) {
|
|
80
|
+
blockIdx[j + 1] = blockIdx[j];
|
|
81
|
+
masks[(j + 1) * 2] = masks[j * 2];
|
|
82
|
+
masks[(j + 1) * 2 + 1] = masks[j * 2 + 1];
|
|
83
|
+
j--;
|
|
84
|
+
}
|
|
85
|
+
blockIdx[j + 1] = k;
|
|
86
|
+
masks[(j + 1) * 2] = m0;
|
|
87
|
+
masks[(j + 1) * 2 + 1] = m1;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const mid = (lo + hi) >>> 1;
|
|
92
|
+
if (blockIdx[mid] < blockIdx[lo]) {
|
|
93
|
+
swap(mid, lo);
|
|
94
|
+
}
|
|
95
|
+
if (blockIdx[hi] < blockIdx[lo]) {
|
|
96
|
+
swap(hi, lo);
|
|
97
|
+
}
|
|
98
|
+
if (blockIdx[hi] < blockIdx[mid]) {
|
|
99
|
+
swap(hi, mid);
|
|
100
|
+
}
|
|
101
|
+
const pivot = blockIdx[mid];
|
|
102
|
+
let i = lo;
|
|
103
|
+
let j = hi;
|
|
104
|
+
while (i <= j) {
|
|
105
|
+
while (blockIdx[i] < pivot) {
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
while (blockIdx[j] > pivot) {
|
|
109
|
+
j--;
|
|
110
|
+
}
|
|
111
|
+
if (i <= j) {
|
|
112
|
+
if (i !== j) {
|
|
113
|
+
swap(i, j);
|
|
114
|
+
}
|
|
115
|
+
i++;
|
|
116
|
+
j--;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (j - lo > hi - i) {
|
|
120
|
+
if (lo < j) {
|
|
121
|
+
stackLo.push(lo);
|
|
122
|
+
stackHi.push(j);
|
|
123
|
+
}
|
|
124
|
+
if (i < hi) {
|
|
125
|
+
stackLo.push(i);
|
|
126
|
+
stackHi.push(hi);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
if (i < hi) {
|
|
131
|
+
stackLo.push(i);
|
|
132
|
+
stackHi.push(hi);
|
|
133
|
+
}
|
|
134
|
+
if (lo < j) {
|
|
135
|
+
stackLo.push(lo);
|
|
136
|
+
stackHi.push(j);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const addCrossFace = (nx, ny, nz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, ourFaceMask, adjFaceMask, shiftAmount, shiftLeft, curLo, curHi, write) => {
|
|
142
|
+
if (nx < 0 || ny < 0 || nz < 0 || nx >= nbx || ny >= nby || nz >= nbz) {
|
|
143
|
+
write(curLo, curHi);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const adjBlockIdx = nx + ny * nbx + nz * nbx * nby;
|
|
147
|
+
if (hasSolid(adjBlockIdx)) {
|
|
148
|
+
write(curLo | ourFaceMask, curHi | ourFaceMask);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const adjIdx = getMixedIndex(adjBlockIdx);
|
|
152
|
+
if (adjIdx === undefined) {
|
|
153
|
+
write(curLo, curHi);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const adjLo = masks[adjIdx * 2];
|
|
157
|
+
const adjHi = masks[adjIdx * 2 + 1];
|
|
158
|
+
const faceLo = adjLo & adjFaceMask;
|
|
159
|
+
const faceHi = adjHi & adjFaceMask;
|
|
160
|
+
if (shiftLeft) {
|
|
161
|
+
write(curLo | (faceLo << shiftAmount), curHi | (faceHi << shiftAmount));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
write(curLo | (faceLo >>> shiftAmount), curHi | (faceHi >>> shiftAmount));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const addCrossFaceZ = (nx, ny, nz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, plusZ, curLo, curHi, write) => {
|
|
168
|
+
if (nx < 0 || ny < 0 || nz < 0 || nx >= nbx || ny >= nby || nz >= nbz) {
|
|
169
|
+
write(curLo, curHi);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const adjBlockIdx = nx + ny * nbx + nz * nbx * nby;
|
|
173
|
+
if (hasSolid(adjBlockIdx)) {
|
|
174
|
+
if (plusZ) {
|
|
175
|
+
write(curLo, curHi | FACE_Z3_HI);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
write(curLo | FACE_Z0_LO, curHi);
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const adjIdx = getMixedIndex(adjBlockIdx);
|
|
183
|
+
if (adjIdx === undefined) {
|
|
184
|
+
write(curLo, curHi);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const adjLo = masks[adjIdx * 2];
|
|
188
|
+
const adjHi = masks[adjIdx * 2 + 1];
|
|
189
|
+
if (plusZ) {
|
|
190
|
+
write(curLo, curHi | ((adjLo & FACE_Z0_LO) << 16));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
write(curLo | ((adjHi & FACE_Z3_HI) >>> 16), curHi);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Block cleanup pass:
|
|
198
|
+
* - remove voxels that have no supporting 6-neighborhood occupancy
|
|
199
|
+
* - fill single-voxel holes fully enclosed by 6 neighbors
|
|
200
|
+
* Includes cross-block neighbor propagation for face-adjacent blocks.
|
|
201
|
+
*/
|
|
202
|
+
export const filterAndFillBlocks = (blocks, nbx = Infinity, nby = Infinity, nbz = Infinity) => {
|
|
203
|
+
const mixed = blocks.getMixedBlocks();
|
|
204
|
+
const solids = blocks.getSolidBlocks();
|
|
205
|
+
const mixedCount = mixed.blockIdx.length;
|
|
206
|
+
const masks = mixed.masks;
|
|
207
|
+
if (mixedCount === 0) {
|
|
208
|
+
return blocks;
|
|
209
|
+
}
|
|
210
|
+
const mixedBlockIdx = new Float64Array(mixedCount);
|
|
211
|
+
for (let i = 0; i < mixedCount; i++) {
|
|
212
|
+
mixedBlockIdx[i] = mixed.blockIdx[i];
|
|
213
|
+
}
|
|
214
|
+
sortMixedByBlockIdx(mixedBlockIdx, masks);
|
|
215
|
+
const sortedSolid = new Float64Array(solids.length);
|
|
216
|
+
for (let i = 0; i < solids.length; i++) {
|
|
217
|
+
sortedSolid[i] = solids[i];
|
|
218
|
+
}
|
|
219
|
+
sortedSolid.sort();
|
|
220
|
+
const hasSolid = (blockIdx) => sortedUint32Has(sortedSolid, blockIdx);
|
|
221
|
+
const getMixedIndex = (blockIdx) => findMixedBlockIndex(mixedBlockIdx, blockIdx);
|
|
222
|
+
const newMasks = new Uint32Array(masks.length);
|
|
223
|
+
let voxelsRemoved = 0;
|
|
224
|
+
let voxelsFilled = 0;
|
|
225
|
+
for (let i = 0; i < mixedCount; i++) {
|
|
226
|
+
const blockIdx = mixedBlockIdx[i];
|
|
227
|
+
const origLo = masks[i * 2];
|
|
228
|
+
const origHi = masks[i * 2 + 1];
|
|
229
|
+
const bx = blockIdx % nbx;
|
|
230
|
+
const byBz = (blockIdx / nbx) | 0;
|
|
231
|
+
const by = byBz % nby;
|
|
232
|
+
const bz = (blockIdx / (nbx * nby)) | 0;
|
|
233
|
+
let pxLo = (origLo >>> 1) & ~FACE_X3;
|
|
234
|
+
let pxHi = (origHi >>> 1) & ~FACE_X3;
|
|
235
|
+
let mxLo = (origLo << 1) & ~FACE_X0;
|
|
236
|
+
let mxHi = (origHi << 1) & ~FACE_X0;
|
|
237
|
+
let pyLo = (origLo >>> 4) & ~FACE_Y3;
|
|
238
|
+
let pyHi = (origHi >>> 4) & ~FACE_Y3;
|
|
239
|
+
let myLo = (origLo << 4) & ~FACE_Y0;
|
|
240
|
+
let myHi = (origHi << 4) & ~FACE_Y0;
|
|
241
|
+
let pzLo = (origLo >>> 16) | (origHi << 16);
|
|
242
|
+
let pzHi = origHi >>> 16;
|
|
243
|
+
let mzLo = origLo << 16;
|
|
244
|
+
let mzHi = (origHi << 16) | (origLo >>> 16);
|
|
245
|
+
addCrossFace(bx + 1, by, bz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, FACE_X3, FACE_X0, 3, true, pxLo, pxHi, (lo, hi) => { pxLo = lo; pxHi = hi; });
|
|
246
|
+
addCrossFace(bx - 1, by, bz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, FACE_X0, FACE_X3, 3, false, mxLo, mxHi, (lo, hi) => { mxLo = lo; mxHi = hi; });
|
|
247
|
+
addCrossFace(bx, by + 1, bz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, FACE_Y3, FACE_Y0, 12, true, pyLo, pyHi, (lo, hi) => { pyLo = lo; pyHi = hi; });
|
|
248
|
+
addCrossFace(bx, by - 1, bz, nbx, nby, nbz, hasSolid, getMixedIndex, masks, FACE_Y0, FACE_Y3, 12, false, myLo, myHi, (lo, hi) => { myLo = lo; myHi = hi; });
|
|
249
|
+
addCrossFaceZ(bx, by, bz + 1, nbx, nby, nbz, hasSolid, getMixedIndex, masks, true, pzLo, pzHi, (lo, hi) => { pzLo = lo; pzHi = hi; });
|
|
250
|
+
addCrossFaceZ(bx, by, bz - 1, nbx, nby, nbz, hasSolid, getMixedIndex, masks, false, mzLo, mzHi, (lo, hi) => { mzLo = lo; mzHi = hi; });
|
|
251
|
+
const neighborLo = pxLo | mxLo | pyLo | myLo | pzLo | mzLo;
|
|
252
|
+
const neighborHi = pxHi | mxHi | pyHi | myHi | pzHi | mzHi;
|
|
253
|
+
let lo = origLo & neighborLo;
|
|
254
|
+
let hi = origHi & neighborHi;
|
|
255
|
+
const fillLo = ~lo & pxLo & mxLo & pyLo & myLo & pzLo & mzLo;
|
|
256
|
+
const fillHi = ~hi & pxHi & mxHi & pyHi & myHi & pzHi & mzHi;
|
|
257
|
+
lo |= fillLo;
|
|
258
|
+
hi |= fillHi;
|
|
259
|
+
voxelsRemoved += popcount(origLo & ~lo) + popcount(origHi & ~hi);
|
|
260
|
+
voxelsFilled += popcount(lo & ~origLo) + popcount(hi & ~origHi);
|
|
261
|
+
newMasks[i * 2] = lo >>> 0;
|
|
262
|
+
newMasks[i * 2 + 1] = hi >>> 0;
|
|
263
|
+
}
|
|
264
|
+
const result = new BlockMaskBuffer();
|
|
265
|
+
for (let i = 0; i < mixedCount; i++) {
|
|
266
|
+
const lo = newMasks[i * 2];
|
|
267
|
+
const hi = newMasks[i * 2 + 1];
|
|
268
|
+
result.addBlock(mixedBlockIdx[i], lo, hi);
|
|
269
|
+
}
|
|
270
|
+
for (let i = 0; i < solids.length; i++) {
|
|
271
|
+
result.addBlock(solids[i], SOLID_LO, SOLID_HI);
|
|
272
|
+
}
|
|
273
|
+
logger.info(`voxel filter: ${voxelsRemoved} voxels removed, ${voxelsFilled} voxels filled`);
|
|
274
|
+
return result;
|
|
275
|
+
};
|
|
276
|
+
/** Crop blocks into [min, max) block range and rebase linear block coordinates. */
|
|
277
|
+
export const cropBlocksToRange = (blocks, sourceNbx, sourceNby, cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz) => {
|
|
278
|
+
const cropped = new BlockMaskBuffer();
|
|
279
|
+
const outNbx = cropMaxBx - cropMinBx;
|
|
280
|
+
const outNby = cropMaxBy - cropMinBy;
|
|
281
|
+
const sourceBStride = sourceNbx * sourceNby;
|
|
282
|
+
const solids = blocks.getSolidBlocks();
|
|
283
|
+
for (let i = 0; i < solids.length; i++) {
|
|
284
|
+
const blockIdx = solids[i];
|
|
285
|
+
const bx = blockIdx % sourceNbx;
|
|
286
|
+
const byBz = (blockIdx / sourceNbx) | 0;
|
|
287
|
+
const by = byBz % sourceNby;
|
|
288
|
+
const bz = (blockIdx / sourceBStride) | 0;
|
|
289
|
+
if (bx < cropMinBx || by < cropMinBy || bz < cropMinBz) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (bx >= cropMaxBx || by >= cropMaxBy || bz >= cropMaxBz) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
cropped.addBlock((bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, SOLID_LO, SOLID_HI);
|
|
296
|
+
}
|
|
297
|
+
const mixed = blocks.getMixedBlocks();
|
|
298
|
+
for (let i = 0; i < mixed.blockIdx.length; i++) {
|
|
299
|
+
const blockIdx = mixed.blockIdx[i];
|
|
300
|
+
const bx = blockIdx % sourceNbx;
|
|
301
|
+
const byBz = (blockIdx / sourceNbx) | 0;
|
|
302
|
+
const by = byBz % sourceNby;
|
|
303
|
+
const bz = (blockIdx / sourceBStride) | 0;
|
|
304
|
+
if (bx < cropMinBx || by < cropMinBy || bz < cropMinBz) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (bx >= cropMaxBx || by >= cropMaxBy || bz >= cropMaxBz) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
cropped.addBlock((bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, mixed.masks[i * 2], mixed.masks[i * 2 + 1]);
|
|
311
|
+
}
|
|
312
|
+
return cropped;
|
|
313
|
+
};
|
|
314
|
+
/** Compute world-space bounds corresponding to a cropped block range. */
|
|
315
|
+
export const cropBounds = (gridBounds, voxelResolution, cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz) => {
|
|
316
|
+
const blockSize = 4 * voxelResolution;
|
|
317
|
+
const croppedMin = {
|
|
318
|
+
x: gridBounds.min.x + cropMinBx * blockSize,
|
|
319
|
+
y: gridBounds.min.y + cropMinBy * blockSize,
|
|
320
|
+
z: gridBounds.min.z + cropMinBz * blockSize
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
min: croppedMin,
|
|
324
|
+
max: {
|
|
325
|
+
x: croppedMin.x + (cropMaxBx - cropMinBx) * blockSize,
|
|
326
|
+
y: croppedMin.y + (cropMaxBy - cropMinBy) * blockSize,
|
|
327
|
+
z: croppedMin.z + (cropMaxBz - cropMinBz) * blockSize
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
/** Tight crop to occupied block bounds. */
|
|
332
|
+
export const cropToOccupied = (grid, gridBounds, voxelResolution) => {
|
|
333
|
+
const occupied = grid.getOccupiedBlockBounds();
|
|
334
|
+
if (!occupied) {
|
|
335
|
+
return { grid, gridBounds };
|
|
336
|
+
}
|
|
337
|
+
const { minBx, minBy, minBz, maxBx, maxBy, maxBz } = occupied;
|
|
338
|
+
const cropMaxBx = maxBx + 1;
|
|
339
|
+
const cropMaxBy = maxBy + 1;
|
|
340
|
+
const cropMaxBz = maxBz + 1;
|
|
341
|
+
const { nbx, nby, nbz } = grid;
|
|
342
|
+
if (minBx === 0 && minBy === 0 && minBz === 0 && cropMaxBx === nbx && cropMaxBy === nby && cropMaxBz === nbz) {
|
|
343
|
+
return { grid, gridBounds };
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
grid: grid.cropTo(minBx, minBy, minBz, cropMaxBx, cropMaxBy, cropMaxBz),
|
|
347
|
+
gridBounds: cropBounds(gridBounds, voxelResolution, minBx, minBy, minBz, cropMaxBx, cropMaxBy, cropMaxBz)
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
/** Tight crop to navigable (non-fully-solid) block bounds. */
|
|
351
|
+
export const cropToNavigable = (grid, gridBounds, voxelResolution) => {
|
|
352
|
+
const navBounds = grid.getNavigableBlockBounds();
|
|
353
|
+
if (!navBounds) {
|
|
354
|
+
return { grid, gridBounds };
|
|
355
|
+
}
|
|
356
|
+
const { minBx, minBy, minBz, maxBx, maxBy, maxBz } = navBounds;
|
|
357
|
+
const { nbx, nby, nbz } = grid;
|
|
358
|
+
// Keep one solid wall block around the navigable cavity. The runtime
|
|
359
|
+
// treats out-of-grid as solid, but collision extraction sees out-of-grid
|
|
360
|
+
// as empty; this padding keeps collision meshes sealed at crop edges.
|
|
361
|
+
const MARGIN = 1;
|
|
362
|
+
const cropMinBx = Math.max(0, minBx - MARGIN);
|
|
363
|
+
const cropMinBy = Math.max(0, minBy - MARGIN);
|
|
364
|
+
const cropMinBz = Math.max(0, minBz - MARGIN);
|
|
365
|
+
const cropMaxBx = Math.min(nbx, maxBx + 1 + MARGIN);
|
|
366
|
+
const cropMaxBy = Math.min(nby, maxBy + 1 + MARGIN);
|
|
367
|
+
const cropMaxBz = Math.min(nbz, maxBz + 1 + MARGIN);
|
|
368
|
+
if (cropMinBx === 0 && cropMinBy === 0 && cropMinBz === 0 && cropMaxBx === nbx && cropMaxBy === nby && cropMaxBz === nbz) {
|
|
369
|
+
return { grid, gridBounds };
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
grid: grid.cropTo(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz),
|
|
373
|
+
gridBounds: cropBounds(gridBounds, voxelResolution, cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz)
|
|
374
|
+
};
|
|
375
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Bounds } from './common.js';
|
|
2
|
+
import type { Mesh } from './marching-cubes.js';
|
|
3
|
+
import { SparseVoxelGrid } from './common.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract a watertight voxel-boundary mesh from a SparseVoxelGrid.
|
|
6
|
+
*
|
|
7
|
+
* Exposed voxel faces are first greedily merged into axis-aligned rectangles.
|
|
8
|
+
* Rectangle boundaries are then split at every collinear rectangle corner
|
|
9
|
+
* before triangulation, so adjacent rectangles share matching edges instead
|
|
10
|
+
* of producing T-junctions.
|
|
11
|
+
*
|
|
12
|
+
* @param grid - Voxel grid after filtering / nav phases.
|
|
13
|
+
* @param gridBounds - Grid bounds aligned to block boundaries.
|
|
14
|
+
* @param voxelResolution - Size of each voxel in world units.
|
|
15
|
+
* @returns Mesh with positions and indices.
|
|
16
|
+
*/
|
|
17
|
+
declare const voxelFaces: (grid: SparseVoxelGrid, gridBounds: Bounds, voxelResolution: number) => Mesh;
|
|
18
|
+
export { voxelFaces };
|