@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,663 @@
|
|
|
1
|
+
import { BLOCK_EMPTY, BLOCK_SOLID, BLOCKS_PER_WORD, EVEN_BITS, readBlockType } from './common.js';
|
|
2
|
+
const HASH_MUL = 0x9E3779B9;
|
|
3
|
+
/**
|
|
4
|
+
* Extract a watertight voxel-boundary mesh from a SparseVoxelGrid.
|
|
5
|
+
*
|
|
6
|
+
* Exposed voxel faces are first greedily merged into axis-aligned rectangles.
|
|
7
|
+
* Rectangle boundaries are then split at every collinear rectangle corner
|
|
8
|
+
* before triangulation, so adjacent rectangles share matching edges instead
|
|
9
|
+
* of producing T-junctions.
|
|
10
|
+
*
|
|
11
|
+
* @param grid - Voxel grid after filtering / nav phases.
|
|
12
|
+
* @param gridBounds - Grid bounds aligned to block boundaries.
|
|
13
|
+
* @param voxelResolution - Size of each voxel in world units.
|
|
14
|
+
* @returns Mesh with positions and indices.
|
|
15
|
+
*/
|
|
16
|
+
const voxelFaces = (grid, gridBounds, voxelResolution) => {
|
|
17
|
+
const { nbx, nby, nbz, bStride, types, masks, nx, ny, nz } = grid;
|
|
18
|
+
const totalBlocks = nbx * nby * nbz;
|
|
19
|
+
const coordStride = Math.max(nx, ny, nz) + 1;
|
|
20
|
+
let faceCap = 1024;
|
|
21
|
+
let faceLen = 0;
|
|
22
|
+
let faceKeys = new Float64Array(faceCap);
|
|
23
|
+
const addFace = (bucket, p, u, v) => {
|
|
24
|
+
if (faceLen === faceCap) {
|
|
25
|
+
faceCap *= 2;
|
|
26
|
+
const grown = new Float64Array(faceCap);
|
|
27
|
+
grown.set(faceKeys);
|
|
28
|
+
faceKeys = grown;
|
|
29
|
+
}
|
|
30
|
+
faceKeys[faceLen++] =
|
|
31
|
+
(((bucket * coordStride + p) * coordStride + u) * coordStride + v);
|
|
32
|
+
};
|
|
33
|
+
const blockTypeAt = (bx, by, bz) => {
|
|
34
|
+
if (bx < 0 || by < 0 || bz < 0 || bx >= nbx || by >= nby || bz >= nbz) {
|
|
35
|
+
return BLOCK_EMPTY;
|
|
36
|
+
}
|
|
37
|
+
return readBlockType(types, bx + by * nbx + bz * bStride);
|
|
38
|
+
};
|
|
39
|
+
const isVoxelSetLocal = (lo, hi, lx, ly, lz) => {
|
|
40
|
+
const bitIdx = lx + (ly << 2) + (lz << 4);
|
|
41
|
+
return bitIdx < 32 ?
|
|
42
|
+
((lo >>> bitIdx) & 1) !== 0 :
|
|
43
|
+
((hi >>> (bitIdx - 32)) & 1) !== 0;
|
|
44
|
+
};
|
|
45
|
+
const isVoxelSetGlobal = (ix, iy, iz) => {
|
|
46
|
+
if (ix < 0 || iy < 0 || iz < 0 || ix >= nx || iy >= ny || iz >= nz) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const blockIdx = (ix >> 2) + (iy >> 2) * nbx + (iz >> 2) * bStride;
|
|
50
|
+
const bt = readBlockType(types, blockIdx);
|
|
51
|
+
if (bt === BLOCK_EMPTY) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (bt === BLOCK_SOLID) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const s = masks.slot(blockIdx);
|
|
58
|
+
return isVoxelSetLocal(masks.lo[s], masks.hi[s], ix & 3, iy & 3, iz & 3);
|
|
59
|
+
};
|
|
60
|
+
const addVoxelFace = (ix, iy, iz, bucket) => {
|
|
61
|
+
switch (bucket) {
|
|
62
|
+
case 0:
|
|
63
|
+
addFace(0, ix, iy, iz);
|
|
64
|
+
break; // -X
|
|
65
|
+
case 1:
|
|
66
|
+
addFace(1, ix + 1, iy, iz);
|
|
67
|
+
break; // +X
|
|
68
|
+
case 2:
|
|
69
|
+
addFace(2, iy, ix, iz);
|
|
70
|
+
break; // -Y
|
|
71
|
+
case 3:
|
|
72
|
+
addFace(3, iy + 1, ix, iz);
|
|
73
|
+
break; // +Y
|
|
74
|
+
case 4:
|
|
75
|
+
addFace(4, iz, ix, iy);
|
|
76
|
+
break; // -Z
|
|
77
|
+
default:
|
|
78
|
+
addFace(5, iz + 1, ix, iy);
|
|
79
|
+
break; // +Z
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const processSolidBlock = (bx, by, bz) => {
|
|
83
|
+
const x0 = bx << 2;
|
|
84
|
+
const y0 = by << 2;
|
|
85
|
+
const z0 = bz << 2;
|
|
86
|
+
const emitX = (bucket, neighborBlockType, ix, nx2) => {
|
|
87
|
+
if (neighborBlockType === BLOCK_SOLID) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (let lz = 0; lz < 4; lz++) {
|
|
91
|
+
const iz = z0 + lz;
|
|
92
|
+
for (let ly = 0; ly < 4; ly++) {
|
|
93
|
+
const iy = y0 + ly;
|
|
94
|
+
if (neighborBlockType === BLOCK_EMPTY || !isVoxelSetGlobal(nx2, iy, iz)) {
|
|
95
|
+
addVoxelFace(ix, iy, iz, bucket);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const emitY = (bucket, neighborBlockType, iy, ny2) => {
|
|
101
|
+
if (neighborBlockType === BLOCK_SOLID) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
for (let lz = 0; lz < 4; lz++) {
|
|
105
|
+
const iz = z0 + lz;
|
|
106
|
+
for (let lx = 0; lx < 4; lx++) {
|
|
107
|
+
const ix = x0 + lx;
|
|
108
|
+
if (neighborBlockType === BLOCK_EMPTY || !isVoxelSetGlobal(ix, ny2, iz)) {
|
|
109
|
+
addVoxelFace(ix, iy, iz, bucket);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const emitZ = (bucket, neighborBlockType, iz, nz2) => {
|
|
115
|
+
if (neighborBlockType === BLOCK_SOLID) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
for (let ly = 0; ly < 4; ly++) {
|
|
119
|
+
const iy = y0 + ly;
|
|
120
|
+
for (let lx = 0; lx < 4; lx++) {
|
|
121
|
+
const ix = x0 + lx;
|
|
122
|
+
if (neighborBlockType === BLOCK_EMPTY || !isVoxelSetGlobal(ix, iy, nz2)) {
|
|
123
|
+
addVoxelFace(ix, iy, iz, bucket);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
emitX(0, blockTypeAt(bx - 1, by, bz), x0, x0 - 1);
|
|
129
|
+
emitX(1, blockTypeAt(bx + 1, by, bz), x0 + 3, x0 + 4);
|
|
130
|
+
emitY(2, blockTypeAt(bx, by - 1, bz), y0, y0 - 1);
|
|
131
|
+
emitY(3, blockTypeAt(bx, by + 1, bz), y0 + 3, y0 + 4);
|
|
132
|
+
emitZ(4, blockTypeAt(bx, by, bz - 1), z0, z0 - 1);
|
|
133
|
+
emitZ(5, blockTypeAt(bx, by, bz + 1), z0 + 3, z0 + 4);
|
|
134
|
+
};
|
|
135
|
+
const processMixedBlock = (blockIdx, bx, by, bz) => {
|
|
136
|
+
const s = masks.slot(blockIdx);
|
|
137
|
+
const lo = masks.lo[s];
|
|
138
|
+
const hi = masks.hi[s];
|
|
139
|
+
const x0 = bx << 2;
|
|
140
|
+
const y0 = by << 2;
|
|
141
|
+
const z0 = bz << 2;
|
|
142
|
+
for (let lz = 0; lz < 4; lz++) {
|
|
143
|
+
const iz = z0 + lz;
|
|
144
|
+
for (let ly = 0; ly < 4; ly++) {
|
|
145
|
+
const iy = y0 + ly;
|
|
146
|
+
for (let lx = 0; lx < 4; lx++) {
|
|
147
|
+
if (!isVoxelSetLocal(lo, hi, lx, ly, lz)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const ix = x0 + lx;
|
|
151
|
+
if (!isVoxelSetGlobal(ix - 1, iy, iz)) {
|
|
152
|
+
addVoxelFace(ix, iy, iz, 0);
|
|
153
|
+
}
|
|
154
|
+
if (!isVoxelSetGlobal(ix + 1, iy, iz)) {
|
|
155
|
+
addVoxelFace(ix, iy, iz, 1);
|
|
156
|
+
}
|
|
157
|
+
if (!isVoxelSetGlobal(ix, iy - 1, iz)) {
|
|
158
|
+
addVoxelFace(ix, iy, iz, 2);
|
|
159
|
+
}
|
|
160
|
+
if (!isVoxelSetGlobal(ix, iy + 1, iz)) {
|
|
161
|
+
addVoxelFace(ix, iy, iz, 3);
|
|
162
|
+
}
|
|
163
|
+
if (!isVoxelSetGlobal(ix, iy, iz - 1)) {
|
|
164
|
+
addVoxelFace(ix, iy, iz, 4);
|
|
165
|
+
}
|
|
166
|
+
if (!isVoxelSetGlobal(ix, iy, iz + 1)) {
|
|
167
|
+
addVoxelFace(ix, iy, iz, 5);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
for (let w = 0; w < types.length; w++) {
|
|
174
|
+
const word = types[w];
|
|
175
|
+
if (word === 0) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
|
|
179
|
+
const baseBlockIdx = w * BLOCKS_PER_WORD;
|
|
180
|
+
while (nonEmpty) {
|
|
181
|
+
const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
|
|
182
|
+
const lane = bp >>> 1;
|
|
183
|
+
const blockIdx = baseBlockIdx + lane;
|
|
184
|
+
nonEmpty &= nonEmpty - 1;
|
|
185
|
+
if (blockIdx >= totalBlocks) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
const bx = blockIdx % nbx;
|
|
189
|
+
const byBz = (blockIdx / nbx) | 0;
|
|
190
|
+
const by = byBz % nby;
|
|
191
|
+
const bz = (byBz / nby) | 0;
|
|
192
|
+
const bt = (word >>> (lane << 1)) & 3;
|
|
193
|
+
if (bt === BLOCK_SOLID) {
|
|
194
|
+
processSolidBlock(bx, by, bz);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
processMixedBlock(blockIdx, bx, by, bz);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (faceLen === 0) {
|
|
202
|
+
return { positions: new Float32Array(0), indices: new Uint32Array(0) };
|
|
203
|
+
}
|
|
204
|
+
let rectCap = 1024;
|
|
205
|
+
let rectLen = 0;
|
|
206
|
+
let rectBucket = new Int32Array(rectCap);
|
|
207
|
+
let rectP = new Int32Array(rectCap);
|
|
208
|
+
let rectU0 = new Int32Array(rectCap);
|
|
209
|
+
let rectV0 = new Int32Array(rectCap);
|
|
210
|
+
let rectU1 = new Int32Array(rectCap);
|
|
211
|
+
let rectV1 = new Int32Array(rectCap);
|
|
212
|
+
const addRect = (bucket, p, u0, v0, u1, v1) => {
|
|
213
|
+
if (rectLen === rectCap) {
|
|
214
|
+
rectCap *= 2;
|
|
215
|
+
const grow = (src) => {
|
|
216
|
+
const out = new Int32Array(rectCap);
|
|
217
|
+
out.set(src);
|
|
218
|
+
return out;
|
|
219
|
+
};
|
|
220
|
+
rectBucket = grow(rectBucket);
|
|
221
|
+
rectP = grow(rectP);
|
|
222
|
+
rectU0 = grow(rectU0);
|
|
223
|
+
rectV0 = grow(rectV0);
|
|
224
|
+
rectU1 = grow(rectU1);
|
|
225
|
+
rectV1 = grow(rectV1);
|
|
226
|
+
}
|
|
227
|
+
rectBucket[rectLen] = bucket;
|
|
228
|
+
rectP[rectLen] = p;
|
|
229
|
+
rectU0[rectLen] = u0;
|
|
230
|
+
rectV0[rectLen] = v0;
|
|
231
|
+
rectU1[rectLen] = u1;
|
|
232
|
+
rectV1[rectLen] = v1;
|
|
233
|
+
rectLen++;
|
|
234
|
+
};
|
|
235
|
+
const keys = faceKeys.subarray(0, faceLen);
|
|
236
|
+
faceKeys = new Float64Array(0);
|
|
237
|
+
keys.sort();
|
|
238
|
+
const decodeGroup = (key) => {
|
|
239
|
+
let q = Math.floor(key / coordStride);
|
|
240
|
+
q = Math.floor(q / coordStride);
|
|
241
|
+
const p = q % coordStride;
|
|
242
|
+
const bucket = Math.floor(q / coordStride);
|
|
243
|
+
return { bucket, p };
|
|
244
|
+
};
|
|
245
|
+
const decodeUvKey = (key) => {
|
|
246
|
+
const v = key % coordStride;
|
|
247
|
+
const q = Math.floor(key / coordStride);
|
|
248
|
+
const u = q % coordStride;
|
|
249
|
+
return u * coordStride + v;
|
|
250
|
+
};
|
|
251
|
+
let groupStart = 0;
|
|
252
|
+
while (groupStart < keys.length) {
|
|
253
|
+
const { bucket, p } = decodeGroup(keys[groupStart]);
|
|
254
|
+
let groupEnd = groupStart + 1;
|
|
255
|
+
while (groupEnd < keys.length) {
|
|
256
|
+
const g = decodeGroup(keys[groupEnd]);
|
|
257
|
+
if (g.bucket !== bucket || g.p !== p) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
groupEnd++;
|
|
261
|
+
}
|
|
262
|
+
const count = groupEnd - groupStart;
|
|
263
|
+
let hCap = 1;
|
|
264
|
+
while (hCap < count / 0.7) {
|
|
265
|
+
hCap *= 2;
|
|
266
|
+
}
|
|
267
|
+
const hMask = hCap - 1;
|
|
268
|
+
const hKeys = new Float64Array(hCap).fill(-1);
|
|
269
|
+
const hVals = new Int32Array(hCap);
|
|
270
|
+
const hash = (key) => {
|
|
271
|
+
const hi = (key / 0x100000000) | 0;
|
|
272
|
+
return (Math.imul((key | 0) ^ hi, HASH_MUL) >>> 0) & hMask;
|
|
273
|
+
};
|
|
274
|
+
for (let i = 0; i < count; i++) {
|
|
275
|
+
const uvKey = decodeUvKey(keys[groupStart + i]);
|
|
276
|
+
let h = hash(uvKey);
|
|
277
|
+
while (hKeys[h] !== -1) {
|
|
278
|
+
h = (h + 1) & hMask;
|
|
279
|
+
}
|
|
280
|
+
hKeys[h] = uvKey;
|
|
281
|
+
hVals[h] = i;
|
|
282
|
+
}
|
|
283
|
+
const lookup = (uvKey) => {
|
|
284
|
+
let h = hash(uvKey);
|
|
285
|
+
while (true) {
|
|
286
|
+
const k = hKeys[h];
|
|
287
|
+
if (k === uvKey) {
|
|
288
|
+
return hVals[h];
|
|
289
|
+
}
|
|
290
|
+
if (k === -1) {
|
|
291
|
+
return -1;
|
|
292
|
+
}
|
|
293
|
+
h = (h + 1) & hMask;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const visited = new Uint8Array(count);
|
|
297
|
+
const uvKeyOf = (u, v) => u * coordStride + v;
|
|
298
|
+
for (let i = 0; i < count; i++) {
|
|
299
|
+
if (visited[i]) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const uvKey = decodeUvKey(keys[groupStart + i]);
|
|
303
|
+
const u0 = Math.floor(uvKey / coordStride);
|
|
304
|
+
const v0 = uvKey % coordStride;
|
|
305
|
+
let width = 1;
|
|
306
|
+
while (true) {
|
|
307
|
+
const idx = lookup(uvKeyOf(u0 + width, v0));
|
|
308
|
+
if (idx === -1 || visited[idx]) {
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
width++;
|
|
312
|
+
}
|
|
313
|
+
let height = 1;
|
|
314
|
+
while (true) {
|
|
315
|
+
let canGrow = true;
|
|
316
|
+
for (let du = 0; du < width; du++) {
|
|
317
|
+
const idx = lookup(uvKeyOf(u0 + du, v0 + height));
|
|
318
|
+
if (idx === -1 || visited[idx]) {
|
|
319
|
+
canGrow = false;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (!canGrow) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
height++;
|
|
327
|
+
}
|
|
328
|
+
for (let dv = 0; dv < height; dv++) {
|
|
329
|
+
for (let du = 0; du < width; du++) {
|
|
330
|
+
visited[lookup(uvKeyOf(u0 + du, v0 + dv))] = 1;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
addRect(bucket, p, u0, v0, u0 + width, v0 + height);
|
|
334
|
+
}
|
|
335
|
+
groupStart = groupEnd;
|
|
336
|
+
}
|
|
337
|
+
const globalPoint = (axis, p, u, v) => {
|
|
338
|
+
if (axis === 0) {
|
|
339
|
+
return [p, u, v];
|
|
340
|
+
}
|
|
341
|
+
if (axis === 1) {
|
|
342
|
+
return [u, p, v];
|
|
343
|
+
}
|
|
344
|
+
return [u, v, p];
|
|
345
|
+
};
|
|
346
|
+
const lineKey = (varAxis, x, y, z) => {
|
|
347
|
+
if (varAxis === 0) {
|
|
348
|
+
return (y + z * coordStride) * 3;
|
|
349
|
+
}
|
|
350
|
+
if (varAxis === 1) {
|
|
351
|
+
return (x + z * coordStride) * 3 + 1;
|
|
352
|
+
}
|
|
353
|
+
return (x + y * coordStride) * 3 + 2;
|
|
354
|
+
};
|
|
355
|
+
const linePoints = new Map();
|
|
356
|
+
const addLinePoint = (key, value) => {
|
|
357
|
+
let points = linePoints.get(key);
|
|
358
|
+
if (!points) {
|
|
359
|
+
points = [];
|
|
360
|
+
linePoints.set(key, points);
|
|
361
|
+
}
|
|
362
|
+
points.push(value);
|
|
363
|
+
};
|
|
364
|
+
const addLineSegment = (x0, y0, z0, x1, y1, z1) => {
|
|
365
|
+
if (x0 !== x1) {
|
|
366
|
+
const key = lineKey(0, x0, y0, z0);
|
|
367
|
+
addLinePoint(key, x0);
|
|
368
|
+
addLinePoint(key, x1);
|
|
369
|
+
}
|
|
370
|
+
else if (y0 !== y1) {
|
|
371
|
+
const key = lineKey(1, x0, y0, z0);
|
|
372
|
+
addLinePoint(key, y0);
|
|
373
|
+
addLinePoint(key, y1);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
const key = lineKey(2, x0, y0, z0);
|
|
377
|
+
addLinePoint(key, z0);
|
|
378
|
+
addLinePoint(key, z1);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
for (let r = 0; r < rectLen; r++) {
|
|
382
|
+
const axis = rectBucket[r] >> 1;
|
|
383
|
+
const p = rectP[r];
|
|
384
|
+
const a = globalPoint(axis, p, rectU0[r], rectV0[r]);
|
|
385
|
+
const b = globalPoint(axis, p, rectU1[r], rectV0[r]);
|
|
386
|
+
const c = globalPoint(axis, p, rectU1[r], rectV1[r]);
|
|
387
|
+
const d = globalPoint(axis, p, rectU0[r], rectV1[r]);
|
|
388
|
+
addLineSegment(a[0], a[1], a[2], b[0], b[1], b[2]);
|
|
389
|
+
addLineSegment(b[0], b[1], b[2], c[0], c[1], c[2]);
|
|
390
|
+
addLineSegment(c[0], c[1], c[2], d[0], d[1], d[2]);
|
|
391
|
+
addLineSegment(d[0], d[1], d[2], a[0], a[1], a[2]);
|
|
392
|
+
}
|
|
393
|
+
for (const points of linePoints.values()) {
|
|
394
|
+
points.sort((a, b) => a - b);
|
|
395
|
+
let write = 0;
|
|
396
|
+
for (let i = 0; i < points.length; i++) {
|
|
397
|
+
if (i === 0 || points[i] !== points[i - 1]) {
|
|
398
|
+
points[write++] = points[i];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
points.length = write;
|
|
402
|
+
}
|
|
403
|
+
let posCap = 1024;
|
|
404
|
+
let posLen = 0;
|
|
405
|
+
let positions = new Float32Array(posCap);
|
|
406
|
+
let idxCap = 1024;
|
|
407
|
+
let idxLen = 0;
|
|
408
|
+
let indices = new Uint32Array(idxCap);
|
|
409
|
+
const vertexMap = new Map();
|
|
410
|
+
let perimeterScratch = new Uint32Array(16);
|
|
411
|
+
let perimeterU = new Int32Array(16);
|
|
412
|
+
let perimeterV = new Int32Array(16);
|
|
413
|
+
let perimeterLen = 0;
|
|
414
|
+
let triPrev = new Int32Array(16);
|
|
415
|
+
let triNext = new Int32Array(16);
|
|
416
|
+
const addPosition = (x, y, z) => {
|
|
417
|
+
if (posLen + 3 > posCap) {
|
|
418
|
+
posCap *= 2;
|
|
419
|
+
const grown = new Float32Array(posCap);
|
|
420
|
+
grown.set(positions);
|
|
421
|
+
positions = grown;
|
|
422
|
+
}
|
|
423
|
+
const idx = posLen / 3;
|
|
424
|
+
positions[posLen++] = gridBounds.min.x + x * voxelResolution;
|
|
425
|
+
positions[posLen++] = gridBounds.min.y + y * voxelResolution;
|
|
426
|
+
positions[posLen++] = gridBounds.min.z + z * voxelResolution;
|
|
427
|
+
return idx;
|
|
428
|
+
};
|
|
429
|
+
const vertexKey = (x, y, z) => {
|
|
430
|
+
return (x + y * coordStride + z * coordStride * coordStride);
|
|
431
|
+
};
|
|
432
|
+
const getVertex = (x, y, z) => {
|
|
433
|
+
const key = vertexKey(x, y, z);
|
|
434
|
+
const existing = vertexMap.get(key);
|
|
435
|
+
if (existing !== undefined) {
|
|
436
|
+
return existing;
|
|
437
|
+
}
|
|
438
|
+
const idx = addPosition(x, y, z);
|
|
439
|
+
vertexMap.set(key, idx);
|
|
440
|
+
return idx;
|
|
441
|
+
};
|
|
442
|
+
const ensureIndexCapacity = (additional) => {
|
|
443
|
+
if (idxLen + additional <= idxCap) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
while (idxLen + additional > idxCap) {
|
|
447
|
+
idxCap *= 2;
|
|
448
|
+
}
|
|
449
|
+
const grown = new Uint32Array(idxCap);
|
|
450
|
+
grown.set(indices);
|
|
451
|
+
indices = grown;
|
|
452
|
+
};
|
|
453
|
+
const appendTri = (a, b, c) => {
|
|
454
|
+
ensureIndexCapacity(3);
|
|
455
|
+
indices[idxLen++] = a;
|
|
456
|
+
indices[idxLen++] = b;
|
|
457
|
+
indices[idxLen++] = c;
|
|
458
|
+
};
|
|
459
|
+
const resetPerimeter = () => {
|
|
460
|
+
perimeterLen = 0;
|
|
461
|
+
};
|
|
462
|
+
const localUv = (axis, x, y, z) => {
|
|
463
|
+
if (axis === 0) {
|
|
464
|
+
return [y, z];
|
|
465
|
+
}
|
|
466
|
+
if (axis === 1) {
|
|
467
|
+
return [x, z];
|
|
468
|
+
}
|
|
469
|
+
return [x, y];
|
|
470
|
+
};
|
|
471
|
+
const addPerimeterVertex = (v, u, pv) => {
|
|
472
|
+
if (perimeterLen > 0 && perimeterScratch[perimeterLen - 1] === v) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (perimeterLen === perimeterScratch.length) {
|
|
476
|
+
const grown = new Uint32Array(perimeterScratch.length * 2);
|
|
477
|
+
grown.set(perimeterScratch);
|
|
478
|
+
perimeterScratch = grown;
|
|
479
|
+
const grownU = new Int32Array(perimeterU.length * 2);
|
|
480
|
+
grownU.set(perimeterU);
|
|
481
|
+
perimeterU = grownU;
|
|
482
|
+
const grownV = new Int32Array(perimeterV.length * 2);
|
|
483
|
+
grownV.set(perimeterV);
|
|
484
|
+
perimeterV = grownV;
|
|
485
|
+
}
|
|
486
|
+
perimeterScratch[perimeterLen++] = v;
|
|
487
|
+
perimeterU[perimeterLen - 1] = u;
|
|
488
|
+
perimeterV[perimeterLen - 1] = pv;
|
|
489
|
+
};
|
|
490
|
+
const addEdgeVertices = (axis, x0, y0, z0, x1, y1, z1) => {
|
|
491
|
+
let varAxis;
|
|
492
|
+
let start;
|
|
493
|
+
let end;
|
|
494
|
+
if (x0 !== x1) {
|
|
495
|
+
varAxis = 0;
|
|
496
|
+
start = x0;
|
|
497
|
+
end = x1;
|
|
498
|
+
}
|
|
499
|
+
else if (y0 !== y1) {
|
|
500
|
+
varAxis = 1;
|
|
501
|
+
start = y0;
|
|
502
|
+
end = y1;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
varAxis = 2;
|
|
506
|
+
start = z0;
|
|
507
|
+
end = z1;
|
|
508
|
+
}
|
|
509
|
+
const key = lineKey(varAxis, x0, y0, z0);
|
|
510
|
+
const points = linePoints.get(key);
|
|
511
|
+
if (!points) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const lo = Math.min(start, end);
|
|
515
|
+
const hi = Math.max(start, end);
|
|
516
|
+
const forward = start <= end;
|
|
517
|
+
const emitPoint = (x, y, z) => {
|
|
518
|
+
const [u, v] = localUv(axis, x, y, z);
|
|
519
|
+
addPerimeterVertex(getVertex(x, y, z), u, v);
|
|
520
|
+
};
|
|
521
|
+
if (forward) {
|
|
522
|
+
for (let i = 0; i < points.length; i++) {
|
|
523
|
+
const t = points[i];
|
|
524
|
+
if (t < lo) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (t > hi) {
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
if (varAxis === 0) {
|
|
531
|
+
emitPoint(t, y0, z0);
|
|
532
|
+
}
|
|
533
|
+
else if (varAxis === 1) {
|
|
534
|
+
emitPoint(x0, t, z0);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
emitPoint(x0, y0, t);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
543
|
+
const t = points[i];
|
|
544
|
+
if (t > hi) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (t < lo) {
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
if (varAxis === 0) {
|
|
551
|
+
emitPoint(t, y0, z0);
|
|
552
|
+
}
|
|
553
|
+
else if (varAxis === 1) {
|
|
554
|
+
emitPoint(x0, t, z0);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
emitPoint(x0, y0, t);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const isConvexEar = (prev, curr, next) => {
|
|
563
|
+
const ax = perimeterU[curr] - perimeterU[prev];
|
|
564
|
+
const ay = perimeterV[curr] - perimeterV[prev];
|
|
565
|
+
const bx = perimeterU[next] - perimeterU[prev];
|
|
566
|
+
const by = perimeterV[next] - perimeterV[prev];
|
|
567
|
+
return ax * by - ay * bx > 0;
|
|
568
|
+
};
|
|
569
|
+
const isNonDegenerateTri = (a, b, c) => {
|
|
570
|
+
const ax = perimeterU[b] - perimeterU[a];
|
|
571
|
+
const ay = perimeterV[b] - perimeterV[a];
|
|
572
|
+
const bx = perimeterU[c] - perimeterU[a];
|
|
573
|
+
const by = perimeterV[c] - perimeterV[a];
|
|
574
|
+
return ax * by - ay * bx > 0;
|
|
575
|
+
};
|
|
576
|
+
const appendOrientedTri = (a, b, c, useLocalCcw) => {
|
|
577
|
+
if (useLocalCcw) {
|
|
578
|
+
appendTri(a, b, c);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
appendTri(a, c, b);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
const triangulatePerimeter = (useLocalCcw) => {
|
|
585
|
+
if (perimeterLen < 3) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (perimeterLen > triPrev.length) {
|
|
589
|
+
let cap = triPrev.length;
|
|
590
|
+
while (perimeterLen > cap) {
|
|
591
|
+
cap *= 2;
|
|
592
|
+
}
|
|
593
|
+
triPrev = new Int32Array(cap);
|
|
594
|
+
triNext = new Int32Array(cap);
|
|
595
|
+
}
|
|
596
|
+
for (let i = 0; i < perimeterLen; i++) {
|
|
597
|
+
triPrev[i] = i === 0 ? perimeterLen - 1 : i - 1;
|
|
598
|
+
triNext[i] = i === perimeterLen - 1 ? 0 : i + 1;
|
|
599
|
+
}
|
|
600
|
+
let remaining = perimeterLen;
|
|
601
|
+
let current = 0;
|
|
602
|
+
let attempts = 0;
|
|
603
|
+
while (remaining > 3 && attempts < remaining) {
|
|
604
|
+
const prev = triPrev[current];
|
|
605
|
+
const next = triNext[current];
|
|
606
|
+
const next2 = triNext[next];
|
|
607
|
+
const keepsArea = remaining !== 4 || isNonDegenerateTri(prev, next, next2);
|
|
608
|
+
if (keepsArea && isConvexEar(prev, current, next)) {
|
|
609
|
+
appendOrientedTri(perimeterScratch[prev], perimeterScratch[current], perimeterScratch[next], useLocalCcw);
|
|
610
|
+
triNext[prev] = next;
|
|
611
|
+
triPrev[next] = prev;
|
|
612
|
+
current = next;
|
|
613
|
+
remaining--;
|
|
614
|
+
attempts = 0;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
current = next;
|
|
618
|
+
attempts++;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (remaining === 3) {
|
|
622
|
+
const a = current;
|
|
623
|
+
const b = triNext[a];
|
|
624
|
+
const c = triNext[b];
|
|
625
|
+
if (isConvexEar(a, b, c)) {
|
|
626
|
+
appendOrientedTri(perimeterScratch[a], perimeterScratch[b], perimeterScratch[c], useLocalCcw);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
for (let r = 0; r < rectLen; r++) {
|
|
631
|
+
const bucket = rectBucket[r];
|
|
632
|
+
const axis = bucket >> 1;
|
|
633
|
+
const positive = (bucket & 1) === 1;
|
|
634
|
+
const p = rectP[r];
|
|
635
|
+
const u0 = rectU0[r];
|
|
636
|
+
const v0 = rectV0[r];
|
|
637
|
+
const u1 = rectU1[r];
|
|
638
|
+
const v1 = rectV1[r];
|
|
639
|
+
const a = globalPoint(axis, p, u0, v0);
|
|
640
|
+
const b = globalPoint(axis, p, u1, v0);
|
|
641
|
+
const c = globalPoint(axis, p, u1, v1);
|
|
642
|
+
const d = globalPoint(axis, p, u0, v1);
|
|
643
|
+
resetPerimeter();
|
|
644
|
+
addEdgeVertices(axis, a[0], a[1], a[2], b[0], b[1], b[2]);
|
|
645
|
+
addEdgeVertices(axis, b[0], b[1], b[2], c[0], c[1], c[2]);
|
|
646
|
+
addEdgeVertices(axis, c[0], c[1], c[2], d[0], d[1], d[2]);
|
|
647
|
+
addEdgeVertices(axis, d[0], d[1], d[2], a[0], a[1], a[2]);
|
|
648
|
+
if (perimeterLen > 1 && perimeterScratch[0] === perimeterScratch[perimeterLen - 1]) {
|
|
649
|
+
perimeterLen--;
|
|
650
|
+
}
|
|
651
|
+
if (perimeterLen < 3) {
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const localCcwIsPositive = axis !== 1;
|
|
655
|
+
const useLocalCcw = positive === localCcwIsPositive;
|
|
656
|
+
triangulatePerimeter(useLocalCcw);
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
positions: positions.slice(0, posLen),
|
|
660
|
+
indices: indices.slice(0, idxLen)
|
|
661
|
+
};
|
|
662
|
+
};
|
|
663
|
+
export { voxelFaces };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BlockMaskBuffer } from './common.js';
|
|
2
|
+
export declare const cpuVoxelize: (xCol: Float32Array, yCol: Float32Array, zCol: Float32Array, sxCol: Float32Array, syCol: Float32Array, szCol: Float32Array, qxCol: Float32Array, qyCol: Float32Array, qzCol: Float32Array, qwCol: Float32Array, aCol: Float32Array, extents: Float32Array, gridBounds: {
|
|
3
|
+
min: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
z: number;
|
|
7
|
+
};
|
|
8
|
+
max: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z: number;
|
|
12
|
+
};
|
|
13
|
+
}, voxelResolution: number, opacityCutoff: number, options?: {
|
|
14
|
+
workerCount?: number;
|
|
15
|
+
}) => Promise<BlockMaskBuffer>;
|
|
16
|
+
/**
|
|
17
|
+
* GPU voxelization path using tiled multi-batch WGSL dispatch.
|
|
18
|
+
* Per-batch Gaussian indices are built on the GPU (count pass, CPU prefix sum, fill pass) into `indexBuffer`,
|
|
19
|
+
* replacing BVH `queryOverlappingRaw` on reference implementation. Batches are packed into mega-dispatches, then read back
|
|
20
|
+
* as per-block 64-bit masks to populate `BlockMaskBuffer`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const gpuVoxelize: (xCol: Float32Array, yCol: Float32Array, zCol: Float32Array, sxCol: Float32Array, syCol: Float32Array, szCol: Float32Array, qxCol: Float32Array, qyCol: Float32Array, qzCol: Float32Array, qwCol: Float32Array, aCol: Float32Array, extents: Float32Array, gridBounds: {
|
|
23
|
+
min: {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
z: number;
|
|
27
|
+
};
|
|
28
|
+
max: {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
z: number;
|
|
32
|
+
};
|
|
33
|
+
}, voxelResolution: number, opacityCutoff: number) => Promise<BlockMaskBuffer>;
|