gaussian-splat-lite 1.3.2 → 1.4.0
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 +16 -1
- package/CONTRIBUTING.md +14 -2
- package/NOTICE +11 -3
- package/README.md +2 -1
- package/THIRD_PARTY_LICENSES.md +64 -6
- package/bin/build-lod.js +241 -0
- package/bin/decoder/decoder.js +340 -0
- package/bin/decoder/decoder_bg.wasm +0 -0
- package/bin/rad/grid.js +620 -0
- package/bin/rad/heap.js +270 -0
- package/bin/rad/input.js +87 -0
- package/bin/rad/lod.js +657 -0
- package/bin/rad/math.js +166 -0
- package/bin/rad/parallel.js +578 -0
- package/bin/rad/progress.js +50 -0
- package/bin/rad/rad.js +612 -0
- package/bin/rad/splats.js +432 -0
- package/bin/rad/storage.js +776 -0
- package/dist/gaussian-splat-lite.cjs +16 -10
- package/dist/gaussian-splat-lite.cjs.map +1 -1
- package/dist/gaussian-splat-lite.min.cjs +106 -98
- package/dist/gaussian-splat-lite.min.cjs.map +1 -1
- package/dist/gaussian-splat-lite.module.js +16 -10
- package/dist/gaussian-splat-lite.module.js.map +1 -1
- package/dist/gaussian-splat-lite.module.min.js +1541 -1533
- package/dist/gaussian-splat-lite.module.min.js.map +1 -1
- package/dist/types/data/RadPagedSplats.d.cts +1 -1
- package/dist/types/data/RadPagedSplats.d.ts +1 -1
- package/dist/types/loaders/rad/radFormat.d.cts +2 -2
- package/dist/types/loaders/rad/radFormat.d.ts +2 -2
- package/docs/BuildRad.md +91 -0
- package/docs/RadStreamScheduler.md +3 -1
- package/package.json +9 -3
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// LOD storage and merging adapted from MIT-licensed code. See THIRD_PARTY_LICENSES.md.
|
|
2
|
+
import {
|
|
3
|
+
HALF,
|
|
4
|
+
SCALE,
|
|
5
|
+
area,
|
|
6
|
+
covariance,
|
|
7
|
+
determinant,
|
|
8
|
+
eigens,
|
|
9
|
+
half,
|
|
10
|
+
halfBits,
|
|
11
|
+
opacityRadius,
|
|
12
|
+
quaternion,
|
|
13
|
+
} from "./math.js";
|
|
14
|
+
import { Progress } from "./progress.js";
|
|
15
|
+
import { IndexArray, PagedArray, Permutation } from "./storage.js";
|
|
16
|
+
|
|
17
|
+
export const SH_DIMS = [9, 15, 21];
|
|
18
|
+
|
|
19
|
+
// What retain() finds a splat to be.
|
|
20
|
+
export const KEEP = 0;
|
|
21
|
+
export const INVALID = 1;
|
|
22
|
+
export const EMPTY = 2;
|
|
23
|
+
// A worker found a page that is not cached.
|
|
24
|
+
export const LATER = 3;
|
|
25
|
+
|
|
26
|
+
// Sets a record's feature size and area from its scales and opacity.
|
|
27
|
+
export function refresh(record) {
|
|
28
|
+
const x = SCALE[halfBits(record[7])];
|
|
29
|
+
const y = SCALE[halfBits(record[8])];
|
|
30
|
+
const z = SCALE[halfBits(record[9])];
|
|
31
|
+
record[14] = Math.fround(2 * Math.max(x, y, z) * opacityRadius(record[3]));
|
|
32
|
+
record[15] = Math.fround(area(x, y, z));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Whether a record has no negative opacity and no NaN or infinite values but
|
|
36
|
+
// zero scales (log scale -Infinity).
|
|
37
|
+
export function validRecord(record) {
|
|
38
|
+
if (record[3] < 0) return false;
|
|
39
|
+
for (let d = 0; d < 16; d++)
|
|
40
|
+
if (
|
|
41
|
+
!Number.isFinite(record[d]) &&
|
|
42
|
+
!(d >= 7 && d <= 9 && record[d] === Number.NEGATIVE_INFINITY)
|
|
43
|
+
)
|
|
44
|
+
return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class Splats {
|
|
49
|
+
constructor(workspace, maxSh) {
|
|
50
|
+
this.workspace = workspace;
|
|
51
|
+
this.maxSh = maxSh;
|
|
52
|
+
this.inputSh = 0;
|
|
53
|
+
this.length = 0;
|
|
54
|
+
this.invalid = 0;
|
|
55
|
+
this.records = new PagedArray(workspace, Float32Array);
|
|
56
|
+
this.sh = SH_DIMS.map(() => new PagedArray(workspace, Uint16Array));
|
|
57
|
+
// The two children of the i-th merged splat are entries 2i and 2i + 1 of
|
|
58
|
+
// `children`. Once buildLod() has laid out the tree, a splat's children
|
|
59
|
+
// are the childCount consecutive rows from row childStart.
|
|
60
|
+
this.childStart = new PagedArray(workspace, Float64Array);
|
|
61
|
+
this.childCount = new IndexArray(workspace);
|
|
62
|
+
this.children = new IndexArray(workspace);
|
|
63
|
+
// Each splat's merge weight, area × opacity, which merge() reads instead
|
|
64
|
+
// of two records.
|
|
65
|
+
this.weight = new PagedArray(workspace, Float64Array);
|
|
66
|
+
// The two child weights of each merged splat, for mergeSh().
|
|
67
|
+
this.blend = new PagedArray(workspace, Float64Array);
|
|
68
|
+
this.merged = new Float64Array(16);
|
|
69
|
+
this.bits = new Uint16Array(0);
|
|
70
|
+
this.merger = new Merger();
|
|
71
|
+
this.shLeft = new Uint16Array(21);
|
|
72
|
+
this.shRight = new Uint16Array(21);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
receive(name, base, values) {
|
|
76
|
+
if (name === "init") {
|
|
77
|
+
if (base > 0x80000000)
|
|
78
|
+
throw new Error("Input exceeds LOD u32 index limit");
|
|
79
|
+
this.length = base;
|
|
80
|
+
this.records.length = base * 16;
|
|
81
|
+
this.inputSh = values[0];
|
|
82
|
+
this.maxSh = Math.min(this.maxSh, this.inputSh, 3);
|
|
83
|
+
for (let band = 0; band < this.maxSh; band++)
|
|
84
|
+
this.sh[band].length = base * SH_DIMS[band];
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!values.length) return;
|
|
88
|
+
// `bits` is reused, so reading leaves no freed buffers behind.
|
|
89
|
+
if (name.startsWith("sh")) {
|
|
90
|
+
const band = Number(name[2]) - 1;
|
|
91
|
+
if (band >= this.maxSh) return;
|
|
92
|
+
if (this.bits.length < values.length)
|
|
93
|
+
this.bits = new Uint16Array(values.length);
|
|
94
|
+
for (let i = 0; i < values.length; i++)
|
|
95
|
+
this.bits[i] = halfBits(values[i]);
|
|
96
|
+
this.sh[band].write(base * SH_DIMS[band], this.bits, values.length);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const [field, dims] = {
|
|
100
|
+
center: [0, 3],
|
|
101
|
+
opacity: [3, 1],
|
|
102
|
+
rgb: [4, 3],
|
|
103
|
+
scale: [7, 3],
|
|
104
|
+
lnScale: [7, 3],
|
|
105
|
+
quat: [10, 4],
|
|
106
|
+
}[name];
|
|
107
|
+
for (let i = 0; i < values.length / dims; i++) {
|
|
108
|
+
const index = (base + i) * 16 + field;
|
|
109
|
+
const page = this.records.page(index, true);
|
|
110
|
+
const offset = index & this.records.mask;
|
|
111
|
+
for (let d = 0; d < dims; d++) {
|
|
112
|
+
const value =
|
|
113
|
+
name === "scale"
|
|
114
|
+
? Math.log(values[i * dims + d])
|
|
115
|
+
: values[i * dims + d];
|
|
116
|
+
page[offset + d] = field === 0 ? value : half(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
read(index, output) {
|
|
122
|
+
return this.records.read(index * 16, output, 16);
|
|
123
|
+
}
|
|
124
|
+
feature(index) {
|
|
125
|
+
return this.records.get(index * 16 + 14);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Skips empty splats and counts invalid ones in `invalid`: NaN or infinite
|
|
129
|
+
// values, including a zero rotation, which the decoder normalizes to NaN.
|
|
130
|
+
// Workers refresh and check blocks of splats in place; the main thread
|
|
131
|
+
// checks those they leave, and moves kept splats after skipped ones.
|
|
132
|
+
retain(pool) {
|
|
133
|
+
const progress = new Progress("Filtering splats", this.length);
|
|
134
|
+
const { results } = pool;
|
|
135
|
+
const block = results.length;
|
|
136
|
+
const record = new Float64Array(16);
|
|
137
|
+
const rows = SH_DIMS.map((dims) => new Uint16Array(dims));
|
|
138
|
+
const columns = [
|
|
139
|
+
[this.records.share(), 16],
|
|
140
|
+
...this.sh
|
|
141
|
+
.slice(0, this.maxSh)
|
|
142
|
+
.map((column, band) => [column.share(), SH_DIMS[band]]),
|
|
143
|
+
];
|
|
144
|
+
let count = 0;
|
|
145
|
+
this.invalid = 0;
|
|
146
|
+
for (let base = 0; base < this.length; base += block) {
|
|
147
|
+
const size = Math.min(block, this.length - base);
|
|
148
|
+
for (const [column, stride] of columns)
|
|
149
|
+
column.load(
|
|
150
|
+
base * stride,
|
|
151
|
+
(base + size) * stride,
|
|
152
|
+
column === this.records,
|
|
153
|
+
);
|
|
154
|
+
pool.check(this, base, size);
|
|
155
|
+
for (let i = 0; i < size; i++) {
|
|
156
|
+
const index = base + i;
|
|
157
|
+
let state = results[i];
|
|
158
|
+
if (state === LATER) {
|
|
159
|
+
this.read(index, record);
|
|
160
|
+
refresh(record);
|
|
161
|
+
state = !this.valid(index, record, rows)
|
|
162
|
+
? INVALID
|
|
163
|
+
: record[3] > 0 && record[14] > 0
|
|
164
|
+
? KEEP
|
|
165
|
+
: EMPTY;
|
|
166
|
+
if (state === KEEP) this.records.write(index * 16, record);
|
|
167
|
+
}
|
|
168
|
+
if (state === INVALID) this.invalid++;
|
|
169
|
+
if (state !== KEEP) continue;
|
|
170
|
+
if (count !== index) {
|
|
171
|
+
this.records.write(count * 16, this.read(index, record));
|
|
172
|
+
for (let band = 0; band < this.maxSh; band++) {
|
|
173
|
+
const dims = SH_DIMS[band];
|
|
174
|
+
this.sh[band].read(index * dims, rows[band]);
|
|
175
|
+
this.sh[band].write(count * dims, rows[band]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
count++;
|
|
179
|
+
}
|
|
180
|
+
progress.update(base + size);
|
|
181
|
+
}
|
|
182
|
+
this.length = count;
|
|
183
|
+
this.records.length = count * 16;
|
|
184
|
+
for (let band = 0; band < this.maxSh; band++)
|
|
185
|
+
this.sh[band].length = count * SH_DIMS[band];
|
|
186
|
+
progress.finish(`${count} usable splats`);
|
|
187
|
+
}
|
|
188
|
+
// Also reads the splat's SH coefficients into `rows`.
|
|
189
|
+
valid(index, record, rows) {
|
|
190
|
+
if (!validRecord(record)) return false;
|
|
191
|
+
for (let band = 0; band < this.maxSh; band++) {
|
|
192
|
+
const row = rows[band];
|
|
193
|
+
this.sh[band].read(index * row.length, row);
|
|
194
|
+
for (const bits of row) if (!Number.isFinite(HALF[bits])) return false;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// `ca` and `cb` are the Similarities terms of splats `a` and `b`, each
|
|
200
|
+
// followed by its weight.
|
|
201
|
+
merge(a, b, ca, cb) {
|
|
202
|
+
const merger = this.merger;
|
|
203
|
+
merger.run(ca, cb, ca[13], cb[13], this.merged);
|
|
204
|
+
return this.append(a, b, this.merged, merger.w, merger.v);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Adds the merge `out` of splats `a` and `b`, blended with weights w and v.
|
|
208
|
+
append(a, b, out, w, v) {
|
|
209
|
+
const index = this.length++;
|
|
210
|
+
this.records.write(index * 16, out);
|
|
211
|
+
this.weight.set(index, out[15] * out[3]);
|
|
212
|
+
this.children.push(a);
|
|
213
|
+
this.children.push(b);
|
|
214
|
+
// SH coefficients do not affect merging; mergeSh() blends them later.
|
|
215
|
+
if (this.maxSh) {
|
|
216
|
+
this.blend.push(w);
|
|
217
|
+
this.blend.push(v);
|
|
218
|
+
}
|
|
219
|
+
return index;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Blends the SH coefficients of the splats merge() created from `initial`
|
|
223
|
+
// on, in creation order, so children are done before their parents. Going
|
|
224
|
+
// band by band keeps the random reads of children within one column.
|
|
225
|
+
// Workers blend batches of splats whose children precede the batch; the
|
|
226
|
+
// rest wait for their children in the batch.
|
|
227
|
+
mergeSh(initial, pool) {
|
|
228
|
+
const { nodes, entries, later, blends, blended, results } = pool;
|
|
229
|
+
// Splats per batch.
|
|
230
|
+
const size = later.length;
|
|
231
|
+
const rows = [this.shLeft, this.shRight];
|
|
232
|
+
for (let band = 0; band < this.maxSh; band++) {
|
|
233
|
+
const progress = new Progress(
|
|
234
|
+
`Blending SH${band + 1}`,
|
|
235
|
+
this.length - initial,
|
|
236
|
+
);
|
|
237
|
+
const dims = SH_DIMS[band];
|
|
238
|
+
const column = this.sh[band].share();
|
|
239
|
+
for (let start = initial; start < this.length; start += size) {
|
|
240
|
+
const count = Math.min(size, this.length - start);
|
|
241
|
+
// Load the pages of the children's rows for the workers. Consecutive
|
|
242
|
+
// splats' children often share pages; a page evicted meanwhile is a
|
|
243
|
+
// miss that the main thread blends.
|
|
244
|
+
let left = -1;
|
|
245
|
+
let right = -1;
|
|
246
|
+
for (let i = 0; i < count; i++) {
|
|
247
|
+
const a = this.children.get((start + i - initial) * 2);
|
|
248
|
+
const b = this.children.get((start + i - initial) * 2 + 1);
|
|
249
|
+
nodes[i] = a;
|
|
250
|
+
entries[i] = b;
|
|
251
|
+
blends[i * 2] = this.blend.get((start + i - initial) * 2);
|
|
252
|
+
blends[i * 2 + 1] = this.blend.get((start + i - initial) * 2 + 1);
|
|
253
|
+
later[i] = Number(a >= start || b >= start);
|
|
254
|
+
if (later[i]) continue;
|
|
255
|
+
if (Math.floor((a * dims) / column.items) !== left) {
|
|
256
|
+
left = Math.floor((a * dims) / column.items);
|
|
257
|
+
column.page(a * dims);
|
|
258
|
+
}
|
|
259
|
+
if (Math.floor((b * dims) / column.items) !== right) {
|
|
260
|
+
right = Math.floor((b * dims) / column.items);
|
|
261
|
+
column.page(b * dims);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
pool.blend(column, count, dims);
|
|
265
|
+
for (let i = 0; i < count; i++) {
|
|
266
|
+
// A worker blended rows it marked 0.
|
|
267
|
+
if (!later[i] && results[i] === 0) continue;
|
|
268
|
+
for (let c = 0; c < 2; c++) {
|
|
269
|
+
const child = c ? entries[i] : nodes[i];
|
|
270
|
+
if (child < start) column.read(child * dims, rows[c], dims);
|
|
271
|
+
else
|
|
272
|
+
for (let d = 0; d < dims; d++)
|
|
273
|
+
rows[c][d] = blended[(child - start) * dims + d];
|
|
274
|
+
}
|
|
275
|
+
const w = blends[i * 2];
|
|
276
|
+
const v = blends[i * 2 + 1];
|
|
277
|
+
for (let d = 0; d < dims; d++)
|
|
278
|
+
blended[i * dims + d] = halfBits(
|
|
279
|
+
HALF[this.shLeft[d]] * w + HALF[this.shRight[d]] * v,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
column.write(start * dims, blended, count * dims);
|
|
283
|
+
progress.update(start + count - initial);
|
|
284
|
+
}
|
|
285
|
+
progress.finish();
|
|
286
|
+
}
|
|
287
|
+
this.blend.dispose();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Moves row indices[i] of the records and SH to row i.
|
|
291
|
+
reorder(indices, pool) {
|
|
292
|
+
const permutation = new Permutation(
|
|
293
|
+
this.workspace,
|
|
294
|
+
indices,
|
|
295
|
+
this.length,
|
|
296
|
+
"Preparing splat order",
|
|
297
|
+
);
|
|
298
|
+
this.records = permutation.apply(
|
|
299
|
+
this.records,
|
|
300
|
+
16,
|
|
301
|
+
pool,
|
|
302
|
+
false,
|
|
303
|
+
"Reordering splats",
|
|
304
|
+
);
|
|
305
|
+
for (let band = 0; band < this.maxSh; band++)
|
|
306
|
+
this.sh[band] = permutation.apply(
|
|
307
|
+
this.sh[band],
|
|
308
|
+
SH_DIMS[band],
|
|
309
|
+
pool,
|
|
310
|
+
false,
|
|
311
|
+
`Reordering SH${band + 1}`,
|
|
312
|
+
);
|
|
313
|
+
permutation.dispose();
|
|
314
|
+
this.length = indices.length;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
dispose() {
|
|
318
|
+
for (const column of [
|
|
319
|
+
this.records,
|
|
320
|
+
...this.sh,
|
|
321
|
+
this.childStart,
|
|
322
|
+
this.childCount,
|
|
323
|
+
this.children,
|
|
324
|
+
this.weight,
|
|
325
|
+
this.blend,
|
|
326
|
+
])
|
|
327
|
+
column.dispose();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Merges two splats, given their Similarities terms `ca` and `cb` and
|
|
332
|
+
// weights (area × opacity), into the record `out`; `w` and `v` are then their
|
|
333
|
+
// blend weights.
|
|
334
|
+
export class Merger {
|
|
335
|
+
constructor() {
|
|
336
|
+
this.cov = new Float64Array(6);
|
|
337
|
+
this.matrix = new Float64Array(9);
|
|
338
|
+
this.vectors = new Float64Array(9);
|
|
339
|
+
this.rotation = new Float64Array(9);
|
|
340
|
+
this.axes = new Uint8Array(3);
|
|
341
|
+
this.w = 0;
|
|
342
|
+
this.v = 0;
|
|
343
|
+
}
|
|
344
|
+
run(ca, cb, wa, wb, out) {
|
|
345
|
+
const total = Math.max(wa + wb, 1e-30);
|
|
346
|
+
const w = wa / total;
|
|
347
|
+
const v = wb / total;
|
|
348
|
+
this.w = w;
|
|
349
|
+
this.v = v;
|
|
350
|
+
for (let d = 0; d < 3; d++) {
|
|
351
|
+
out[d] = Math.fround(ca[d] * w + cb[d] * v);
|
|
352
|
+
out[4 + d] = half(ca[3 + d] * w + cb[3 + d] * v);
|
|
353
|
+
}
|
|
354
|
+
const ax = ca[0] - out[0];
|
|
355
|
+
const ay = ca[1] - out[1];
|
|
356
|
+
const az = ca[2] - out[2];
|
|
357
|
+
const bx = cb[0] - out[0];
|
|
358
|
+
const by = cb[1] - out[1];
|
|
359
|
+
const bz = cb[2] - out[2];
|
|
360
|
+
const cov = this.cov;
|
|
361
|
+
cov[0] = (ca[6] + ax * ax) * w + (cb[6] + bx * bx) * v;
|
|
362
|
+
cov[1] = (ca[7] + ay * ay) * w + (cb[7] + by * by) * v;
|
|
363
|
+
cov[2] = (ca[8] + az * az) * w + (cb[8] + bz * bz) * v;
|
|
364
|
+
cov[3] = (ca[9] + ax * ay) * w + (cb[9] + bx * by) * v;
|
|
365
|
+
cov[4] = (ca[10] + ax * az) * w + (cb[10] + bx * bz) * v;
|
|
366
|
+
cov[5] = (ca[11] + ay * az) * w + (cb[11] + by * bz) * v;
|
|
367
|
+
for (const value of cov)
|
|
368
|
+
if (!Number.isFinite(value)) throw new Error("LOD covariance overflow");
|
|
369
|
+
eigens(cov, this.matrix, this.vectors, this.axes);
|
|
370
|
+
const r = this.rotation;
|
|
371
|
+
for (let col = 0; col < 3; col++) {
|
|
372
|
+
const axis = this.axes[col];
|
|
373
|
+
const norm = Math.hypot(
|
|
374
|
+
this.vectors[axis],
|
|
375
|
+
this.vectors[3 + axis],
|
|
376
|
+
this.vectors[6 + axis],
|
|
377
|
+
);
|
|
378
|
+
for (let row = 0; row < 3; row++)
|
|
379
|
+
r[col * 3 + row] = this.vectors[row * 3 + axis] / norm;
|
|
380
|
+
out[7 + col] = Math.max(
|
|
381
|
+
1e-30,
|
|
382
|
+
Math.sqrt(Math.max(0, this.matrix[axis * 3 + axis])),
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (
|
|
386
|
+
r[0] * (r[4] * r[8] - r[5] * r[7]) -
|
|
387
|
+
r[3] * (r[1] * r[8] - r[2] * r[7]) +
|
|
388
|
+
r[6] * (r[1] * r[5] - r[2] * r[4]) <
|
|
389
|
+
0
|
|
390
|
+
) {
|
|
391
|
+
r[6] *= -1;
|
|
392
|
+
r[7] *= -1;
|
|
393
|
+
r[8] *= -1;
|
|
394
|
+
}
|
|
395
|
+
out[3] = half(
|
|
396
|
+
Math.max(0.000001, Math.min(1000, total / area(out[7], out[8], out[9]))),
|
|
397
|
+
);
|
|
398
|
+
for (let d = 7; d <= 9; d++) out[d] = half(Math.log(out[d]));
|
|
399
|
+
quaternion(r, out);
|
|
400
|
+
for (let d = 10; d <= 13; d++) out[d] = half(out[d]);
|
|
401
|
+
refresh(out);
|
|
402
|
+
if (!Number.isFinite(out[14]) || !Number.isFinite(out[15]))
|
|
403
|
+
throw new Error("LOD scale overflow");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export class Similarities {
|
|
408
|
+
constructor(splats) {
|
|
409
|
+
this.splats = splats;
|
|
410
|
+
this.record = new Float64Array(16);
|
|
411
|
+
this.cov = new Float64Array(6);
|
|
412
|
+
this.rotation = new Float64Array(9);
|
|
413
|
+
}
|
|
414
|
+
// The terms of splat `index`, then its weight as merge() sets it.
|
|
415
|
+
get(index, out) {
|
|
416
|
+
const record = this.splats.read(index, this.record);
|
|
417
|
+
this.terms(record, out);
|
|
418
|
+
out[13] = record[15] * record[3];
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
// The terms of `record`: center, color, covariance and log determinant.
|
|
422
|
+
terms(record, out) {
|
|
423
|
+
covariance(record, this.cov, this.rotation);
|
|
424
|
+
for (let d = 0; d < 3; d++) {
|
|
425
|
+
out[d] = record[d];
|
|
426
|
+
out[3 + d] = record[4 + d];
|
|
427
|
+
}
|
|
428
|
+
for (let d = 0; d < 6; d++) out[6 + d] = Math.fround(this.cov[d]);
|
|
429
|
+
out[12] = Math.fround(0.25 * Math.log(determinant(this.cov)));
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
}
|