gaussian-splat-lite 1.5.0 → 1.5.2
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 +29 -9
- package/bin/build-lod.js +14 -3
- package/bin/decoder/cluster.wasm +0 -0
- package/bin/decoder/decoder.js +5 -4
- package/bin/decoder/decoder_bg.wasm +0 -0
- package/bin/rad/cluster.js +47 -14
- package/bin/rad/heap.js +1 -2
- package/bin/rad/lod.js +48 -67
- package/bin/rad/rad.js +50 -28
- package/bin/rad/splats.js +129 -37
- package/bin/rad/storage.js +147 -34
- package/bin/rad/writeback.js +100 -0
- package/dist/gaussian-splat-lite.cjs +37 -3
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +127 -93
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +37 -3
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +972 -938
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/docs/BuildRad.md +2 -2
- package/docs/RadStreamScheduler.md +3 -3
- package/docs/SogStreamScheduler.md +1 -1
- package/docs/SplatLoader.md +1 -0
- package/package.json +1 -1
package/bin/rad/rad.js
CHANGED
|
@@ -16,7 +16,7 @@ import { HALF, half, halfBits, opacityRadius } from "./math.js";
|
|
|
16
16
|
import { WorkerGroup, encoders } from "./parallel.js";
|
|
17
17
|
import { Progress } from "./progress.js";
|
|
18
18
|
import { SH_DIMS } from "./splats.js";
|
|
19
|
-
import { readAll, removePath
|
|
19
|
+
import { PAGE_BYTES, readAll, removePath } from "./storage.js";
|
|
20
20
|
|
|
21
21
|
const CHUNK = 65536;
|
|
22
22
|
// Fixed s8 range with SPZ's 1/128 SH step; -1 clips to -127/128.
|
|
@@ -158,27 +158,34 @@ const unorm = (value, min, max) =>
|
|
|
158
158
|
Math.round(Math.max(0, Math.min(255, ((value - min) / (max - min)) * 255)));
|
|
159
159
|
|
|
160
160
|
// The columns encodeChunk() reads. With a codebook, labels replace the SH.
|
|
161
|
-
|
|
161
|
+
// The records before `kept` come through the cache, where counting left
|
|
162
|
+
// them; the rest of them and the other columns, read once, pass it by.
|
|
163
|
+
function gather(splats, base, count, codebook, kept) {
|
|
162
164
|
return {
|
|
163
165
|
base,
|
|
164
166
|
count,
|
|
165
167
|
maxSh: splats.maxSh,
|
|
166
|
-
records: splats.records.
|
|
168
|
+
records: splats.records.scan(
|
|
169
|
+
base * 16,
|
|
170
|
+
new Float32Array(count * 16),
|
|
171
|
+
count * 16,
|
|
172
|
+
kept * 16,
|
|
173
|
+
),
|
|
167
174
|
sh: codebook
|
|
168
175
|
? []
|
|
169
176
|
: splats.sh
|
|
170
177
|
.slice(0, splats.maxSh)
|
|
171
178
|
.map((column, band) =>
|
|
172
|
-
column.
|
|
179
|
+
column.stream(
|
|
173
180
|
base * SH_DIMS[band],
|
|
174
181
|
new Uint16Array(count * SH_DIMS[band]),
|
|
175
182
|
),
|
|
176
183
|
),
|
|
177
184
|
...(codebook && {
|
|
178
|
-
labels: codebook.labels.
|
|
185
|
+
labels: codebook.labels.stream(base, new Uint16Array(count)),
|
|
179
186
|
}),
|
|
180
|
-
counts: splats.childCount.
|
|
181
|
-
starts: splats.childStart.
|
|
187
|
+
counts: splats.childCount.stream(base, new Uint32Array(count)),
|
|
188
|
+
starts: splats.childStart.stream(base, new Float64Array(count)),
|
|
182
189
|
};
|
|
183
190
|
}
|
|
184
191
|
|
|
@@ -447,15 +454,16 @@ if (!isMainThread && workerData?.coder) {
|
|
|
447
454
|
|
|
448
455
|
// With a codebook from clusterSh(), splats store SH as its labels.
|
|
449
456
|
export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
457
|
+
const { workspace } = splats;
|
|
450
458
|
const chunks = [];
|
|
451
459
|
const stem = chunkStem(output);
|
|
452
|
-
const spool = chunked ? undefined :
|
|
460
|
+
const spool = chunked ? undefined : workspace.file();
|
|
453
461
|
// Write every file completely before publishing chunks, then the header.
|
|
454
462
|
// Track published names too, so cancellation rolls back this conversion.
|
|
455
463
|
const staged = [];
|
|
456
464
|
const created = [];
|
|
457
465
|
const stage = (file, write) => {
|
|
458
|
-
|
|
466
|
+
workspace.check();
|
|
459
467
|
const temporary = path.join(
|
|
460
468
|
path.dirname(file),
|
|
461
469
|
`.${path.basename(file)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`,
|
|
@@ -464,14 +472,15 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
464
472
|
staged.push({ temporary, file });
|
|
465
473
|
try {
|
|
466
474
|
write(fd);
|
|
475
|
+
workspace.check();
|
|
467
476
|
fsyncSync(fd);
|
|
468
|
-
}
|
|
477
|
+
} finally {
|
|
469
478
|
try {
|
|
479
|
+
workspace.writeback.synced(fd);
|
|
480
|
+
} finally {
|
|
470
481
|
closeSync(fd);
|
|
471
|
-
}
|
|
472
|
-
throw error;
|
|
482
|
+
}
|
|
473
483
|
}
|
|
474
|
-
closeSync(fd);
|
|
475
484
|
};
|
|
476
485
|
let total = 0;
|
|
477
486
|
let progress;
|
|
@@ -479,11 +488,11 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
479
488
|
if (chunked) {
|
|
480
489
|
const filename = `${stem}-${chunks.length}.radc`;
|
|
481
490
|
stage(path.join(path.dirname(output), filename), (fd) =>
|
|
482
|
-
|
|
491
|
+
workspace.write(fd, bytes, 0),
|
|
483
492
|
);
|
|
484
493
|
chunks.push({ offset: 0, bytes: bytes.length, filename });
|
|
485
494
|
} else {
|
|
486
|
-
|
|
495
|
+
workspace.write(spool, bytes, total);
|
|
487
496
|
chunks.push({ offset: total, bytes: bytes.length });
|
|
488
497
|
}
|
|
489
498
|
total += bytes.length;
|
|
@@ -493,21 +502,29 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
493
502
|
// order. A chunk's failure is thrown when its turn comes, not as an
|
|
494
503
|
// unhandled rejection while an earlier chunk is awaited.
|
|
495
504
|
const pending = [];
|
|
496
|
-
const encoder = new Encoder(
|
|
497
|
-
splats.workspace,
|
|
498
|
-
Math.ceil(splats.length / CHUNK),
|
|
499
|
-
);
|
|
505
|
+
const encoder = new Encoder(workspace, Math.ceil(splats.length / CHUNK));
|
|
500
506
|
try {
|
|
501
|
-
// Count the encoding ranges in a first pass.
|
|
507
|
+
// Count the encoding ranges in a first pass. The records that the cache
|
|
508
|
+
// holds stay there for encoding; see gather(). Recompute the prefix
|
|
509
|
+
// after measuring worker growth so it fits a cache that has shrunk.
|
|
510
|
+
const keep = () => {
|
|
511
|
+
workspace.adjust();
|
|
512
|
+
return Math.floor(Math.max(0, workspace.capacity - PAGE_BYTES) / 64);
|
|
513
|
+
};
|
|
502
514
|
const chunkCount = Math.ceil(splats.length / CHUNK);
|
|
503
515
|
progress = new Progress("Analyzing RAD encoding", chunkCount);
|
|
504
516
|
let countedChunks = 0;
|
|
505
517
|
for (let base = 0; base < splats.length; base += CHUNK) {
|
|
506
|
-
|
|
518
|
+
workspace.check();
|
|
519
|
+
// The encoders grow as they work, and reading past the cache loads
|
|
520
|
+
// no pages, which would measure them.
|
|
521
|
+
const kept = keep();
|
|
507
522
|
const count = Math.min(CHUNK, splats.length - base);
|
|
508
|
-
const records = splats.records.
|
|
523
|
+
const records = splats.records.scan(
|
|
509
524
|
base * 16,
|
|
510
525
|
new Float32Array(count * 16),
|
|
526
|
+
count * 16,
|
|
527
|
+
kept * 16,
|
|
511
528
|
);
|
|
512
529
|
const counted = encoder
|
|
513
530
|
.count(records)
|
|
@@ -522,12 +539,14 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
522
539
|
progress.finish();
|
|
523
540
|
progress = new Progress("Encoding RAD chunks", chunkCount);
|
|
524
541
|
for (let base = 0; base < splats.length; base += CHUNK) {
|
|
525
|
-
|
|
542
|
+
workspace.check();
|
|
543
|
+
const kept = keep();
|
|
526
544
|
const chunk = gather(
|
|
527
545
|
splats,
|
|
528
546
|
base,
|
|
529
547
|
Math.min(CHUNK, splats.length - base),
|
|
530
548
|
codebook,
|
|
549
|
+
kept,
|
|
531
550
|
);
|
|
532
551
|
if (codebook && base === 0)
|
|
533
552
|
chunk.codes = encodeCodes(codebook, splats.maxSh);
|
|
@@ -556,25 +575,28 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
556
575
|
meta.length + (spool === undefined ? 0 : total),
|
|
557
576
|
);
|
|
558
577
|
stage(output, (fd) => {
|
|
559
|
-
|
|
578
|
+
workspace.write(fd, meta, 0);
|
|
560
579
|
if (spool !== undefined) {
|
|
561
580
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
562
581
|
for (let offset = 0; offset < total; offset += buffer.length) {
|
|
563
|
-
|
|
582
|
+
workspace.check();
|
|
564
583
|
const bytes = buffer.subarray(
|
|
565
584
|
0,
|
|
566
585
|
Math.min(buffer.length, total - offset),
|
|
567
586
|
);
|
|
568
587
|
readAll(spool, bytes, offset);
|
|
569
|
-
|
|
588
|
+
workspace.write(fd, bytes, meta.length + offset);
|
|
570
589
|
writing.update(meta.length + offset + bytes.length);
|
|
571
590
|
}
|
|
572
591
|
}
|
|
573
592
|
});
|
|
574
593
|
writing.finish();
|
|
594
|
+
// Closing an output waits only for a sync that includes it; a temporary
|
|
595
|
+
// file's failed writeback must surface before anything is published.
|
|
596
|
+
workspace.writeback.wait();
|
|
575
597
|
const publishing = new Progress("Publishing RAD files", staged.length);
|
|
576
598
|
for (const { temporary, file } of staged) {
|
|
577
|
-
|
|
599
|
+
workspace.check();
|
|
578
600
|
// rename can replace an existing file, so reserve the name exclusively.
|
|
579
601
|
const fd = openSync(file, "wx");
|
|
580
602
|
created.push(file);
|
|
@@ -582,7 +604,7 @@ export async function writeRad(splats, output, chunked, lodBase, codebook) {
|
|
|
582
604
|
renameSync(temporary, file);
|
|
583
605
|
publishing.update(created.length);
|
|
584
606
|
}
|
|
585
|
-
|
|
607
|
+
workspace.check();
|
|
586
608
|
publishing.finish();
|
|
587
609
|
return meta.length + total;
|
|
588
610
|
} catch (error) {
|
package/bin/rad/splats.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
quaternion,
|
|
13
13
|
} from "./math.js";
|
|
14
14
|
import { Progress } from "./progress.js";
|
|
15
|
-
import { IndexArray, PagedArray, Permutation } from "./storage.js";
|
|
15
|
+
import { IndexArray, PAGE_BYTES, PagedArray, Permutation } from "./storage.js";
|
|
16
16
|
|
|
17
17
|
export const SH_DIMS = [9, 15, 21];
|
|
18
18
|
|
|
@@ -75,8 +75,15 @@ export class Splats {
|
|
|
75
75
|
// Each splat's merge weight, area × opacity, which merge() reads instead
|
|
76
76
|
// of two records.
|
|
77
77
|
this.weight = new PagedArray(workspace, Float64Array);
|
|
78
|
+
// Each splat's feature size, for the passes that read nothing else of a
|
|
79
|
+
// record, and the bounds of the centers; see reorder().
|
|
80
|
+
this.sizes = new PagedArray(workspace, Float32Array);
|
|
81
|
+
this.bounds = undefined;
|
|
78
82
|
// The two child weights of each merged splat, for mergeSh().
|
|
79
83
|
this.blend = new PagedArray(workspace, Float64Array);
|
|
84
|
+
// Each decoded splat's row in the SH columns: as decoded, and from
|
|
85
|
+
// mergeSh() on, in the order merges read them; see shRow().
|
|
86
|
+
this.shOrder = undefined;
|
|
80
87
|
this.merged = new Float64Array(16);
|
|
81
88
|
this.bits = new Uint16Array(0);
|
|
82
89
|
this.merger = new Merger();
|
|
@@ -133,9 +140,6 @@ export class Splats {
|
|
|
133
140
|
read(index, output) {
|
|
134
141
|
return this.records.read(index * 16, output, 16);
|
|
135
142
|
}
|
|
136
|
-
feature(index) {
|
|
137
|
-
return this.records.get(index * 16 + 14);
|
|
138
|
-
}
|
|
139
143
|
|
|
140
144
|
// Skips empty splats and counts invalid ones in `invalid`: NaN or infinite
|
|
141
145
|
// values, including a zero rotation, which the decoder normalizes to NaN.
|
|
@@ -192,6 +196,8 @@ export class Splats {
|
|
|
192
196
|
this.sh[band].write(count * dims, rows[band]);
|
|
193
197
|
}
|
|
194
198
|
}
|
|
199
|
+
// The record's page is cached.
|
|
200
|
+
this.sizes.set(count, this.records.get(count * 16 + 14));
|
|
195
201
|
count++;
|
|
196
202
|
}
|
|
197
203
|
progress.update(base + size);
|
|
@@ -226,6 +232,7 @@ export class Splats {
|
|
|
226
232
|
const index = this.length++;
|
|
227
233
|
this.records.write(index * 16, out);
|
|
228
234
|
this.weight.set(index, out[15] * out[3]);
|
|
235
|
+
this.sizes.set(index, out[14]);
|
|
229
236
|
this.children.push(a);
|
|
230
237
|
this.children.push(b);
|
|
231
238
|
// SH coefficients do not affect merging; mergeSh() blends them later.
|
|
@@ -236,46 +243,105 @@ export class Splats {
|
|
|
236
243
|
return index;
|
|
237
244
|
}
|
|
238
245
|
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
// rest wait for their children in the batch.
|
|
246
|
+
// Blend each SH band in creation order, children before parents. Reorder
|
|
247
|
+
// leaves for sequential reads; only merged children need random reads.
|
|
248
|
+
// Batches use at most half the cache for child pages. Workers handle
|
|
249
|
+
// earlier children; children within the batch are blended below in order.
|
|
244
250
|
mergeSh(initial, pool) {
|
|
251
|
+
if (!this.maxSh) return;
|
|
245
252
|
const { nodes, entries, later, blends, blended, results } = pool;
|
|
253
|
+
// Each leaf occurs once, as a merge child or the lone root. Replace its
|
|
254
|
+
// decoded SH row with its position in merge-read order.
|
|
255
|
+
const reads = new IndexArray(this.workspace);
|
|
256
|
+
const collecting = new Progress("Collecting SH order", this.length);
|
|
257
|
+
for (let k = 0; k < (this.length - initial) * 2; k++) {
|
|
258
|
+
const child = this.children.get(k);
|
|
259
|
+
if (child < initial) {
|
|
260
|
+
reads.push(this.shOrder.get(child));
|
|
261
|
+
this.shOrder.set(child, reads.length - 1);
|
|
262
|
+
}
|
|
263
|
+
if ((k & 4095) === 0) collecting.update(k / 2);
|
|
264
|
+
}
|
|
265
|
+
if (this.length === initial) {
|
|
266
|
+
reads.push(this.shOrder.get(0));
|
|
267
|
+
this.shOrder.set(0, 0);
|
|
268
|
+
}
|
|
269
|
+
collecting.finish();
|
|
270
|
+
const leafRows = new Permutation(
|
|
271
|
+
this.workspace,
|
|
272
|
+
reads,
|
|
273
|
+
initial,
|
|
274
|
+
"Preparing SH order",
|
|
275
|
+
);
|
|
276
|
+
reads.dispose();
|
|
246
277
|
// Splats per batch.
|
|
247
278
|
const size = later.length;
|
|
248
279
|
const rows = [this.shLeft, this.shRight];
|
|
249
280
|
for (let band = 0; band < this.maxSh; band++) {
|
|
281
|
+
const dims = SH_DIMS[band];
|
|
282
|
+
const column = leafRows.apply(
|
|
283
|
+
this.sh[band],
|
|
284
|
+
dims,
|
|
285
|
+
pool,
|
|
286
|
+
false,
|
|
287
|
+
`Reordering SH${band + 1}`,
|
|
288
|
+
);
|
|
289
|
+
this.sh[band] = column;
|
|
250
290
|
const progress = new Progress(
|
|
251
291
|
`Blending SH${band + 1}`,
|
|
252
292
|
this.length - initial,
|
|
253
293
|
);
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
294
|
+
// The batch that last counted each page of the column.
|
|
295
|
+
const counted = new Float64Array(
|
|
296
|
+
Math.ceil((this.length * dims) / column.items),
|
|
297
|
+
);
|
|
298
|
+
let batch = 0;
|
|
299
|
+
// The row of the next decoded splat that a merge reads.
|
|
300
|
+
let next = 0;
|
|
301
|
+
for (let start = initial; start < this.length; ) {
|
|
302
|
+
const limit = Math.min(size, this.length - start);
|
|
303
|
+
const pages = Math.max(
|
|
304
|
+
1,
|
|
305
|
+
Math.floor(this.workspace.capacity / PAGE_BYTES / 2),
|
|
306
|
+
);
|
|
307
|
+
batch++;
|
|
308
|
+
let taken = 0;
|
|
309
|
+
let count = 0;
|
|
258
310
|
// Load the pages of the children's rows for the workers. Consecutive
|
|
259
311
|
// splats' children often share pages; a page evicted meanwhile is a
|
|
260
312
|
// miss that the main thread blends.
|
|
261
313
|
let left = -1;
|
|
262
314
|
let right = -1;
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
315
|
+
while (count < limit) {
|
|
316
|
+
const k = (start + count - initial) * 2;
|
|
317
|
+
const a = this.children.get(k);
|
|
318
|
+
const b = this.children.get(k + 1);
|
|
319
|
+
const ra = a < initial ? next : a;
|
|
320
|
+
const rb = b < initial ? next + Number(a < initial) : b;
|
|
321
|
+
const pa = Math.floor((ra * dims) / column.items);
|
|
322
|
+
const pb = Math.floor((rb * dims) / column.items);
|
|
323
|
+
let added = Number(ra < start && counted[pa] !== batch);
|
|
324
|
+
if (rb < start && counted[pb] !== batch && !(added && pb === pa))
|
|
325
|
+
added++;
|
|
326
|
+
if (count && taken + added > pages) break;
|
|
327
|
+
taken += added;
|
|
328
|
+
if (ra < start) counted[pa] = batch;
|
|
329
|
+
if (rb < start) counted[pb] = batch;
|
|
330
|
+
next += Number(a < initial) + Number(b < initial);
|
|
331
|
+
const i = count++;
|
|
332
|
+
nodes[i] = ra;
|
|
333
|
+
entries[i] = rb;
|
|
334
|
+
blends[i * 2] = this.blend.get(k);
|
|
335
|
+
blends[i * 2 + 1] = this.blend.get(k + 1);
|
|
336
|
+
later[i] = Number(ra >= start || rb >= start);
|
|
271
337
|
if (later[i]) continue;
|
|
272
|
-
if (
|
|
273
|
-
left =
|
|
274
|
-
column.page(
|
|
338
|
+
if (pa !== left) {
|
|
339
|
+
left = pa;
|
|
340
|
+
column.page(ra * dims);
|
|
275
341
|
}
|
|
276
|
-
if (
|
|
277
|
-
right =
|
|
278
|
-
column.page(
|
|
342
|
+
if (pb !== right) {
|
|
343
|
+
right = pb;
|
|
344
|
+
column.page(rb * dims);
|
|
279
345
|
}
|
|
280
346
|
}
|
|
281
347
|
pool.blend(column, count, dims);
|
|
@@ -297,14 +363,19 @@ export class Splats {
|
|
|
297
363
|
);
|
|
298
364
|
}
|
|
299
365
|
column.write(start * dims, blended, count * dims);
|
|
300
|
-
|
|
366
|
+
start += count;
|
|
367
|
+
progress.update(start - initial);
|
|
301
368
|
}
|
|
302
369
|
progress.finish();
|
|
303
370
|
}
|
|
371
|
+
leafRows.dispose();
|
|
304
372
|
this.blend.dispose();
|
|
305
373
|
}
|
|
306
374
|
|
|
307
|
-
// Moves row indices[i] of the records
|
|
375
|
+
// Moves row indices[i] of the records to row i, and keeps `indices` for
|
|
376
|
+
// the SH rows, which move once merging has built the tree; see mergeSh().
|
|
377
|
+
// The splats' sizes and weights, and the bounds of their centers, come
|
|
378
|
+
// from the records while each window of them is cached.
|
|
308
379
|
reorder(indices, pool) {
|
|
309
380
|
const permutation = new Permutation(
|
|
310
381
|
this.workspace,
|
|
@@ -312,23 +383,43 @@ export class Splats {
|
|
|
312
383
|
this.length,
|
|
313
384
|
"Preparing splat order",
|
|
314
385
|
);
|
|
386
|
+
this.sizes.dispose();
|
|
387
|
+
const sizes = this.sizes;
|
|
388
|
+
const bounds = {
|
|
389
|
+
min: [0, 1, 2].map(() => Number.POSITIVE_INFINITY),
|
|
390
|
+
max: [0, 1, 2].map(() => Number.NEGATIVE_INFINITY),
|
|
391
|
+
};
|
|
392
|
+
const record = new Float64Array(16);
|
|
315
393
|
this.records = permutation.apply(
|
|
316
394
|
this.records,
|
|
317
395
|
16,
|
|
318
396
|
pool,
|
|
319
397
|
false,
|
|
320
398
|
"Reordering splats",
|
|
399
|
+
(records, start, end) => {
|
|
400
|
+
for (let i = start; i < end; i++) {
|
|
401
|
+
records.read(i * 16, record, 16);
|
|
402
|
+
for (let d = 0; d < 3; d++) {
|
|
403
|
+
bounds.min[d] = Math.min(bounds.min[d], record[d]);
|
|
404
|
+
bounds.max[d] = Math.max(bounds.max[d], record[d]);
|
|
405
|
+
}
|
|
406
|
+
this.weight.set(i, record[15] * record[3]);
|
|
407
|
+
sizes.set(i, record[14]);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
321
410
|
);
|
|
322
|
-
|
|
323
|
-
this.sh[band] = permutation.apply(
|
|
324
|
-
this.sh[band],
|
|
325
|
-
SH_DIMS[band],
|
|
326
|
-
pool,
|
|
327
|
-
false,
|
|
328
|
-
`Reordering SH${band + 1}`,
|
|
329
|
-
);
|
|
411
|
+
this.bounds = bounds;
|
|
330
412
|
permutation.dispose();
|
|
331
413
|
this.length = indices.length;
|
|
414
|
+
if (this.maxSh) this.shOrder = indices;
|
|
415
|
+
else indices.dispose();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// The row of splat `index` in the SH columns after mergeSh(): merged
|
|
419
|
+
// splats keep theirs, and the decoded ones are in the order merges read
|
|
420
|
+
// them.
|
|
421
|
+
shRow(index) {
|
|
422
|
+
return index < this.shOrder.length ? this.shOrder.get(index) : index;
|
|
332
423
|
}
|
|
333
424
|
|
|
334
425
|
dispose() {
|
|
@@ -339,6 +430,7 @@ export class Splats {
|
|
|
339
430
|
this.childCount,
|
|
340
431
|
this.children,
|
|
341
432
|
this.weight,
|
|
433
|
+
this.sizes,
|
|
342
434
|
this.blend,
|
|
343
435
|
])
|
|
344
436
|
column.dispose();
|