@speridlabs/visus 0.1.0 → 0.3.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/README.md +85 -117
- package/dist/main.d.ts +320 -0
- package/dist/main.es.js +1299 -10
- package/dist/main.umd.js +286 -0
- package/dist/react.d.ts +19 -0
- package/dist/react.es.js +1484 -196
- package/package.json +13 -8
- package/dist/index.d.ts +0 -12
- package/dist/ply-CQ9dyX1o.mjs +0 -1299
package/dist/ply-CQ9dyX1o.mjs
DELETED
|
@@ -1,1299 +0,0 @@
|
|
|
1
|
-
var ct = Object.defineProperty;
|
|
2
|
-
var lt = (k, t, r) => t in k ? ct(k, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : k[t] = r;
|
|
3
|
-
var f = (k, t, r) => lt(k, typeof t != "symbol" ? t + "" : t, r);
|
|
4
|
-
import * as n from "three";
|
|
5
|
-
class et {
|
|
6
|
-
constructor() {
|
|
7
|
-
f(this, "min", new n.Vector3(1 / 0, 1 / 0, 1 / 0));
|
|
8
|
-
f(this, "max", new n.Vector3(-1 / 0, -1 / 0, -1 / 0));
|
|
9
|
-
f(this, "center", new n.Vector3());
|
|
10
|
-
/** Half extents (size/2) */
|
|
11
|
-
f(this, "halfExtents", new n.Vector3());
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Reset the bounding box to its initial state
|
|
15
|
-
*/
|
|
16
|
-
reset() {
|
|
17
|
-
this.min.set(1 / 0, 1 / 0, 1 / 0), this.max.set(-1 / 0, -1 / 0, -1 / 0), this.center.set(0, 0, 0), this.halfExtents.set(0, 0, 0);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Expand the bounding box to include the given point
|
|
21
|
-
* @param point Point to include in the bounding box
|
|
22
|
-
*/
|
|
23
|
-
expandByPoint(t) {
|
|
24
|
-
this.min.x = Math.min(this.min.x, t.x), this.min.y = Math.min(this.min.y, t.y), this.min.z = Math.min(this.min.z, t.z), this.max.x = Math.max(this.max.x, t.x), this.max.y = Math.max(this.max.y, t.y), this.max.z = Math.max(this.max.z, t.z), this.updateDerived();
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Expand this bounding box to include another bounding box
|
|
28
|
-
* @param box Bounding box to include
|
|
29
|
-
*/
|
|
30
|
-
expandByBox(t) {
|
|
31
|
-
this.min.x = Math.min(this.min.x, t.min.x), this.min.y = Math.min(this.min.y, t.min.y), this.min.z = Math.min(this.min.z, t.min.z), this.max.x = Math.max(this.max.x, t.max.x), this.max.y = Math.max(this.max.y, t.max.y), this.max.z = Math.max(this.max.z, t.max.z), this.updateDerived();
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Update the center and half extents based on min/max
|
|
35
|
-
*/
|
|
36
|
-
updateDerived() {
|
|
37
|
-
this.center.addVectors(this.min, this.max).multiplyScalar(0.5), this.halfExtents.subVectors(this.max, this.min).multiplyScalar(0.5);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Check if this box contains a point
|
|
41
|
-
* @param point Point to check
|
|
42
|
-
* @returns True if the point is inside the box
|
|
43
|
-
*/
|
|
44
|
-
containsPoint(t) {
|
|
45
|
-
return t.x >= this.min.x && t.x <= this.max.x && t.y >= this.min.y && t.y <= this.max.y && t.z >= this.min.z && t.z <= this.max.z;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Create a Three.js Box3 from this bounding box
|
|
49
|
-
* @returns THREE.Box3 representation
|
|
50
|
-
*/
|
|
51
|
-
toBox3() {
|
|
52
|
-
return new n.Box3().set(this.min, this.max);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Create a clone of this bounding box
|
|
56
|
-
* @returns New bounding box with the same values
|
|
57
|
-
*/
|
|
58
|
-
clone() {
|
|
59
|
-
const t = new et();
|
|
60
|
-
return t.min.copy(this.min), t.max.copy(this.max), t.center.copy(this.center), t.halfExtents.copy(this.halfExtents), t;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
class ht {
|
|
64
|
-
// TODO: there is no sh spherical harmonics
|
|
65
|
-
constructor(t = 0) {
|
|
66
|
-
f(this, "numSplats", 0);
|
|
67
|
-
f(this, "positions");
|
|
68
|
-
f(this, "rotations");
|
|
69
|
-
f(this, "scales");
|
|
70
|
-
f(this, "colors");
|
|
71
|
-
f(this, "opacities");
|
|
72
|
-
f(this, "centers");
|
|
73
|
-
f(this, "boundingBox", new et());
|
|
74
|
-
this.numSplats = t, this.allocateBuffers(t);
|
|
75
|
-
}
|
|
76
|
-
allocateBuffers(t) {
|
|
77
|
-
this.positions = new Float32Array(t * 3), this.rotations = new Float32Array(t * 4), this.scales = new Float32Array(t * 3), this.colors = new Float32Array(t * 3), this.opacities = new Float32Array(t), this.centers = new Float32Array(t * 3);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Set data for a specific splat
|
|
81
|
-
* @param index Splat index
|
|
82
|
-
* @param position Position vector
|
|
83
|
-
* @param rotation Quaternion
|
|
84
|
-
* @param scale Scale vector (expects LOG SCALE)
|
|
85
|
-
* @param color Color
|
|
86
|
-
* @param opacity Opacity value
|
|
87
|
-
*/
|
|
88
|
-
setSplat(t, r, e, o, s, a) {
|
|
89
|
-
if (t >= this.numSplats)
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Splat index out of bounds: ${t} >= ${this.numSplats}`
|
|
92
|
-
);
|
|
93
|
-
const i = t * 3, c = t * 4, h = t * 3, v = t * 3;
|
|
94
|
-
this.positions[i] = r.x, this.positions[i + 1] = r.y, this.positions[i + 2] = r.z, this.rotations[c] = e.x, this.rotations[c + 1] = e.y, this.rotations[c + 2] = e.z, this.rotations[c + 3] = e.w, this.scales[h] = o.x, this.scales[h + 1] = o.y, this.scales[h + 2] = o.z, this.colors[v] = s.r, this.colors[v + 1] = s.g, this.colors[v + 2] = s.b, this.opacities[t] = a, this.centers[i] = r.x, this.centers[i + 1] = r.y, this.centers[i + 2] = r.z;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Get a splat's data
|
|
98
|
-
* @param index Splat index
|
|
99
|
-
* @returns Object containing splat data (linear scale)
|
|
100
|
-
*/
|
|
101
|
-
getSplat(t) {
|
|
102
|
-
if (t >= this.numSplats)
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Splat index out of bounds: ${t} >= ${this.numSplats}`
|
|
105
|
-
);
|
|
106
|
-
const r = t * 3, e = t * 4, o = t * 3, s = t * 3;
|
|
107
|
-
return {
|
|
108
|
-
position: new n.Vector3(
|
|
109
|
-
this.positions[r],
|
|
110
|
-
this.positions[r + 1],
|
|
111
|
-
this.positions[r + 2]
|
|
112
|
-
),
|
|
113
|
-
rotation: new n.Quaternion(
|
|
114
|
-
this.rotations[e],
|
|
115
|
-
this.rotations[e + 1],
|
|
116
|
-
this.rotations[e + 2],
|
|
117
|
-
this.rotations[e + 3]
|
|
118
|
-
),
|
|
119
|
-
// Convert log scale back to linear scale for external use
|
|
120
|
-
scale: new n.Vector3(
|
|
121
|
-
Math.exp(this.scales[o]),
|
|
122
|
-
Math.exp(this.scales[o + 1]),
|
|
123
|
-
Math.exp(this.scales[o + 2])
|
|
124
|
-
),
|
|
125
|
-
color: new n.Color(
|
|
126
|
-
this.colors[s],
|
|
127
|
-
this.colors[s + 1],
|
|
128
|
-
this.colors[s + 2]
|
|
129
|
-
),
|
|
130
|
-
opacity: this.opacities[t]
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Calculate the bounding box for all splats
|
|
135
|
-
* NOTE: Currently it only uses positions, not the actual splat bounds
|
|
136
|
-
*/
|
|
137
|
-
calculateBoundingBox() {
|
|
138
|
-
this.boundingBox.reset();
|
|
139
|
-
const t = new n.Vector3();
|
|
140
|
-
for (let r = 0; r < this.numSplats; r++) {
|
|
141
|
-
const e = r * 3;
|
|
142
|
-
t.set(
|
|
143
|
-
this.positions[e],
|
|
144
|
-
this.positions[e + 1],
|
|
145
|
-
this.positions[e + 2]
|
|
146
|
-
), this.boundingBox.expandByPoint(t);
|
|
147
|
-
}
|
|
148
|
-
return this.boundingBox;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Create a Three.js BufferGeometry from this splat data
|
|
152
|
-
* This is for visualization/debugging purposes only, not for rendering
|
|
153
|
-
*/
|
|
154
|
-
createDebugGeometry() {
|
|
155
|
-
const t = new n.BufferGeometry();
|
|
156
|
-
t.setAttribute(
|
|
157
|
-
"position",
|
|
158
|
-
new n.BufferAttribute(this.positions, 3)
|
|
159
|
-
);
|
|
160
|
-
const r = new Float32Array(this.numSplats * 3), e = new n.Quaternion(), o = new n.Euler();
|
|
161
|
-
for (let s = 0; s < this.numSplats; s++) {
|
|
162
|
-
const a = s * 4, i = s * 3;
|
|
163
|
-
e.set(
|
|
164
|
-
this.rotations[a],
|
|
165
|
-
this.rotations[a + 1],
|
|
166
|
-
this.rotations[a + 2],
|
|
167
|
-
this.rotations[a + 3]
|
|
168
|
-
), o.setFromQuaternion(e), r[i] = o.x, r[i + 1] = o.y, r[i + 2] = o.z;
|
|
169
|
-
}
|
|
170
|
-
return t.setAttribute(
|
|
171
|
-
"rotation",
|
|
172
|
-
new n.BufferAttribute(r, 3)
|
|
173
|
-
), t.setAttribute(
|
|
174
|
-
"scale",
|
|
175
|
-
new n.BufferAttribute(this.scales, 3)
|
|
176
|
-
), t.setAttribute(
|
|
177
|
-
"color",
|
|
178
|
-
new n.BufferAttribute(this.colors, 3)
|
|
179
|
-
), t.setAttribute(
|
|
180
|
-
"opacity",
|
|
181
|
-
new n.BufferAttribute(this.opacities, 1)
|
|
182
|
-
), t;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
class ut extends n.EventDispatcher {
|
|
186
|
-
constructor() {
|
|
187
|
-
super();
|
|
188
|
-
f(this, "worker");
|
|
189
|
-
f(this, "centers", null);
|
|
190
|
-
f(this, "orderTexture", null);
|
|
191
|
-
/** Bounding box data for optimization */
|
|
192
|
-
f(this, "chunks", null);
|
|
193
|
-
f(this, "lastCameraPosition", new n.Vector3());
|
|
194
|
-
f(this, "lastCameraDirection", new n.Vector3());
|
|
195
|
-
const r = this.createWorkerCode(), e = new Blob([r], { type: "application/javascript" });
|
|
196
|
-
this.worker = new Worker(URL.createObjectURL(e)), this.worker.onmessage = this.onWorkerMessage.bind(this);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Handles messages received from the sorting worker.
|
|
200
|
-
* @param event The message event from the worker.
|
|
201
|
-
*/
|
|
202
|
-
onWorkerMessage(r) {
|
|
203
|
-
if (!this.orderTexture || !this.orderTexture.image)
|
|
204
|
-
return console.error("SplatSorter: Order texture not initialized!");
|
|
205
|
-
const { order: e, count: o } = r.data, s = this.orderTexture.image.data;
|
|
206
|
-
if (!(s instanceof Uint32Array))
|
|
207
|
-
return console.error(
|
|
208
|
-
"SplatSorter: Order texture data is not a Uint32Array!"
|
|
209
|
-
);
|
|
210
|
-
s.set(new Uint32Array(e)), this.orderTexture.needsUpdate = !0;
|
|
211
|
-
const a = s.buffer.slice(0), i = { order: a };
|
|
212
|
-
this.worker.postMessage(i, [a]), this.dispatchEvent({ type: "updated", count: o });
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Initializes the sorter with necessary data and textures.
|
|
216
|
-
* @param orderTexture The THREE.DataTexture (R32UI) to store the sorted splat indices.
|
|
217
|
-
* @param centers A Float32Array containing the center position (x, y, z) for each splat.
|
|
218
|
-
* @param chunks Optional: A Float32Array containing bounding box chunk data [minX, minY, minZ, maxX, maxY, maxZ, ...] for optimization.
|
|
219
|
-
*/
|
|
220
|
-
init(r, e, o) {
|
|
221
|
-
if (!r || !(r.image.data instanceof Uint32Array))
|
|
222
|
-
throw new Error("SplatSorter: Invalid orderTexture provided. Must be DataTexture with Uint32Array data.");
|
|
223
|
-
if (!e || e.length % 3 !== 0)
|
|
224
|
-
throw new Error("SplatSorter: Invalid centers array provided. Length must be multiple of 3.");
|
|
225
|
-
if (r.image.data.length < e.length / 3)
|
|
226
|
-
throw new Error("SplatSorter: orderTexture data buffer is smaller than the number of splats.");
|
|
227
|
-
const s = e.length / 3;
|
|
228
|
-
this.orderTexture = r, this.centers = e.slice();
|
|
229
|
-
const a = this.orderTexture.image.data;
|
|
230
|
-
for (let g = 0; g < s; g++) a[g] = g;
|
|
231
|
-
this.orderTexture.needsUpdate = !0;
|
|
232
|
-
const i = a.buffer.slice(0), c = this.centers.buffer.slice(0), h = {
|
|
233
|
-
order: i,
|
|
234
|
-
centers: c
|
|
235
|
-
}, v = [
|
|
236
|
-
i,
|
|
237
|
-
c
|
|
238
|
-
];
|
|
239
|
-
if (o) {
|
|
240
|
-
this.chunks = o.slice();
|
|
241
|
-
const g = this.chunks.buffer.slice(0);
|
|
242
|
-
h.chunks = g, v.push(g);
|
|
243
|
-
}
|
|
244
|
-
this.worker.postMessage(h, v);
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Applies an optional mapping to filter or reorder splats before sorting.
|
|
248
|
-
* The sorter will only consider splats whose original indices are present in the mapping.
|
|
249
|
-
* @param mapping A Uint32Array where each element is the *original* index of a splat to include, or null to reset mapping.
|
|
250
|
-
*/
|
|
251
|
-
setMapping(r) {
|
|
252
|
-
if (!this.centers)
|
|
253
|
-
return console.warn(
|
|
254
|
-
"SplatSorter: Cannot set mapping before initialization."
|
|
255
|
-
);
|
|
256
|
-
let e;
|
|
257
|
-
const o = [];
|
|
258
|
-
if (!r) {
|
|
259
|
-
const c = this.centers.buffer.slice(0);
|
|
260
|
-
return e = {
|
|
261
|
-
centers: c,
|
|
262
|
-
mapping: null
|
|
263
|
-
}, o.push(c), this.worker.postMessage(e, o);
|
|
264
|
-
}
|
|
265
|
-
const s = new Float32Array(r.length * 3);
|
|
266
|
-
for (let c = 0; c < r.length; c++) {
|
|
267
|
-
const h = r[c];
|
|
268
|
-
if (h * 3 + 2 >= this.centers.length) {
|
|
269
|
-
console.warn(
|
|
270
|
-
`SplatSorter: Mapping index ${h} out of bounds.`
|
|
271
|
-
);
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
const v = h * 3, g = c * 3;
|
|
275
|
-
s[g + 0] = this.centers[v + 0], s[g + 1] = this.centers[v + 1], s[g + 2] = this.centers[v + 2];
|
|
276
|
-
}
|
|
277
|
-
const a = s.buffer.slice(0), i = r.buffer.slice(0);
|
|
278
|
-
e = {
|
|
279
|
-
centers: a,
|
|
280
|
-
mapping: i
|
|
281
|
-
}, o.push(a, i), this.worker.postMessage(e, o);
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Updates the camera parameters used for sorting.
|
|
285
|
-
* @param position The camera's position in the sorter's local coordinate space.
|
|
286
|
-
* @param direction The camera's forward direction in the sorter's local coordinate space.
|
|
287
|
-
*/
|
|
288
|
-
setCamera(r, e) {
|
|
289
|
-
const o = this.lastCameraPosition.distanceToSquared(r) > 1e-7, s = this.lastCameraDirection.dot(e) < 0.9999;
|
|
290
|
-
if (!o && !s)
|
|
291
|
-
return;
|
|
292
|
-
this.lastCameraPosition.copy(r), this.lastCameraDirection.copy(e);
|
|
293
|
-
const a = {
|
|
294
|
-
cameraPosition: { x: r.x, y: r.y, z: r.z },
|
|
295
|
-
cameraDirection: { x: e.x, y: e.y, z: e.z }
|
|
296
|
-
};
|
|
297
|
-
this.worker.postMessage(a);
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Terminates the Web Worker and cleans up resources.
|
|
301
|
-
*/
|
|
302
|
-
dispose() {
|
|
303
|
-
this.worker && this.worker.terminate(), this.orderTexture = null, this.centers = null, this.chunks = null;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Creates the self-contained code for the sorting Web Worker.
|
|
307
|
-
* @returns A string containing the JavaScript code for the worker.
|
|
308
|
-
*/
|
|
309
|
-
createWorkerCode() {
|
|
310
|
-
return `(${(function() {
|
|
311
|
-
let e = null, o = null, s = null, a = null, i = null, c = null, h = !1;
|
|
312
|
-
const v = { x: 0, y: 0, z: 0 }, g = { x: 0, y: 0, z: 0 }, p = { x: 0, y: 0, z: 0 }, u = { x: 0, y: 0, z: 0 };
|
|
313
|
-
let l = null, b = null;
|
|
314
|
-
const A = 32, O = new Array(A).fill(0), q = new Array(A).fill(0), R = new Array(A).fill(0), E = (F, z, _) => {
|
|
315
|
-
for (; F <= z; ) {
|
|
316
|
-
const M = z + F >> 1, m = _(M);
|
|
317
|
-
if (m > 0) F = M + 1;
|
|
318
|
-
else if (m < 0) z = M - 1;
|
|
319
|
-
else return M;
|
|
320
|
-
}
|
|
321
|
-
return ~F;
|
|
322
|
-
}, W = () => {
|
|
323
|
-
if (!e || !o || !i || !c)
|
|
324
|
-
return;
|
|
325
|
-
if (o.length === 0) {
|
|
326
|
-
const y = {
|
|
327
|
-
order: e.buffer,
|
|
328
|
-
count: 0
|
|
329
|
-
};
|
|
330
|
-
self.postMessage(y, [e.buffer]), e = null;
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const F = i.x, z = i.y, _ = i.z, M = c.x, m = c.y, B = c.z, P = 1e-4, V = Math.abs(F - v.x) > P || Math.abs(z - v.y) > P || Math.abs(_ - v.z) > P, x = Math.abs(M - g.x) > P || Math.abs(m - g.y) > P || Math.abs(B - g.z) > P;
|
|
334
|
-
if (!h && !V && !x)
|
|
335
|
-
return;
|
|
336
|
-
h = !1, v.x = F, v.y = z, v.z = _, g.x = M, g.y = m, g.z = B;
|
|
337
|
-
let d = 1 / 0, C = -1 / 0;
|
|
338
|
-
for (let y = 0; y < 8; ++y) {
|
|
339
|
-
const T = y & 1 ? p.x : u.x, U = y & 2 ? p.y : u.y, w = y & 4 ? p.z : u.z, D = T * M + U * m + w * B;
|
|
340
|
-
d = Math.min(d, D), C = Math.max(C, D);
|
|
341
|
-
}
|
|
342
|
-
const I = o.length / 3, L = C - d, S = (1 << Math.max(
|
|
343
|
-
10,
|
|
344
|
-
Math.min(20, Math.ceil(Math.log2(I / 4)))
|
|
345
|
-
)) + 1;
|
|
346
|
-
if ((!l || l.length !== I) && (l = new Uint32Array(I)), !b || b.length !== S ? b = new Uint32Array(S) : b.fill(0), L < 1e-7) {
|
|
347
|
-
for (let y = 0; y < I; ++y) l[y] = 0;
|
|
348
|
-
b[0] = I;
|
|
349
|
-
} else if (s && s.length > 0) {
|
|
350
|
-
const y = s.length / 6;
|
|
351
|
-
O.fill(0);
|
|
352
|
-
for (let w = 0; w < y; ++w) {
|
|
353
|
-
const D = w * 6, G = s[D + 0], Z = s[D + 1], J = s[D + 2], H = s[D + 3], N = G * M + Z * m + J * B - d, K = N - H, $ = N + H, X = Math.max(
|
|
354
|
-
0,
|
|
355
|
-
Math.floor(K * A / L)
|
|
356
|
-
), tt = Math.min(
|
|
357
|
-
A,
|
|
358
|
-
Math.ceil($ * A / L)
|
|
359
|
-
);
|
|
360
|
-
for (let j = X; j < tt; ++j)
|
|
361
|
-
O[j]++;
|
|
362
|
-
}
|
|
363
|
-
let T = 0;
|
|
364
|
-
for (let w = 0; w < A; ++w) T += O[w];
|
|
365
|
-
R[0] = 0, q[0] = 0;
|
|
366
|
-
for (let w = 1; w < A; ++w)
|
|
367
|
-
R[w - 1] = O[w - 1] / T * S >>> 0, q[w] = q[w - 1] + R[w - 1];
|
|
368
|
-
R[A - 1] = O[A - 1] / T * S >>> 0;
|
|
369
|
-
const U = L / A;
|
|
370
|
-
for (let w = 0; w < I; ++w) {
|
|
371
|
-
const D = w * 3, G = o[D + 0], Z = o[D + 1], J = o[D + 2], H = G * M + Z * m + J * B, K = (C - H) / U, $ = Math.max(
|
|
372
|
-
0,
|
|
373
|
-
Math.min(
|
|
374
|
-
A - 1,
|
|
375
|
-
Math.floor(K)
|
|
376
|
-
)
|
|
377
|
-
), X = K - $, tt = q[$] + R[$] * X >>> 0, j = Math.min(tt, S - 1);
|
|
378
|
-
l[w] = j, b[j]++;
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
const y = (S - 1) / L;
|
|
382
|
-
for (let T = 0; T < I; ++T) {
|
|
383
|
-
const U = T * 3, w = o[U + 0], D = o[U + 1], G = o[U + 2], Z = w * M + D * m + G * B, H = (C - Z) * y >>> 0, N = Math.min(H, S - 1);
|
|
384
|
-
l[T] = N, b[N]++;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
for (let y = 1; y < S; y++)
|
|
388
|
-
b[y] += b[y - 1];
|
|
389
|
-
for (let y = I - 1; y >= 0; y--) {
|
|
390
|
-
const T = l[y], U = --b[T];
|
|
391
|
-
e[U] = a ? a[y] : y;
|
|
392
|
-
}
|
|
393
|
-
const at = F * M + z * m + _ * B, rt = (y) => {
|
|
394
|
-
if (!e) return -1 / 0;
|
|
395
|
-
const T = e[y], U = T;
|
|
396
|
-
if (!o || U * 3 + 2 >= o.length)
|
|
397
|
-
return -1 / 0;
|
|
398
|
-
const w = U * 3;
|
|
399
|
-
return o[w] * M + o[w + 1] * m + o[w + 2] * B - at;
|
|
400
|
-
};
|
|
401
|
-
let st = I;
|
|
402
|
-
if (I > 0 && rt(I - 1) < 0) {
|
|
403
|
-
const y = E(
|
|
404
|
-
0,
|
|
405
|
-
I - 1,
|
|
406
|
-
rt
|
|
407
|
-
);
|
|
408
|
-
st = y < 0 ? ~y : y;
|
|
409
|
-
}
|
|
410
|
-
const it = {
|
|
411
|
-
order: e.buffer,
|
|
412
|
-
count: st
|
|
413
|
-
};
|
|
414
|
-
self.postMessage(it, [e.buffer]), e = null;
|
|
415
|
-
};
|
|
416
|
-
self.onmessage = (F) => {
|
|
417
|
-
const z = F.data;
|
|
418
|
-
z.order && (e = new Uint32Array(z.order));
|
|
419
|
-
let _ = !1;
|
|
420
|
-
if (z.centers && (o = new Float32Array(z.centers), _ = !0, h = !0), Object.prototype.hasOwnProperty.call(z, "mapping") && (a = z.mapping ? new Uint32Array(z.mapping) : null, h = !0), z.chunks) {
|
|
421
|
-
if (s = new Float32Array(z.chunks), s.length > 0 && s[3] > 0)
|
|
422
|
-
for (let M = 0; M < s.length / 6; ++M) {
|
|
423
|
-
const m = M * 6, B = s[m + 0], P = s[m + 1], V = s[m + 2], x = s[m + 3], d = s[m + 4], C = s[m + 5];
|
|
424
|
-
s[m + 0] = (B + x) * 0.5, s[m + 1] = (P + d) * 0.5, s[m + 2] = (V + C) * 0.5, s[m + 3] = Math.sqrt(
|
|
425
|
-
(x - B) ** 2 + (d - P) ** 2 + (C - V) ** 2
|
|
426
|
-
) * 0.5;
|
|
427
|
-
}
|
|
428
|
-
h = !0;
|
|
429
|
-
}
|
|
430
|
-
if (_ && o && o.length > 0) {
|
|
431
|
-
p.x = u.x = o[0], p.y = u.y = o[1], p.z = u.z = o[2];
|
|
432
|
-
for (let M = 1; M < o.length / 3; M++) {
|
|
433
|
-
const m = M * 3;
|
|
434
|
-
p.x = Math.min(p.x, o[m + 0]), u.x = Math.max(u.x, o[m + 0]), p.y = Math.min(p.y, o[m + 1]), u.y = Math.max(u.y, o[m + 1]), p.z = Math.min(p.z, o[m + 2]), u.z = Math.max(u.z, o[m + 2]);
|
|
435
|
-
}
|
|
436
|
-
} else _ && o && o.length === 0 && (p.x = u.x = p.y = u.y = p.z = u.z = 0);
|
|
437
|
-
z.cameraPosition && (i = z.cameraPosition), z.cameraDirection && (c = z.cameraDirection), W();
|
|
438
|
-
};
|
|
439
|
-
}).toString()})();`;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
const dt = (k, t) => {
|
|
443
|
-
const r = Math.max(
|
|
444
|
-
0,
|
|
445
|
-
Math.min(65535, Math.round((k + 1) / 2 * 65535))
|
|
446
|
-
), e = Math.max(
|
|
447
|
-
0,
|
|
448
|
-
Math.min(65535, Math.round((t + 1) / 2 * 65535))
|
|
449
|
-
);
|
|
450
|
-
return r << 16 | e;
|
|
451
|
-
}, ft = new ArrayBuffer(4), ot = new DataView(ft), pt = (k) => (ot.setUint32(0, k, !0), ot.getFloat32(0, !0));
|
|
452
|
-
class mt {
|
|
453
|
-
/**
|
|
454
|
-
* Create a new TextureManager for a set of splats
|
|
455
|
-
* @param splatData The splat data to manage textures for
|
|
456
|
-
*/
|
|
457
|
-
constructor(t) {
|
|
458
|
-
f(this, "transformA");
|
|
459
|
-
// position xyz and rotation quaternion y,z components
|
|
460
|
-
f(this, "transformB");
|
|
461
|
-
// rotation quaternion x and scale xyz
|
|
462
|
-
f(this, "colorTexture");
|
|
463
|
-
// color an opacity
|
|
464
|
-
f(this, "orderTexture");
|
|
465
|
-
f(this, "textureWidth");
|
|
466
|
-
f(this, "textureHeight");
|
|
467
|
-
const r = t.numSplats;
|
|
468
|
-
this.textureWidth = Math.ceil(Math.sqrt(r)), this.textureHeight = Math.ceil(r / this.textureWidth), this.transformA = this.createTransformATexture(t), this.transformB = this.createTransformBTexture(t), this.colorTexture = this.createColorTexture(t), this.orderTexture = this.createOrderTexture(r);
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Create the transform A texture (positions and quaternion w component)
|
|
472
|
-
* @param splatData The splat data
|
|
473
|
-
* @returns DataTexture containing position data
|
|
474
|
-
*/
|
|
475
|
-
createTransformATexture(t) {
|
|
476
|
-
const r = t.numSplats, e = new Float32Array(
|
|
477
|
-
this.textureWidth * this.textureHeight * 4
|
|
478
|
-
);
|
|
479
|
-
for (let s = 0; s < r; s++) {
|
|
480
|
-
const a = s * 4, i = s * 3, c = s * 4;
|
|
481
|
-
e[a] = t.positions[i], e[a + 1] = t.positions[i + 1], e[a + 2] = t.positions[i + 2];
|
|
482
|
-
const h = t.rotations[c + 1], v = t.rotations[c + 2], g = dt(h, v);
|
|
483
|
-
e[a + 3] = pt(g);
|
|
484
|
-
}
|
|
485
|
-
const o = new n.DataTexture(
|
|
486
|
-
e,
|
|
487
|
-
this.textureWidth,
|
|
488
|
-
this.textureHeight,
|
|
489
|
-
n.RGBAFormat,
|
|
490
|
-
n.FloatType
|
|
491
|
-
// Store as Float32, shader will reinterpret bits
|
|
492
|
-
);
|
|
493
|
-
return o.needsUpdate = !0, o.magFilter = n.NearestFilter, o.minFilter = n.NearestFilter, o;
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Create the transform B texture (scale and rotation xyz)
|
|
497
|
-
* @param splatData The splat data
|
|
498
|
-
* @returns DataTexture containing scale and rotation data
|
|
499
|
-
*/
|
|
500
|
-
createTransformBTexture(t) {
|
|
501
|
-
const r = t.numSplats, e = new Float32Array(
|
|
502
|
-
this.textureWidth * this.textureHeight * 4
|
|
503
|
-
);
|
|
504
|
-
for (let s = 0; s < r; s++) {
|
|
505
|
-
const a = s * 4, i = s * 3, c = s * 4;
|
|
506
|
-
e[a] = t.scales[i], e[a + 1] = t.scales[i + 1], e[a + 2] = t.scales[i + 2], e[a + 3] = t.rotations[c];
|
|
507
|
-
}
|
|
508
|
-
const o = new n.DataTexture(
|
|
509
|
-
e,
|
|
510
|
-
this.textureWidth,
|
|
511
|
-
this.textureHeight,
|
|
512
|
-
n.RGBAFormat,
|
|
513
|
-
n.FloatType
|
|
514
|
-
);
|
|
515
|
-
return o.needsUpdate = !0, o.magFilter = n.NearestFilter, o.minFilter = n.NearestFilter, o;
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Create the color texture (RGB and opacity)
|
|
519
|
-
* @param splatData The splat data
|
|
520
|
-
* @returns DataTexture containing color data
|
|
521
|
-
*/
|
|
522
|
-
createColorTexture(t) {
|
|
523
|
-
const r = t.numSplats, e = new Float32Array(
|
|
524
|
-
this.textureWidth * this.textureHeight * 4
|
|
525
|
-
);
|
|
526
|
-
for (let s = 0; s < r; s++) {
|
|
527
|
-
const a = s * 4, i = s * 3;
|
|
528
|
-
e[a] = t.colors[i], e[a + 1] = t.colors[i + 1], e[a + 2] = t.colors[i + 2], e[a + 3] = t.opacities[s];
|
|
529
|
-
}
|
|
530
|
-
const o = new n.DataTexture(
|
|
531
|
-
e,
|
|
532
|
-
this.textureWidth,
|
|
533
|
-
this.textureHeight,
|
|
534
|
-
n.RGBAFormat,
|
|
535
|
-
n.FloatType
|
|
536
|
-
);
|
|
537
|
-
return o.needsUpdate = !0, o;
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Create the order texture for sorting
|
|
541
|
-
* @param numSplats Number of splats
|
|
542
|
-
* @returns DataTexture for storing order indices
|
|
543
|
-
*/
|
|
544
|
-
createOrderTexture(t) {
|
|
545
|
-
const r = new Uint32Array(this.textureWidth * this.textureHeight);
|
|
546
|
-
for (let o = 0; o < t; o++)
|
|
547
|
-
r[o] = o;
|
|
548
|
-
const e = new n.DataTexture(
|
|
549
|
-
r,
|
|
550
|
-
this.textureWidth,
|
|
551
|
-
this.textureHeight,
|
|
552
|
-
n.RedIntegerFormat,
|
|
553
|
-
n.UnsignedIntType
|
|
554
|
-
);
|
|
555
|
-
return e.needsUpdate = !0, e;
|
|
556
|
-
}
|
|
557
|
-
dispose() {
|
|
558
|
-
this.transformA.dispose(), this.transformB.dispose(), this.colorTexture.dispose(), this.orderTexture.dispose();
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
const xt = (
|
|
562
|
-
/* glsl */
|
|
563
|
-
`
|
|
564
|
-
precision highp float;
|
|
565
|
-
precision highp int;
|
|
566
|
-
precision highp usampler2D;
|
|
567
|
-
precision highp sampler2D;
|
|
568
|
-
|
|
569
|
-
// --- Uniforms (Provided by Three.js) ---
|
|
570
|
-
// uniform mat4 modelViewMatrix; // Don't redeclare
|
|
571
|
-
// uniform mat4 projectionMatrix; // Don't redeclare
|
|
572
|
-
uniform vec2 viewport; // Viewport dimensions (width, height)
|
|
573
|
-
uniform uint numSplats; // Total number of splats potentially renderable by sorter
|
|
574
|
-
|
|
575
|
-
// Textures
|
|
576
|
-
uniform highp sampler2D transformA; // vec4: pos.xyz, packed_rot.yz
|
|
577
|
-
uniform highp sampler2D transformB; // vec4: log_scale.xyz, rot.x
|
|
578
|
-
uniform highp sampler2D splatColor; // vec4: color.rgb, opacity
|
|
579
|
-
uniform highp usampler2D splatOrder; // uint: original splat index
|
|
580
|
-
|
|
581
|
-
// --- Attributes ---
|
|
582
|
-
// Instancing attribute: base index for this block of splats
|
|
583
|
-
attribute uint splatInstanceIndex;
|
|
584
|
-
|
|
585
|
-
// Per-vertex attribute of the quad (Built-in, don't redeclare 'position')
|
|
586
|
-
// attribute vec3 position; // Use the built-in 'position'
|
|
587
|
-
// position.xy: corner coords (-1..1)
|
|
588
|
-
// position.z: index within the instance block (0..INSTANCE_BUFFER_SIZE-1)
|
|
589
|
-
|
|
590
|
-
// --- Varyings ---
|
|
591
|
-
varying vec4 vColor;
|
|
592
|
-
varying vec2 vUv; // For gaussian calculation in fragment shader
|
|
593
|
-
|
|
594
|
-
// --- Constants ---
|
|
595
|
-
#define INSTANCE_BUFFER_SIZE 128.0 // Must match the size in SplatMesh
|
|
596
|
-
|
|
597
|
-
// --- Helper Functions ---
|
|
598
|
-
|
|
599
|
-
// Unpack two 16-bit floats (halfs) from a single 32-bit float (packed as uint bits)
|
|
600
|
-
vec2 unpackHalf2x16FromFloat(float packedFloat) {
|
|
601
|
-
uint packedUInt = floatBitsToUint(packedFloat);
|
|
602
|
-
uint y_bits = packedUInt >> 16;
|
|
603
|
-
uint z_bits = packedUInt & uint(0xFFFF);
|
|
604
|
-
|
|
605
|
-
// NOTE: This assumes packing used "packNormalizedFloatToUint16Pair"
|
|
606
|
-
// which maps [-1, 1] to [0, 65535]. Adjust if packing method changes.
|
|
607
|
-
float y = (float(y_bits) / 65535.0) * 2.0 - 1.0;
|
|
608
|
-
float z = (float(z_bits) / 65535.0) * 2.0 - 1.0;
|
|
609
|
-
return vec2(y, z);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Reconstruct quaternion from packed components (x, yz_packed)
|
|
613
|
-
vec4 unpackRotation(float yz_packed, float x) {
|
|
614
|
-
vec2 yz = unpackHalf2x16FromFloat(yz_packed);
|
|
615
|
-
float y = yz.x;
|
|
616
|
-
float z = yz.y;
|
|
617
|
-
float w_squared = 1.0 - x*x - y*y - z*z;
|
|
618
|
-
float w = (w_squared < 0.0) ? 0.0 : sqrt(w_squared); // Calculate w
|
|
619
|
-
return vec4(x, y, z, w);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Convert quaternion to 3x3 rotation matrix
|
|
623
|
-
mat3 quatToMat3(vec4 q) {
|
|
624
|
-
float x = q.x, y = q.y, z = q.z, w = q.w;
|
|
625
|
-
float x2 = x + x, y2 = y + y, z2 = z + z;
|
|
626
|
-
float xx = x * x2, xy = x * y2, xz = x * z2;
|
|
627
|
-
float yy = y * y2, yz = y * z2, zz = z * z2;
|
|
628
|
-
float wx = w * x2, wy = w * y2, wz = w * z2;
|
|
629
|
-
|
|
630
|
-
return mat3(
|
|
631
|
-
1.0 - (yy + zz), xy + wz, xz - wy,
|
|
632
|
-
xy - wz, 1.0 - (xx + zz), yz + wx,
|
|
633
|
-
xz + wy, yz - wx, 1.0 - (xx + yy)
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Calculate model-space covariance components R * S^2 * transpose(R)
|
|
638
|
-
void getModelCovariance(vec3 scale, vec4 rotation, out vec3 covA, out vec3 covB) {
|
|
639
|
-
mat3 R = quatToMat3(rotation);
|
|
640
|
-
|
|
641
|
-
// Define M = R * S (where S is the diagonal scale matrix)
|
|
642
|
-
// Note R is column-major, so R[0] is the first column, etc.
|
|
643
|
-
// We want M's rows to be R's rows scaled by the corresponding scale factor.
|
|
644
|
-
mat3 M = mat3(
|
|
645
|
-
R[0][0] * scale.x, R[0][1] * scale.y, R[0][2] * scale.z, // Row 0 = (R_row0 * S)
|
|
646
|
-
R[1][0] * scale.x, R[1][1] * scale.y, R[1][2] * scale.z, // Row 1 = (R_row1 * S)
|
|
647
|
-
R[2][0] * scale.x, R[2][1] * scale.y, R[2][2] * scale.z // Row 2 = (R_row2 * S)
|
|
648
|
-
);
|
|
649
|
-
|
|
650
|
-
// Now compute Vrk = M * transpose(M)
|
|
651
|
-
// Vrk[i][j] = dot(M[i], M[j]) where M[i] is the i-th row vector of M.
|
|
652
|
-
covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2])); // Vrk[0][0], Vrk[0][1], Vrk[0][2]
|
|
653
|
-
covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2])); // Vrk[1][1], Vrk[1][2], Vrk[2][2]
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// --- Main Function ---
|
|
657
|
-
void main(void) {
|
|
658
|
-
// Calculate the final splat index for this vertex
|
|
659
|
-
uint instanceOffset = uint(position.z); // Read index within instance from Z
|
|
660
|
-
uint orderedSplatIndex = splatInstanceIndex + instanceOffset;
|
|
661
|
-
|
|
662
|
-
// Discard if this splat index is beyond the sorted count
|
|
663
|
-
if (orderedSplatIndex >= numSplats) {
|
|
664
|
-
gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Off-screen
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// --- Source Data Lookup ---
|
|
669
|
-
uint texWidth = uint(textureSize(splatOrder, 0).x);
|
|
670
|
-
ivec2 orderUV = ivec2(orderedSplatIndex % texWidth, orderedSplatIndex / texWidth);
|
|
671
|
-
uint originalSplatIndex = texelFetch(splatOrder, orderUV, 0).r;
|
|
672
|
-
|
|
673
|
-
ivec2 dataUV = ivec2(originalSplatIndex % texWidth, originalSplatIndex / texWidth);
|
|
674
|
-
|
|
675
|
-
// Fetch data from textures
|
|
676
|
-
vec4 texTransformA = texelFetch(transformA, dataUV, 0);
|
|
677
|
-
vec4 texTransformB = texelFetch(transformB, dataUV, 0);
|
|
678
|
-
vec4 texColor = texelFetch(splatColor, dataUV, 0);
|
|
679
|
-
|
|
680
|
-
vec3 splatPosition = texTransformA.xyz;
|
|
681
|
-
vec3 splatLogScale = texTransformB.xyz; // Assume stored as log scale
|
|
682
|
-
vec4 splatRotation = unpackRotation(texTransformA.w, texTransformB.w); // Unpack rot yz, x
|
|
683
|
-
vec3 splatColor = texColor.rgb;
|
|
684
|
-
float splatOpacity = texColor.a;
|
|
685
|
-
|
|
686
|
-
vec3 splatScale = exp(splatLogScale); // Convert log scale to actual scale
|
|
687
|
-
|
|
688
|
-
// --- Center Projection ---
|
|
689
|
-
|
|
690
|
-
// Compute normalized view-space center
|
|
691
|
-
vec4 centerClipRaw = modelViewMatrix * vec4(splatPosition, 1.0);
|
|
692
|
-
vec3 centerView = centerClipRaw.xyz / centerClipRaw.w;
|
|
693
|
-
|
|
694
|
-
// Early out if splat is behind the camera
|
|
695
|
-
if (centerView.z > -0.2) { // Use small epsilon
|
|
696
|
-
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Project to clip space
|
|
701
|
-
vec4 centerClip = projectionMatrix * centerClipRaw;
|
|
702
|
-
|
|
703
|
-
// --- Covariance Calculation & Projection ---
|
|
704
|
-
|
|
705
|
-
// Calculating model space covariance (Vrk)
|
|
706
|
-
vec3 covA, covB;
|
|
707
|
-
getModelCovariance(splatScale, splatRotation, covA, covB);
|
|
708
|
-
mat3 Vrk = mat3(
|
|
709
|
-
covA.x, covA.y, covA.z,
|
|
710
|
-
covA.y, covB.x, covB.y, // covB.x is Vrk[1][1]
|
|
711
|
-
covA.z, covB.y, covB.z // covB.y is Vrk[1][2], covB.z is Vrk[2][2]
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
// Calculate Jacobian J (screen space change wrt view space change)
|
|
715
|
-
// Requres focal lenghts and view-space center depth (tz)
|
|
716
|
-
float focalX = viewport.x * projectionMatrix[0][0];
|
|
717
|
-
float focalY = viewport.y * projectionMatrix[1][1];
|
|
718
|
-
float tz = centerView.z;
|
|
719
|
-
float tz2 = tz * tz;
|
|
720
|
-
|
|
721
|
-
// Jacobian J = [fx/tz, 0, -fx*tx/tz^2]
|
|
722
|
-
// [0, fy/tz, -fy*ty/tz^2]
|
|
723
|
-
mat3 J = mat3(
|
|
724
|
-
focalX / tz, 0.0, -focalX * centerView.x / tz2,
|
|
725
|
-
0.0, focalY / tz, -focalY * centerView.y / tz2,
|
|
726
|
-
0.0, 0.0, 0.0
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
// Calculate W
|
|
730
|
-
mat3 W = transpose(mat3(modelViewMatrix));
|
|
731
|
-
// Calculate T = W * J
|
|
732
|
-
mat3 T = W * J;
|
|
733
|
-
// Calculate Projected 2D Covariance: SigmaProj = transpose(T) * Vrk * T
|
|
734
|
-
mat3 SigmaProj = transpose(T) * Vrk * T;
|
|
735
|
-
|
|
736
|
-
// Extract 2D components and add bias
|
|
737
|
-
float cov2D_11 = SigmaProj[0][0] + 0.3;
|
|
738
|
-
float cov2D_12 = SigmaProj[0][1];
|
|
739
|
-
float cov2D_22 = SigmaProj[1][1] + 0.3;
|
|
740
|
-
|
|
741
|
-
// --- Calculate 2D Screen Space Rotation & Scale ---
|
|
742
|
-
|
|
743
|
-
float det = cov2D_11 * cov2D_22 - cov2D_12 * cov2D_12;
|
|
744
|
-
// Ensure determinant is non-negative before sqrt
|
|
745
|
-
if (det <= 0.0) {
|
|
746
|
-
gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Discard degenerated
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
float trace = cov2D_11 + cov2D_22;
|
|
751
|
-
float traceOver2 = 0.5 * trace;
|
|
752
|
-
float discriminantSqrt = sqrt(max(traceOver2 * traceOver2 - det, 0.0)); // Avoid sqrt(negative)
|
|
753
|
-
|
|
754
|
-
float lambda1 = traceOver2 + discriminantSqrt; // Larger eigenvalue
|
|
755
|
-
float lambda2 = max(0.1, traceOver2 - discriminantSqrt); // Smaller eigenvalue, clamped
|
|
756
|
-
|
|
757
|
-
// Compute eigenvectors
|
|
758
|
-
|
|
759
|
-
vec2 v1_eigen, v2_eigen;
|
|
760
|
-
|
|
761
|
-
// Handle diagonal case
|
|
762
|
-
if(abs(cov2D_12) < 1e-16) {
|
|
763
|
-
v1_eigen = vec2(1.0, 0.0);
|
|
764
|
-
} else {
|
|
765
|
-
v1_eigen = normalize(vec2(cov2D_12, lambda1 - cov2D_11)); // diagonal choice
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
v2_eigen = vec2(-v1_eigen.y, v1_eigen.x); // Perpendicular eigenvector
|
|
769
|
-
|
|
770
|
-
// Calculate SCALED vectors (l1, l2, incorparate factor)
|
|
771
|
-
float scaleFactor = 2.0 * sqrt(2.0); // ~2.8284
|
|
772
|
-
float l1 = sqrt(lambda1) * scaleFactor; // scaleX
|
|
773
|
-
float l2 = sqrt(lambda2) * scaleFactor; // scaleY
|
|
774
|
-
|
|
775
|
-
// Early out tiny splats
|
|
776
|
-
if (l1 < 2.0 && l2 < 2.0) { // Check if smaller than ~2 pixel
|
|
777
|
-
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// Scaled eigenvectors for offset calculation
|
|
782
|
-
vec2 v1_scaled = l1 * v1_eigen;
|
|
783
|
-
vec2 v2_scaled = l2 * v2_eigen;
|
|
784
|
-
|
|
785
|
-
// --- FRUSTUM CHECK ---
|
|
786
|
-
|
|
787
|
-
vec2 clipRadius = vec2(max(l1, l2)) * (centerClip.w / viewport);
|
|
788
|
-
|
|
789
|
-
// Check if the bounding circle's edge is outside the [-w, w] range in clip space for x or y
|
|
790
|
-
if (any(greaterThan(abs(centerClip.xy) + clipRadius, vec2(abs(centerClip.w))))) {
|
|
791
|
-
gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Off-screen
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// --- END FRUSTUM CHECK ---
|
|
796
|
-
|
|
797
|
-
// --- Final Vertex Position ---
|
|
798
|
-
|
|
799
|
-
vec2 cornerOffset = position.xy; // (-1,-1) to (1,1)
|
|
800
|
-
|
|
801
|
-
// Clip the quad so only high-alpha core is visible
|
|
802
|
-
float alpha = max(splatOpacity, 1e-6); // avoid log(0)
|
|
803
|
-
float clip = min(1.0, sqrt(-log(1.0 / 255.0 / alpha)) / 2.0);
|
|
804
|
-
|
|
805
|
-
// Apply clip to the corner offset *before* calculating screen space offset
|
|
806
|
-
vec2 clippedCornerOffset = cornerOffset * clip;
|
|
807
|
-
vec2 screenOffsetPixels = clippedCornerOffset.x * v1_scaled + clippedCornerOffset.y * v2_scaled;
|
|
808
|
-
//vec2 screenOffsetPixels = vec2(clippedCornerOffset.x, clippedCornerOffset.y);
|
|
809
|
-
|
|
810
|
-
// Convert pixel offset to clip space offset
|
|
811
|
-
vec2 clipOffset = screenOffsetPixels * (centerClip.w / viewport);
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
// Apply offset to center clip position
|
|
815
|
-
gl_Position = centerClip + vec4(clipOffset, 0.0, 0.0);
|
|
816
|
-
|
|
817
|
-
// --- Pass data to Fragment Shader ---
|
|
818
|
-
|
|
819
|
-
vColor = vec4(splatColor, splatOpacity);
|
|
820
|
-
vUv = clippedCornerOffset;
|
|
821
|
-
}
|
|
822
|
-
`
|
|
823
|
-
), yt = (
|
|
824
|
-
/* glsl */
|
|
825
|
-
`
|
|
826
|
-
precision highp float;
|
|
827
|
-
|
|
828
|
-
varying vec4 vColor; // Color and opacity passed from vertex shader
|
|
829
|
-
varying vec2 vUv; // Quad UV coordinates (-1 to 1) passed from vertex shader
|
|
830
|
-
|
|
831
|
-
void main(void) {
|
|
832
|
-
|
|
833
|
-
float distSq = dot(vUv, vUv); // Calculate squared distance from center (in the quad's coordinate system)
|
|
834
|
-
if (distSq > 1.0) discard; // Discard fragments outside the circle inscribed in the quad
|
|
835
|
-
|
|
836
|
-
// Calculate Gaussian function: alpha = opacity * exp(-distSq * factor)
|
|
837
|
-
// The factor 4.0 corresponds to the original implementation's scaling.
|
|
838
|
-
// factor = 1 / (2 * sigma^2), where sigma controls the spread.
|
|
839
|
-
// Using 4.0 implies sigma^2 = 1/8.
|
|
840
|
-
float alpha = exp(-distSq * 4.0) * vColor.a;
|
|
841
|
-
|
|
842
|
-
if (alpha < 1.0 / 255.0) discard; // Discard fragments with very low alpha
|
|
843
|
-
|
|
844
|
-
// Premultiply color by alpha (required for correct blending)
|
|
845
|
-
gl_FragColor = vec4(vColor.rgb * alpha, alpha);
|
|
846
|
-
}
|
|
847
|
-
`
|
|
848
|
-
);
|
|
849
|
-
class gt extends n.ShaderMaterial {
|
|
850
|
-
constructor(t = {}) {
|
|
851
|
-
const r = {
|
|
852
|
-
// Textures (values set via methods)
|
|
853
|
-
transformA: { value: null },
|
|
854
|
-
transformB: { value: null },
|
|
855
|
-
splatColor: { value: null },
|
|
856
|
-
splatOrder: { value: null },
|
|
857
|
-
// Other uniforms
|
|
858
|
-
viewport: { value: new n.Vector2(1, 1) },
|
|
859
|
-
// Will be updated
|
|
860
|
-
numSplats: { value: 0 }
|
|
861
|
-
// Max splats to render (updated by sorter)
|
|
862
|
-
};
|
|
863
|
-
super({
|
|
864
|
-
vertexShader: xt,
|
|
865
|
-
fragmentShader: yt,
|
|
866
|
-
uniforms: r,
|
|
867
|
-
transparent: !0,
|
|
868
|
-
blending: n.CustomBlending,
|
|
869
|
-
// Premultiplied alpha blending:
|
|
870
|
-
// color = src_color * src_alpha + dst_color * (1 - src_alpha)
|
|
871
|
-
// alpha = src_alpha * 1 + dst_alpha * (1 - src_alpha)
|
|
872
|
-
blendSrc: n.OneFactor,
|
|
873
|
-
// Using ONE because shader outputs premultiplied color (color * alpha)
|
|
874
|
-
blendDst: n.OneMinusSrcAlphaFactor,
|
|
875
|
-
blendSrcAlpha: n.OneFactor,
|
|
876
|
-
// source alpha comes from shader
|
|
877
|
-
blendDstAlpha: n.OneMinusSrcAlphaFactor,
|
|
878
|
-
blendEquation: n.AddEquation,
|
|
879
|
-
blendEquationAlpha: n.AddEquation,
|
|
880
|
-
depthTest: !0,
|
|
881
|
-
depthWrite: !1,
|
|
882
|
-
// Disable depth write for transparency
|
|
883
|
-
side: n.DoubleSide,
|
|
884
|
-
// Render both sides (or CULLFACE_NONE equivalent)
|
|
885
|
-
// Optional settings from constructor
|
|
886
|
-
alphaTest: t.alphaTest !== void 0 ? t.alphaTest : 0,
|
|
887
|
-
// Typically 0 for blending
|
|
888
|
-
toneMapped: t.toneMapped !== void 0 ? t.toneMapped : !1
|
|
889
|
-
// prettier-ignore
|
|
890
|
-
}), t.alphaHash && (this.alphaHash = !0, this.depthWrite = !0, this.blending = n.NoBlending);
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Update the viewport size
|
|
894
|
-
* @param width Viewport width
|
|
895
|
-
* @param height Viewport height
|
|
896
|
-
*/
|
|
897
|
-
updateViewport(t, r) {
|
|
898
|
-
this.uniforms.viewport.value.set(t, r);
|
|
899
|
-
}
|
|
900
|
-
/**
|
|
901
|
-
* Set transform texture A (positions)
|
|
902
|
-
* @param texture Texture containing positions
|
|
903
|
-
*/
|
|
904
|
-
setTransformA(t) {
|
|
905
|
-
this.uniforms.transformA.value = t;
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* Set transform texture B (rotation, scale)
|
|
909
|
-
* @param texture Texture containing rotation and scale data
|
|
910
|
-
*/
|
|
911
|
-
setTransformB(t) {
|
|
912
|
-
this.uniforms.transformB.value = t;
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Set color texture
|
|
916
|
-
* @param texture Texture containing colors
|
|
917
|
-
*/
|
|
918
|
-
setColorTexture(t) {
|
|
919
|
-
this.uniforms.splatColor.value = t;
|
|
920
|
-
}
|
|
921
|
-
/**
|
|
922
|
-
* Set order texture
|
|
923
|
-
* @param texture Texture containing sort order
|
|
924
|
-
*/
|
|
925
|
-
setOrderTexture(t) {
|
|
926
|
-
this.uniforms.splatOrder.value = t;
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Set number of splats to render
|
|
930
|
-
* @param count Number of splats
|
|
931
|
-
*/
|
|
932
|
-
setNumSplats(t) {
|
|
933
|
-
this.uniforms.numSplats.value = t;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
const Y = class Y extends n.Mesh {
|
|
937
|
-
// Match shader constant
|
|
938
|
-
/**
|
|
939
|
-
* Create a new SplatMesh for rendering Gaussian splats
|
|
940
|
-
* @param splatData The splat data to render
|
|
941
|
-
* @param options Rendering options
|
|
942
|
-
*/
|
|
943
|
-
constructor(r, e = {}) {
|
|
944
|
-
const o = new gt(e), s = Y.createInstancedGeometry(r.numSplats, Y.INSTANCE_SIZE);
|
|
945
|
-
super(s, o);
|
|
946
|
-
f(this, "sorter");
|
|
947
|
-
f(this, "splatData");
|
|
948
|
-
f(this, "options");
|
|
949
|
-
f(this, "textureManager");
|
|
950
|
-
f(this, "material");
|
|
951
|
-
f(this, "geometry");
|
|
952
|
-
f(this, "lastCameraPositionLocal", new n.Vector3());
|
|
953
|
-
f(this, "lastCameraDirectionLocal", new n.Vector3());
|
|
954
|
-
f(this, "invModelMatrix", new n.Matrix4());
|
|
955
|
-
this.geometry = s, this.material = o, this.splatData = r, this.frustumCulled = !1, this.options = { autoSort: !0, ...e }, this.textureManager = new mt(r), this.sorter = new ut();
|
|
956
|
-
let a = this.createChunks() || void 0;
|
|
957
|
-
a === null && console.warn("Visus: Could not create sorter chunks, bounding box might be invalid."), a = void 0, this.sorter.init(
|
|
958
|
-
this.textureManager.orderTexture,
|
|
959
|
-
this.splatData.centers,
|
|
960
|
-
a ?? void 0
|
|
961
|
-
), this.sorter.addEventListener(
|
|
962
|
-
"updated",
|
|
963
|
-
(i) => {
|
|
964
|
-
const c = i.count;
|
|
965
|
-
this.geometry.instanceCount = Math.ceil(c / Y.INSTANCE_SIZE), this.material.setNumSplats(c);
|
|
966
|
-
}
|
|
967
|
-
), this.material.setTransformA(this.textureManager.transformA), this.material.setTransformB(this.textureManager.transformB), this.material.setColorTexture(this.textureManager.colorTexture), this.material.setOrderTexture(this.textureManager.orderTexture), this.material.setNumSplats(0), this.geometry.boundingBox = r.boundingBox.toBox3(), this.geometry.boundingSphere = new n.Sphere(), this.geometry.boundingBox.getBoundingSphere(this.geometry.boundingSphere);
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Creates the instanced geometry for rendering splats.
|
|
971
|
-
* @param totalSplats Total number of splats in the data.
|
|
972
|
-
* @param instanceSize Number of splats per instance.
|
|
973
|
-
* @returns InstancedBufferGeometry
|
|
974
|
-
*/
|
|
975
|
-
static createInstancedGeometry(r, e) {
|
|
976
|
-
const o = Math.ceil(r / e), s = new n.BufferGeometry(), a = new Float32Array([
|
|
977
|
-
// x, y, splat_index_in_instance
|
|
978
|
-
-1,
|
|
979
|
-
-1,
|
|
980
|
-
0,
|
|
981
|
-
1,
|
|
982
|
-
-1,
|
|
983
|
-
0,
|
|
984
|
-
1,
|
|
985
|
-
1,
|
|
986
|
-
0,
|
|
987
|
-
-1,
|
|
988
|
-
1,
|
|
989
|
-
0
|
|
990
|
-
]), i = new Uint16Array([0, 1, 2, 0, 2, 3]), c = new Float32Array(4 * 3 * e);
|
|
991
|
-
for (let p = 0; p < e; p++) {
|
|
992
|
-
const u = p * 4 * 3;
|
|
993
|
-
for (let l = 0; l < 4; l++)
|
|
994
|
-
c[u + l * 3 + 0] = a[l * 3 + 0], c[u + l * 3 + 1] = a[l * 3 + 1], c[u + l * 3 + 2] = p;
|
|
995
|
-
}
|
|
996
|
-
const h = new Uint32Array(6 * e);
|
|
997
|
-
for (let p = 0; p < e; p++) {
|
|
998
|
-
const u = p * 6, l = p * 4;
|
|
999
|
-
h[u + 0] = i[0] + l, h[u + 1] = i[1] + l, h[u + 2] = i[2] + l, h[u + 3] = i[3] + l, h[u + 4] = i[4] + l, h[u + 5] = i[5] + l;
|
|
1000
|
-
}
|
|
1001
|
-
s.setAttribute("position", new n.BufferAttribute(c, 3)), s.setIndex(new n.BufferAttribute(h, 1));
|
|
1002
|
-
const v = new n.InstancedBufferGeometry();
|
|
1003
|
-
v.index = s.index, v.setAttribute("position", s.getAttribute("position"));
|
|
1004
|
-
const g = new Uint32Array(o);
|
|
1005
|
-
for (let p = 0; p < o; p++)
|
|
1006
|
-
g[p] = p * e;
|
|
1007
|
-
return v.setAttribute("splatInstanceIndex", new n.InstancedBufferAttribute(g, 1, !1)), v.instanceCount = 0, v;
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Create chunks data (bounding box min/max) for the sorter.
|
|
1011
|
-
* @returns Float32Array containing chunk data [minX, minY, minZ, maxX, maxY, maxZ] or null.
|
|
1012
|
-
*/
|
|
1013
|
-
createChunks() {
|
|
1014
|
-
const r = this.splatData.boundingBox;
|
|
1015
|
-
return r.min.x === 1 / 0 || r.max.x === -1 / 0 ? null : new Float32Array([
|
|
1016
|
-
r.min.x,
|
|
1017
|
-
r.min.y,
|
|
1018
|
-
r.min.z,
|
|
1019
|
-
r.max.x,
|
|
1020
|
-
r.max.y,
|
|
1021
|
-
r.max.z
|
|
1022
|
-
]);
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Update the viewport size
|
|
1026
|
-
* @param width Viewport width
|
|
1027
|
-
* @param height Viewport height
|
|
1028
|
-
*/
|
|
1029
|
-
updateViewport(r, e) {
|
|
1030
|
-
this.material.updateViewport(r, e);
|
|
1031
|
-
}
|
|
1032
|
-
/**
|
|
1033
|
-
* Sorts splats based on camera position and direction.
|
|
1034
|
-
* @param camera The camera to sort against.
|
|
1035
|
-
*/
|
|
1036
|
-
sort(r) {
|
|
1037
|
-
const e = new n.Vector3(), o = new n.Vector3();
|
|
1038
|
-
r.getWorldPosition(e), r.getWorldDirection(o), this.invModelMatrix.copy(this.matrixWorld).invert();
|
|
1039
|
-
const s = e.applyMatrix4(this.invModelMatrix), a = o.transformDirection(this.invModelMatrix), i = this.lastCameraPositionLocal.distanceToSquared(s) > 1e-6, c = this.lastCameraDirectionLocal.dot(a) < 0.999;
|
|
1040
|
-
this.options.autoSort && (i || c) && (this.lastCameraPositionLocal.copy(s), this.lastCameraDirectionLocal.copy(a), this.sorter.setCamera(s, a));
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* THREE.js hook called before rendering the object.
|
|
1044
|
-
* Used here to trigger sorting and update viewport.
|
|
1045
|
-
* @param renderer The renderer
|
|
1046
|
-
* @param scene The scene
|
|
1047
|
-
* @param camera The camera
|
|
1048
|
-
*/
|
|
1049
|
-
// prettier-ignore
|
|
1050
|
-
// @ts-expect-error scene is not used
|
|
1051
|
-
onBeforeRender(r, e, o) {
|
|
1052
|
-
this.sort(o);
|
|
1053
|
-
const s = r.getSize(new n.Vector2());
|
|
1054
|
-
let { width: a, height: i } = s;
|
|
1055
|
-
const c = r.xr;
|
|
1056
|
-
if (c.enabled && c.isPresenting) {
|
|
1057
|
-
const h = c.getCamera().cameras[0].view;
|
|
1058
|
-
h && (a = h.width, i = h.height);
|
|
1059
|
-
}
|
|
1060
|
-
this.updateViewport(a, i);
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* Dispose of resources
|
|
1064
|
-
*/
|
|
1065
|
-
dispose() {
|
|
1066
|
-
this.sorter.dispose(), this.geometry.dispose(), this.material.dispose(), this.textureManager.dispose();
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
// Cached inverse matrix
|
|
1070
|
-
/** Number of splats combined into a single instanced draw call. */
|
|
1071
|
-
f(Y, "INSTANCE_SIZE", 128);
|
|
1072
|
-
let nt = Y;
|
|
1073
|
-
class wt extends n.Loader {
|
|
1074
|
-
/**
|
|
1075
|
-
* Load a PLY file with Gaussian Splat data
|
|
1076
|
-
* @param url URL of the PLY file
|
|
1077
|
-
* @param onLoad Optional callback when loading is complete
|
|
1078
|
-
* @param onProgress Optional progress callback
|
|
1079
|
-
* @param onError Optional error callback
|
|
1080
|
-
*/
|
|
1081
|
-
load(t, r, e, o) {
|
|
1082
|
-
const s = new n.FileLoader(this.manager);
|
|
1083
|
-
s.setResponseType("arraybuffer"), s.setRequestHeader(this.requestHeader), s.setPath(this.path), s.setWithCredentials(this.withCredentials), s.load(
|
|
1084
|
-
t,
|
|
1085
|
-
(a) => {
|
|
1086
|
-
try {
|
|
1087
|
-
if (r) {
|
|
1088
|
-
const i = this.parse(a);
|
|
1089
|
-
r(i);
|
|
1090
|
-
}
|
|
1091
|
-
} catch (i) {
|
|
1092
|
-
o ? o(i) : console.error(i), this.manager.itemError(t);
|
|
1093
|
-
}
|
|
1094
|
-
},
|
|
1095
|
-
e,
|
|
1096
|
-
o
|
|
1097
|
-
);
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Load a PLY file asynchronously and return a Promise
|
|
1101
|
-
* @param url URL of the PLY file
|
|
1102
|
-
* @param onProgress Optional progress callback
|
|
1103
|
-
* @returns A Promise that resolves with the parsed SplatData
|
|
1104
|
-
*/
|
|
1105
|
-
loadAsync(t, r) {
|
|
1106
|
-
return new Promise((e, o) => {
|
|
1107
|
-
const s = new n.FileLoader(this.manager);
|
|
1108
|
-
s.setResponseType("arraybuffer"), s.setRequestHeader(this.requestHeader), s.setPath(this.path), s.setWithCredentials(this.withCredentials), s.load(
|
|
1109
|
-
t,
|
|
1110
|
-
(a) => {
|
|
1111
|
-
try {
|
|
1112
|
-
const i = this.parse(a);
|
|
1113
|
-
e(i);
|
|
1114
|
-
} catch (i) {
|
|
1115
|
-
o(i), this.manager.itemError(t);
|
|
1116
|
-
}
|
|
1117
|
-
},
|
|
1118
|
-
r,
|
|
1119
|
-
(a) => {
|
|
1120
|
-
o(a), this.manager.itemError(t);
|
|
1121
|
-
}
|
|
1122
|
-
);
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
/**
|
|
1126
|
-
* Parse PLY buffer data into SplatData
|
|
1127
|
-
* @param buffer ArrayBuffer containing PLY data
|
|
1128
|
-
* @returns Parsed SplatData
|
|
1129
|
-
*/
|
|
1130
|
-
parse(t) {
|
|
1131
|
-
const r = new TextDecoder(), e = new Uint8Array(t), o = [112, 108, 121, 10], s = `
|
|
1132
|
-
end_header
|
|
1133
|
-
`;
|
|
1134
|
-
for (let x = 0; x < o.length; x++)
|
|
1135
|
-
if (e[x] !== o[x])
|
|
1136
|
-
throw new Error("Invalid PLY file: Missing magic bytes");
|
|
1137
|
-
let a = 0;
|
|
1138
|
-
for (let x = 0; x < e.length - s.length; x++) {
|
|
1139
|
-
let d = !0;
|
|
1140
|
-
for (let C = 0; C < s.length; C++)
|
|
1141
|
-
if (e[x + C] !== s.charCodeAt(C)) {
|
|
1142
|
-
d = !1;
|
|
1143
|
-
break;
|
|
1144
|
-
}
|
|
1145
|
-
if (d) {
|
|
1146
|
-
a = x + s.length;
|
|
1147
|
-
break;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
if (a === 0)
|
|
1151
|
-
throw new Error("Invalid PLY file: Could not find end of header");
|
|
1152
|
-
const c = r.decode(
|
|
1153
|
-
e.slice(0, a)
|
|
1154
|
-
).split(`
|
|
1155
|
-
`), h = [];
|
|
1156
|
-
let v = null;
|
|
1157
|
-
for (let x = 1; x < c.length; x++) {
|
|
1158
|
-
const d = c[x].trim();
|
|
1159
|
-
if (d === "" || d === "end_header") continue;
|
|
1160
|
-
const C = d.split(" ");
|
|
1161
|
-
switch (C[0]) {
|
|
1162
|
-
case "format":
|
|
1163
|
-
v = C[1];
|
|
1164
|
-
break;
|
|
1165
|
-
case "element":
|
|
1166
|
-
h.push({
|
|
1167
|
-
name: C[1],
|
|
1168
|
-
count: parseInt(C[2], 10),
|
|
1169
|
-
properties: []
|
|
1170
|
-
});
|
|
1171
|
-
break;
|
|
1172
|
-
case "property":
|
|
1173
|
-
if (h.length === 0)
|
|
1174
|
-
throw new Error(
|
|
1175
|
-
"Invalid PLY file: Property without element"
|
|
1176
|
-
);
|
|
1177
|
-
h[h.length - 1].properties.push({
|
|
1178
|
-
type: C[1],
|
|
1179
|
-
name: C[2],
|
|
1180
|
-
storage: null
|
|
1181
|
-
});
|
|
1182
|
-
break;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
if (v !== "binary_little_endian")
|
|
1186
|
-
throw new Error(`Unsupported PLY format: ${v}`);
|
|
1187
|
-
const g = h.find((x) => x.name === "vertex");
|
|
1188
|
-
if (!g)
|
|
1189
|
-
throw new Error("Invalid PLY file: No vertex element found");
|
|
1190
|
-
const p = new ht(g.count), u = new DataView(t);
|
|
1191
|
-
let l = a;
|
|
1192
|
-
const b = (x) => g.properties.findIndex((d) => d.name === x), A = b("x"), O = b("y"), q = b("z"), R = [
|
|
1193
|
-
b("rot_0"),
|
|
1194
|
-
b("rot_1"),
|
|
1195
|
-
b("rot_2"),
|
|
1196
|
-
b("rot_3")
|
|
1197
|
-
], E = [
|
|
1198
|
-
b("scale_0"),
|
|
1199
|
-
b("scale_1"),
|
|
1200
|
-
b("scale_2")
|
|
1201
|
-
], W = [
|
|
1202
|
-
b("f_dc_0"),
|
|
1203
|
-
b("f_dc_1"),
|
|
1204
|
-
b("f_dc_2")
|
|
1205
|
-
], F = b("opacity");
|
|
1206
|
-
if ([
|
|
1207
|
-
A,
|
|
1208
|
-
O,
|
|
1209
|
-
q,
|
|
1210
|
-
...R,
|
|
1211
|
-
...E,
|
|
1212
|
-
...W,
|
|
1213
|
-
F
|
|
1214
|
-
].some((x) => x === -1))
|
|
1215
|
-
throw new Error("Invalid PLY file: Missing required properties");
|
|
1216
|
-
const _ = 0.28209479177387814, M = (x) => {
|
|
1217
|
-
if (x > 0) return 1 / (1 + Math.exp(-x));
|
|
1218
|
-
const d = Math.exp(x);
|
|
1219
|
-
return d / (1 + d);
|
|
1220
|
-
}, m = new n.Vector3(), B = new n.Quaternion(), P = new n.Vector3(), V = new n.Color();
|
|
1221
|
-
for (let x = 0; x < g.count; x++) {
|
|
1222
|
-
const d = [];
|
|
1223
|
-
for (let I = 0; I < g.properties.length; I++) {
|
|
1224
|
-
const Q = g.properties[I].type;
|
|
1225
|
-
let S;
|
|
1226
|
-
switch (Q) {
|
|
1227
|
-
case "char":
|
|
1228
|
-
S = u.getInt8(l), l += 1;
|
|
1229
|
-
break;
|
|
1230
|
-
case "uchar":
|
|
1231
|
-
S = u.getUint8(l), l += 1;
|
|
1232
|
-
break;
|
|
1233
|
-
case "short":
|
|
1234
|
-
S = u.getInt16(l, !0), l += 2;
|
|
1235
|
-
break;
|
|
1236
|
-
case "ushort":
|
|
1237
|
-
S = u.getUint16(l, !0), l += 2;
|
|
1238
|
-
break;
|
|
1239
|
-
case "int":
|
|
1240
|
-
S = u.getInt32(l, !0), l += 4;
|
|
1241
|
-
break;
|
|
1242
|
-
case "uint":
|
|
1243
|
-
S = u.getUint32(l, !0), l += 4;
|
|
1244
|
-
break;
|
|
1245
|
-
case "float":
|
|
1246
|
-
S = u.getFloat32(l, !0), l += 4;
|
|
1247
|
-
break;
|
|
1248
|
-
case "double":
|
|
1249
|
-
S = u.getFloat64(l, !0), l += 8;
|
|
1250
|
-
break;
|
|
1251
|
-
default:
|
|
1252
|
-
throw new Error(`Unsupported property type: ${Q}`);
|
|
1253
|
-
}
|
|
1254
|
-
d.push(S);
|
|
1255
|
-
}
|
|
1256
|
-
m.set(
|
|
1257
|
-
d[A],
|
|
1258
|
-
d[O],
|
|
1259
|
-
d[q]
|
|
1260
|
-
), B.set(
|
|
1261
|
-
// TODO: dangerous see if true
|
|
1262
|
-
d[R[1]],
|
|
1263
|
-
// PLY stores rot 1,2,3,0 (x,y,z,w)
|
|
1264
|
-
d[R[2]],
|
|
1265
|
-
d[R[3]],
|
|
1266
|
-
d[R[0]]
|
|
1267
|
-
).normalize(), P.set(
|
|
1268
|
-
d[E[0]],
|
|
1269
|
-
// Read directly assuming it's log scale
|
|
1270
|
-
d[E[1]],
|
|
1271
|
-
d[E[2]]
|
|
1272
|
-
), V.set(
|
|
1273
|
-
0.5 + d[W[0]] * _,
|
|
1274
|
-
0.5 + d[W[1]] * _,
|
|
1275
|
-
0.5 + d[W[2]] * _
|
|
1276
|
-
), V.r = Math.max(0, Math.min(1, V.r)), V.g = Math.max(0, Math.min(1, V.g)), V.b = Math.max(0, Math.min(1, V.b));
|
|
1277
|
-
const C = M(d[F]);
|
|
1278
|
-
p.setSplat(
|
|
1279
|
-
x,
|
|
1280
|
-
m,
|
|
1281
|
-
B,
|
|
1282
|
-
P,
|
|
1283
|
-
// Pass log scale directly
|
|
1284
|
-
V,
|
|
1285
|
-
C
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
return p.calculateBoundingBox(), p;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
export {
|
|
1292
|
-
et as B,
|
|
1293
|
-
wt as P,
|
|
1294
|
-
ht as S,
|
|
1295
|
-
mt as T,
|
|
1296
|
-
ut as a,
|
|
1297
|
-
nt as b,
|
|
1298
|
-
gt as c
|
|
1299
|
-
};
|