@onerjs/core 8.46.1 → 8.46.3

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.
Files changed (54) hide show
  1. package/Collisions/gpuPicker.js +9 -1
  2. package/Collisions/gpuPicker.js.map +1 -1
  3. package/Engines/WebGPU/webgpuCacheRenderPipeline.d.ts +20 -0
  4. package/Engines/WebGPU/webgpuCacheRenderPipeline.js +58 -13
  5. package/Engines/WebGPU/webgpuCacheRenderPipeline.js.map +1 -1
  6. package/Engines/WebGPU/webgpuConstants.d.ts +5 -2
  7. package/Engines/WebGPU/webgpuConstants.js +3 -0
  8. package/Engines/WebGPU/webgpuConstants.js.map +1 -1
  9. package/Engines/abstractEngine.js +2 -2
  10. package/Engines/abstractEngine.js.map +1 -1
  11. package/Engines/engine.d.ts +45 -41
  12. package/Engines/shaderStore.js +2 -2
  13. package/Engines/shaderStore.js.map +1 -1
  14. package/Engines/webgpuEngine.d.ts +84 -0
  15. package/Engines/webgpuEngine.js +80 -1
  16. package/Engines/webgpuEngine.js.map +1 -1
  17. package/Layers/thinHighlightLayer.js.map +1 -1
  18. package/Layers/thinSelectionOutlineLayer.js +20 -5
  19. package/Layers/thinSelectionOutlineLayer.js.map +1 -1
  20. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.d.ts +7 -0
  21. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.js +22 -0
  22. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.js.map +1 -1
  23. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +2 -28
  24. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
  25. package/Materials/Textures/baseTexture.js +3 -0
  26. package/Materials/Textures/baseTexture.js.map +1 -1
  27. package/Materials/Textures/texture.js +1 -0
  28. package/Materials/Textures/texture.js.map +1 -1
  29. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.d.ts +46 -0
  30. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js +56 -0
  31. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js.map +1 -0
  32. package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +104 -463
  33. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +553 -2018
  34. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
  35. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.d.ts +554 -0
  36. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +2017 -0
  37. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -0
  38. package/Meshes/index.d.ts +2 -0
  39. package/Meshes/index.js +2 -0
  40. package/Meshes/index.js.map +1 -1
  41. package/Misc/tools.js +1 -1
  42. package/Misc/tools.js.map +1 -1
  43. package/Rendering/depthRenderer.js +2 -1
  44. package/Rendering/depthRenderer.js.map +1 -1
  45. package/node.d.ts +7 -0
  46. package/node.js +17 -0
  47. package/node.js.map +1 -1
  48. package/package.json +2 -2
  49. package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.d.ts +0 -5
  50. package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.js +0 -35
  51. package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.js.map +0 -1
  52. package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.d.ts +0 -5
  53. package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.js +0 -36
  54. package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.js.map +0 -1
@@ -0,0 +1,2017 @@
1
+ import { SubMesh } from "../subMesh.js";
2
+ import { Mesh } from "../mesh.js";
3
+ import { VertexData } from "../mesh.vertexData.js";
4
+ import { Matrix, TmpVectors, Vector2, Vector3 } from "../../Maths/math.vector.js";
5
+ import { Logger } from "../../Misc/logger.js";
6
+ import { GaussianSplattingMaterial } from "../../Materials/GaussianSplatting/gaussianSplattingMaterial.js";
7
+ import { RawTexture } from "../../Materials/Textures/rawTexture.js";
8
+
9
+ import "../thinInstanceMesh.js";
10
+ import { ToHalfFloat } from "../../Misc/textureTools.js";
11
+ import { Scalar } from "../../Maths/math.scalar.js";
12
+ import { runCoroutineSync, runCoroutineAsync, createYieldingScheduler } from "../../Misc/coroutine.js";
13
+ import { EngineStore } from "../../Engines/engineStore.js";
14
+ import { ImportMeshAsync } from "../../Loading/sceneLoader.js";
15
+ const IsNative = typeof _native !== "undefined";
16
+ const Native = IsNative ? _native : null;
17
+ // @internal
18
+ const UnpackUnorm = (value, bits) => {
19
+ const t = (1 << bits) - 1;
20
+ return (value & t) / t;
21
+ };
22
+ // @internal
23
+ const Unpack111011 = (value, result) => {
24
+ result.x = UnpackUnorm(value >>> 21, 11);
25
+ result.y = UnpackUnorm(value >>> 11, 10);
26
+ result.z = UnpackUnorm(value, 11);
27
+ };
28
+ // @internal
29
+ const Unpack8888 = (value, result) => {
30
+ result[0] = UnpackUnorm(value >>> 24, 8) * 255;
31
+ result[1] = UnpackUnorm(value >>> 16, 8) * 255;
32
+ result[2] = UnpackUnorm(value >>> 8, 8) * 255;
33
+ result[3] = UnpackUnorm(value, 8) * 255;
34
+ };
35
+ // @internal
36
+ // unpack quaternion with 2,10,10,10 format (largest element, 3x10bit element)
37
+ const UnpackRot = (value, result) => {
38
+ const norm = 1.0 / (Math.sqrt(2) * 0.5);
39
+ const a = (UnpackUnorm(value >>> 20, 10) - 0.5) * norm;
40
+ const b = (UnpackUnorm(value >>> 10, 10) - 0.5) * norm;
41
+ const c = (UnpackUnorm(value, 10) - 0.5) * norm;
42
+ const m = Math.sqrt(1.0 - (a * a + b * b + c * c));
43
+ switch (value >>> 30) {
44
+ case 0:
45
+ result.set(m, a, b, c);
46
+ break;
47
+ case 1:
48
+ result.set(a, m, b, c);
49
+ break;
50
+ case 2:
51
+ result.set(a, b, m, c);
52
+ break;
53
+ case 3:
54
+ result.set(a, b, c, m);
55
+ break;
56
+ }
57
+ };
58
+ /**
59
+ * Representation of the types
60
+ */
61
+ var PLYType;
62
+ (function (PLYType) {
63
+ PLYType[PLYType["FLOAT"] = 0] = "FLOAT";
64
+ PLYType[PLYType["INT"] = 1] = "INT";
65
+ PLYType[PLYType["UINT"] = 2] = "UINT";
66
+ PLYType[PLYType["DOUBLE"] = 3] = "DOUBLE";
67
+ PLYType[PLYType["UCHAR"] = 4] = "UCHAR";
68
+ PLYType[PLYType["UNDEFINED"] = 5] = "UNDEFINED";
69
+ })(PLYType || (PLYType = {}));
70
+ /**
71
+ * Usage types of the PLY values
72
+ */
73
+ var PLYValue;
74
+ (function (PLYValue) {
75
+ PLYValue[PLYValue["MIN_X"] = 0] = "MIN_X";
76
+ PLYValue[PLYValue["MIN_Y"] = 1] = "MIN_Y";
77
+ PLYValue[PLYValue["MIN_Z"] = 2] = "MIN_Z";
78
+ PLYValue[PLYValue["MAX_X"] = 3] = "MAX_X";
79
+ PLYValue[PLYValue["MAX_Y"] = 4] = "MAX_Y";
80
+ PLYValue[PLYValue["MAX_Z"] = 5] = "MAX_Z";
81
+ PLYValue[PLYValue["MIN_SCALE_X"] = 6] = "MIN_SCALE_X";
82
+ PLYValue[PLYValue["MIN_SCALE_Y"] = 7] = "MIN_SCALE_Y";
83
+ PLYValue[PLYValue["MIN_SCALE_Z"] = 8] = "MIN_SCALE_Z";
84
+ PLYValue[PLYValue["MAX_SCALE_X"] = 9] = "MAX_SCALE_X";
85
+ PLYValue[PLYValue["MAX_SCALE_Y"] = 10] = "MAX_SCALE_Y";
86
+ PLYValue[PLYValue["MAX_SCALE_Z"] = 11] = "MAX_SCALE_Z";
87
+ PLYValue[PLYValue["PACKED_POSITION"] = 12] = "PACKED_POSITION";
88
+ PLYValue[PLYValue["PACKED_ROTATION"] = 13] = "PACKED_ROTATION";
89
+ PLYValue[PLYValue["PACKED_SCALE"] = 14] = "PACKED_SCALE";
90
+ PLYValue[PLYValue["PACKED_COLOR"] = 15] = "PACKED_COLOR";
91
+ PLYValue[PLYValue["X"] = 16] = "X";
92
+ PLYValue[PLYValue["Y"] = 17] = "Y";
93
+ PLYValue[PLYValue["Z"] = 18] = "Z";
94
+ PLYValue[PLYValue["SCALE_0"] = 19] = "SCALE_0";
95
+ PLYValue[PLYValue["SCALE_1"] = 20] = "SCALE_1";
96
+ PLYValue[PLYValue["SCALE_2"] = 21] = "SCALE_2";
97
+ PLYValue[PLYValue["DIFFUSE_RED"] = 22] = "DIFFUSE_RED";
98
+ PLYValue[PLYValue["DIFFUSE_GREEN"] = 23] = "DIFFUSE_GREEN";
99
+ PLYValue[PLYValue["DIFFUSE_BLUE"] = 24] = "DIFFUSE_BLUE";
100
+ PLYValue[PLYValue["OPACITY"] = 25] = "OPACITY";
101
+ PLYValue[PLYValue["F_DC_0"] = 26] = "F_DC_0";
102
+ PLYValue[PLYValue["F_DC_1"] = 27] = "F_DC_1";
103
+ PLYValue[PLYValue["F_DC_2"] = 28] = "F_DC_2";
104
+ PLYValue[PLYValue["F_DC_3"] = 29] = "F_DC_3";
105
+ PLYValue[PLYValue["ROT_0"] = 30] = "ROT_0";
106
+ PLYValue[PLYValue["ROT_1"] = 31] = "ROT_1";
107
+ PLYValue[PLYValue["ROT_2"] = 32] = "ROT_2";
108
+ PLYValue[PLYValue["ROT_3"] = 33] = "ROT_3";
109
+ PLYValue[PLYValue["MIN_COLOR_R"] = 34] = "MIN_COLOR_R";
110
+ PLYValue[PLYValue["MIN_COLOR_G"] = 35] = "MIN_COLOR_G";
111
+ PLYValue[PLYValue["MIN_COLOR_B"] = 36] = "MIN_COLOR_B";
112
+ PLYValue[PLYValue["MAX_COLOR_R"] = 37] = "MAX_COLOR_R";
113
+ PLYValue[PLYValue["MAX_COLOR_G"] = 38] = "MAX_COLOR_G";
114
+ PLYValue[PLYValue["MAX_COLOR_B"] = 39] = "MAX_COLOR_B";
115
+ PLYValue[PLYValue["SH_0"] = 40] = "SH_0";
116
+ PLYValue[PLYValue["SH_1"] = 41] = "SH_1";
117
+ PLYValue[PLYValue["SH_2"] = 42] = "SH_2";
118
+ PLYValue[PLYValue["SH_3"] = 43] = "SH_3";
119
+ PLYValue[PLYValue["SH_4"] = 44] = "SH_4";
120
+ PLYValue[PLYValue["SH_5"] = 45] = "SH_5";
121
+ PLYValue[PLYValue["SH_6"] = 46] = "SH_6";
122
+ PLYValue[PLYValue["SH_7"] = 47] = "SH_7";
123
+ PLYValue[PLYValue["SH_8"] = 48] = "SH_8";
124
+ PLYValue[PLYValue["SH_9"] = 49] = "SH_9";
125
+ PLYValue[PLYValue["SH_10"] = 50] = "SH_10";
126
+ PLYValue[PLYValue["SH_11"] = 51] = "SH_11";
127
+ PLYValue[PLYValue["SH_12"] = 52] = "SH_12";
128
+ PLYValue[PLYValue["SH_13"] = 53] = "SH_13";
129
+ PLYValue[PLYValue["SH_14"] = 54] = "SH_14";
130
+ PLYValue[PLYValue["SH_15"] = 55] = "SH_15";
131
+ PLYValue[PLYValue["SH_16"] = 56] = "SH_16";
132
+ PLYValue[PLYValue["SH_17"] = 57] = "SH_17";
133
+ PLYValue[PLYValue["SH_18"] = 58] = "SH_18";
134
+ PLYValue[PLYValue["SH_19"] = 59] = "SH_19";
135
+ PLYValue[PLYValue["SH_20"] = 60] = "SH_20";
136
+ PLYValue[PLYValue["SH_21"] = 61] = "SH_21";
137
+ PLYValue[PLYValue["SH_22"] = 62] = "SH_22";
138
+ PLYValue[PLYValue["SH_23"] = 63] = "SH_23";
139
+ PLYValue[PLYValue["SH_24"] = 64] = "SH_24";
140
+ PLYValue[PLYValue["SH_25"] = 65] = "SH_25";
141
+ PLYValue[PLYValue["SH_26"] = 66] = "SH_26";
142
+ PLYValue[PLYValue["SH_27"] = 67] = "SH_27";
143
+ PLYValue[PLYValue["SH_28"] = 68] = "SH_28";
144
+ PLYValue[PLYValue["SH_29"] = 69] = "SH_29";
145
+ PLYValue[PLYValue["SH_30"] = 70] = "SH_30";
146
+ PLYValue[PLYValue["SH_31"] = 71] = "SH_31";
147
+ PLYValue[PLYValue["SH_32"] = 72] = "SH_32";
148
+ PLYValue[PLYValue["SH_33"] = 73] = "SH_33";
149
+ PLYValue[PLYValue["SH_34"] = 74] = "SH_34";
150
+ PLYValue[PLYValue["SH_35"] = 75] = "SH_35";
151
+ PLYValue[PLYValue["SH_36"] = 76] = "SH_36";
152
+ PLYValue[PLYValue["SH_37"] = 77] = "SH_37";
153
+ PLYValue[PLYValue["SH_38"] = 78] = "SH_38";
154
+ PLYValue[PLYValue["SH_39"] = 79] = "SH_39";
155
+ PLYValue[PLYValue["SH_40"] = 80] = "SH_40";
156
+ PLYValue[PLYValue["SH_41"] = 81] = "SH_41";
157
+ PLYValue[PLYValue["SH_42"] = 82] = "SH_42";
158
+ PLYValue[PLYValue["SH_43"] = 83] = "SH_43";
159
+ PLYValue[PLYValue["SH_44"] = 84] = "SH_44";
160
+ PLYValue[PLYValue["UNDEFINED"] = 85] = "UNDEFINED";
161
+ })(PLYValue || (PLYValue = {}));
162
+ /**
163
+ * Base class for Gaussian Splatting meshes. Contains all single-cloud rendering logic.
164
+ * @internal Use GaussianSplattingMesh instead; this class is an internal implementation detail.
165
+ */
166
+ export class GaussianSplattingMeshBase extends Mesh {
167
+ /**
168
+ * If true, disables depth sorting of the splats (default: false)
169
+ */
170
+ get disableDepthSort() {
171
+ return this._disableDepthSort;
172
+ }
173
+ set disableDepthSort(value) {
174
+ if (!this._disableDepthSort && value) {
175
+ this._worker?.terminate();
176
+ this._worker = null;
177
+ this._disableDepthSort = true;
178
+ }
179
+ else if (this._disableDepthSort && !value) {
180
+ this._disableDepthSort = false;
181
+ this._sortIsDirty = true;
182
+ this._instantiateWorker();
183
+ }
184
+ }
185
+ /**
186
+ * View direction factor used to compute the SH view direction in the shader.
187
+ * @deprecated Not used anymore for SH rendering
188
+ */
189
+ get viewDirectionFactor() {
190
+ return Vector3.OneReadOnly;
191
+ }
192
+ /**
193
+ * SH degree. 0 = no sh (default). 1 = 3 parameters. 2 = 8 parameters. 3 = 15 parameters.
194
+ * Value is clamped between 0 and the maximum degree available from loaded data.
195
+ */
196
+ get shDegree() {
197
+ return this._shDegree;
198
+ }
199
+ set shDegree(value) {
200
+ const maxDegree = this._shTextures?.length ?? 0;
201
+ const clamped = Math.max(0, Math.min(Math.round(value), maxDegree));
202
+ if (this._shDegree === clamped) {
203
+ return;
204
+ }
205
+ this._shDegree = clamped;
206
+ this.material?.resetDrawCache();
207
+ }
208
+ /**
209
+ * Maximum SH degree available from the loaded data.
210
+ */
211
+ get maxShDegree() {
212
+ return this._shTextures?.length ?? 0;
213
+ }
214
+ /**
215
+ * Number of splats in the mesh
216
+ */
217
+ get splatCount() {
218
+ return this._splatIndex?.length;
219
+ }
220
+ /**
221
+ * returns the splats data array buffer that contains in order : postions (3 floats), size (3 floats), color (4 bytes), orientation quaternion (4 bytes)
222
+ * Only available if the mesh was created with keepInRam: true
223
+ */
224
+ get splatsData() {
225
+ return this._keepInRam ? this._splatsData : null;
226
+ }
227
+ /**
228
+ * returns the SH data arrays
229
+ * Only available if the mesh was created with keepInRam: true
230
+ */
231
+ get shData() {
232
+ return this._keepInRam ? this._shData : null;
233
+ }
234
+ /**
235
+ * Gets the covariancesA texture
236
+ */
237
+ get covariancesATexture() {
238
+ return this._covariancesATexture;
239
+ }
240
+ /**
241
+ * Gets the covariancesB texture
242
+ */
243
+ get covariancesBTexture() {
244
+ return this._covariancesBTexture;
245
+ }
246
+ /**
247
+ * Gets the centers texture
248
+ */
249
+ get centersTexture() {
250
+ return this._centersTexture;
251
+ }
252
+ /**
253
+ * Gets the colors texture
254
+ */
255
+ get colorsTexture() {
256
+ return this._colorsTexture;
257
+ }
258
+ /**
259
+ * Gets the SH textures
260
+ */
261
+ get shTextures() {
262
+ return this._shTextures;
263
+ }
264
+ /**
265
+ * Gets the kernel size
266
+ * Documentation and mathematical explanations here:
267
+ * https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
268
+ * https://github.com/autonomousvision/mip-splatting/issues/18#issuecomment-1929388931
269
+ */
270
+ get kernelSize() {
271
+ return this._material instanceof GaussianSplattingMaterial ? this._material.kernelSize : 0;
272
+ }
273
+ /**
274
+ * Get the compensation state
275
+ */
276
+ get compensation() {
277
+ return this._material instanceof GaussianSplattingMaterial ? this._material.compensation : false;
278
+ }
279
+ /**
280
+ * set rendering material
281
+ */
282
+ set material(value) {
283
+ this._material = value;
284
+ this._material.backFaceCulling = false;
285
+ this._material.cullBackFaces = false;
286
+ value.resetDrawCache();
287
+ }
288
+ /**
289
+ * get rendering material
290
+ */
291
+ get material() {
292
+ return this._material;
293
+ }
294
+ static _MakeSplatGeometryForMesh(mesh) {
295
+ const vertexData = new VertexData();
296
+ const originPositions = [-2, -2, 0, 2, -2, 0, 2, 2, 0, -2, 2, 0];
297
+ const originIndices = [0, 1, 2, 0, 2, 3];
298
+ const positions = [];
299
+ const indices = [];
300
+ for (let i = 0; i < GaussianSplattingMeshBase._BatchSize; i++) {
301
+ for (let j = 0; j < 12; j++) {
302
+ if (j == 2 || j == 5 || j == 8 || j == 11) {
303
+ positions.push(i); // local splat index
304
+ }
305
+ else {
306
+ positions.push(originPositions[j]);
307
+ }
308
+ }
309
+ indices.push(originIndices.map((v) => v + i * 4));
310
+ }
311
+ vertexData.positions = positions;
312
+ vertexData.indices = indices.flat();
313
+ vertexData.applyToMesh(mesh);
314
+ }
315
+ /**
316
+ * Creates a new gaussian splatting mesh
317
+ * @param name defines the name of the mesh
318
+ * @param url defines the url to load from (optional)
319
+ * @param scene defines the hosting scene (optional)
320
+ * @param keepInRam keep datas in ram for editing purpose
321
+ */
322
+ constructor(name, url = null, scene = null, keepInRam = false) {
323
+ super(name, scene);
324
+ /** @internal */
325
+ this._vertexCount = 0;
326
+ this._worker = null;
327
+ this._modelViewProjectionMatrix = Matrix.Identity();
328
+ this._canPostToWorker = true;
329
+ this._readyToDisplay = false;
330
+ this._covariancesATexture = null;
331
+ this._covariancesBTexture = null;
332
+ this._centersTexture = null;
333
+ this._colorsTexture = null;
334
+ this._splatPositions = null;
335
+ this._splatIndex = null;
336
+ this._shTextures = null;
337
+ /** @internal */
338
+ this._splatsData = null;
339
+ /** @internal */
340
+ this._shData = null;
341
+ this._textureSize = new Vector2(0, 0);
342
+ this._keepInRam = false;
343
+ this._alwaysRetainSplatsData = false;
344
+ this._delayedTextureUpdate = null;
345
+ this._useRGBACovariants = false;
346
+ this._material = null;
347
+ this._tmpCovariances = [0, 0, 0, 0, 0, 0];
348
+ this._sortIsDirty = false;
349
+ // Cached bounding box for incremental addPart updates (O(1) vs O(N) scan of positions)
350
+ this._cachedBoundingMin = null;
351
+ this._cachedBoundingMax = null;
352
+ /** @internal */
353
+ this._shDegree = 0;
354
+ this._cameraViewInfos = new Map();
355
+ /**
356
+ * Cosine value of the angle threshold to update view dependent splat sorting. Default is 0.0001.
357
+ */
358
+ this.viewUpdateThreshold = GaussianSplattingMeshBase._DefaultViewUpdateThreshold;
359
+ this._disableDepthSort = false;
360
+ this._loadingPromise = null;
361
+ this._updateTextureFromData = (texture, data, width, lineStart, lineCount) => {
362
+ this.getEngine().updateTextureData(texture.getInternalTexture(), data, 0, lineStart, width, lineCount, 0, 0, false);
363
+ };
364
+ this.subMeshes = [];
365
+ new SubMesh(0, 0, 4 * GaussianSplattingMeshBase._BatchSize, 0, 6 * GaussianSplattingMeshBase._BatchSize, this);
366
+ this.setEnabled(false);
367
+ // webGL2 and webGPU support for RG texture with float16 is fine. not webGL1
368
+ this._useRGBACovariants = !this.getEngine().isWebGPU && this.getEngine().version === 1.0;
369
+ this._keepInRam = keepInRam;
370
+ if (url) {
371
+ this._loadingPromise = this.loadFileAsync(url);
372
+ }
373
+ const gaussianSplattingMaterial = new GaussianSplattingMaterial(this.name + "_material", this._scene);
374
+ // Cast is safe: GaussianSplattingMeshBase is @internal; all concrete instances are GaussianSplattingMesh.
375
+ gaussianSplattingMaterial.setSourceMesh(this);
376
+ this._material = gaussianSplattingMaterial;
377
+ // delete meshes created for cameras on camera removal
378
+ this._scene.onCameraRemovedObservable.add((camera) => {
379
+ const cameraId = camera.uniqueId;
380
+ // delete mesh for this camera
381
+ if (this._cameraViewInfos.has(cameraId)) {
382
+ const cameraViewInfos = this._cameraViewInfos.get(cameraId);
383
+ cameraViewInfos?.mesh.dispose();
384
+ this._cameraViewInfos.delete(cameraId);
385
+ }
386
+ });
387
+ }
388
+ /**
389
+ * Get the loading promise when loading the mesh from a URL in the constructor
390
+ * @returns constructor loading promise or null if no URL was provided
391
+ */
392
+ getLoadingPromise() {
393
+ return this._loadingPromise;
394
+ }
395
+ /**
396
+ * Returns the class name
397
+ * @returns "GaussianSplattingMeshBase"
398
+ */
399
+ getClassName() {
400
+ return "GaussianSplattingMeshBase";
401
+ }
402
+ /**
403
+ * Returns the total number of vertices (splats) within the mesh
404
+ * @returns the total number of vertices
405
+ */
406
+ getTotalVertices() {
407
+ return this._vertexCount;
408
+ }
409
+ /**
410
+ * Is this node ready to be used/rendered
411
+ * @param completeCheck defines if a complete check (including materials and lights) has to be done (false by default)
412
+ * @returns true when ready
413
+ */
414
+ isReady(completeCheck = false) {
415
+ if (!super.isReady(completeCheck, true)) {
416
+ return false;
417
+ }
418
+ if (!this._readyToDisplay) {
419
+ // mesh is ready when worker has done at least 1 sorting
420
+ this._postToWorker(true);
421
+ return false;
422
+ }
423
+ return true;
424
+ }
425
+ _getCameraDirection(camera) {
426
+ const cameraViewMatrix = camera.getViewMatrix();
427
+ const cameraProjectionMatrix = camera.getProjectionMatrix();
428
+ const cameraViewProjectionMatrix = TmpVectors.Matrix[0];
429
+ cameraViewMatrix.multiplyToRef(cameraProjectionMatrix, cameraViewProjectionMatrix);
430
+ const modelMatrix = this.getWorldMatrix();
431
+ const modelViewMatrix = TmpVectors.Matrix[1];
432
+ modelMatrix.multiplyToRef(cameraViewMatrix, modelViewMatrix);
433
+ modelMatrix.multiplyToRef(cameraViewProjectionMatrix, this._modelViewProjectionMatrix);
434
+ // return vector used to compute distance to camera
435
+ const localDirection = TmpVectors.Vector3[1];
436
+ localDirection.set(modelViewMatrix.m[2], modelViewMatrix.m[6], modelViewMatrix.m[10]);
437
+ localDirection.normalize();
438
+ return localDirection;
439
+ }
440
+ /** @internal */
441
+ _postToWorker(forced = false) {
442
+ const scene = this._scene;
443
+ const frameId = scene.getFrameId();
444
+ // force update or at least frame update for camera is outdated
445
+ let outdated = false;
446
+ this._cameraViewInfos.forEach((cameraViewInfos) => {
447
+ if (cameraViewInfos.frameIdLastUpdate !== frameId) {
448
+ outdated = true;
449
+ }
450
+ });
451
+ // array of cameras used for rendering
452
+ const cameras = this._scene.activeCameras?.length ? this._scene.activeCameras : [this._scene.activeCamera];
453
+ // list view infos for active cameras
454
+ const activeViewInfos = [];
455
+ cameras.forEach((camera) => {
456
+ if (!camera) {
457
+ return;
458
+ }
459
+ const cameraId = camera.uniqueId;
460
+ const cameraViewInfos = this._cameraViewInfos.get(cameraId);
461
+ if (cameraViewInfos) {
462
+ activeViewInfos.push(cameraViewInfos);
463
+ }
464
+ else {
465
+ // mesh doesn't exist yet for this camera
466
+ const cameraMesh = new Mesh(this.name + "_cameraMesh_" + cameraId, this._scene);
467
+ // not visible with inspector or the scene graph
468
+ cameraMesh.reservedDataStore = { hidden: true };
469
+ cameraMesh.setEnabled(false);
470
+ cameraMesh.material = this.material;
471
+ if (cameraMesh.material && cameraMesh.material instanceof GaussianSplattingMaterial) {
472
+ const gsMaterial = cameraMesh.material;
473
+ // GaussianSplattingMaterial source mesh may not have been set yet.
474
+ // This happens for cloned resources from asset containers for instance,
475
+ // where material is cloned before mesh.
476
+ if (!gsMaterial.getSourceMesh()) {
477
+ // Cast is safe: see constructor comment above.
478
+ gsMaterial.setSourceMesh(this);
479
+ }
480
+ }
481
+ GaussianSplattingMeshBase._MakeSplatGeometryForMesh(cameraMesh);
482
+ const newViewInfos = {
483
+ camera: camera,
484
+ cameraDirection: new Vector3(0, 0, 0),
485
+ mesh: cameraMesh,
486
+ frameIdLastUpdate: frameId,
487
+ splatIndexBufferSet: false,
488
+ };
489
+ activeViewInfos.push(newViewInfos);
490
+ this._cameraViewInfos.set(cameraId, newViewInfos);
491
+ }
492
+ });
493
+ // sort view infos by last updated frame id: first item is the least recently updated
494
+ activeViewInfos.sort((a, b) => a.frameIdLastUpdate - b.frameIdLastUpdate);
495
+ const hasSortFunction = this._worker || Native?.sortSplats || this._disableDepthSort;
496
+ if ((forced || outdated) && hasSortFunction && (this._scene.activeCameras?.length || this._scene.activeCamera) && this._canPostToWorker) {
497
+ // view infos sorted by least recent updated frame id
498
+ activeViewInfos.forEach((cameraViewInfos) => {
499
+ const camera = cameraViewInfos.camera;
500
+ const cameraDirection = this._getCameraDirection(camera);
501
+ const previousCameraDirection = cameraViewInfos.cameraDirection;
502
+ const dot = Vector3.Dot(cameraDirection, previousCameraDirection);
503
+ if ((forced || Math.abs(dot - 1) >= this.viewUpdateThreshold) && this._canPostToWorker) {
504
+ cameraViewInfos.cameraDirection.copyFrom(cameraDirection);
505
+ cameraViewInfos.frameIdLastUpdate = frameId;
506
+ this._canPostToWorker = false;
507
+ if (this._worker) {
508
+ const cameraViewMatrix = camera.getViewMatrix();
509
+ this._worker.postMessage({
510
+ worldMatrix: this.getWorldMatrix().m,
511
+ cameraForward: [cameraViewMatrix.m[2], cameraViewMatrix.m[6], cameraViewMatrix.m[10]],
512
+ cameraPosition: [camera.globalPosition.x, camera.globalPosition.y, camera.globalPosition.z],
513
+ depthMix: this._depthMix,
514
+ cameraId: camera.uniqueId,
515
+ }, [this._depthMix.buffer]);
516
+ }
517
+ else if (Native?.sortSplats) {
518
+ Native.sortSplats(this._modelViewProjectionMatrix, this._splatPositions, this._splatIndex, this._scene.useRightHandedSystem);
519
+ if (cameraViewInfos.splatIndexBufferSet) {
520
+ cameraViewInfos.mesh.thinInstanceBufferUpdated("splatIndex");
521
+ }
522
+ else {
523
+ cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
524
+ cameraViewInfos.splatIndexBufferSet = true;
525
+ }
526
+ this._canPostToWorker = true;
527
+ this._readyToDisplay = true;
528
+ }
529
+ }
530
+ });
531
+ }
532
+ else if (this._disableDepthSort) {
533
+ activeViewInfos.forEach((cameraViewInfos) => {
534
+ if (!cameraViewInfos.splatIndexBufferSet) {
535
+ cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
536
+ cameraViewInfos.splatIndexBufferSet = true;
537
+ }
538
+ });
539
+ this._canPostToWorker = true;
540
+ this._readyToDisplay = true;
541
+ }
542
+ }
543
+ /**
544
+ * Triggers the draw call for the mesh. Usually, you don't need to call this method by your own because the mesh rendering is handled by the scene rendering manager
545
+ * @param subMesh defines the subMesh to render
546
+ * @param enableAlphaMode defines if alpha mode can be changed
547
+ * @param effectiveMeshReplacement defines an optional mesh used to provide info for the rendering
548
+ * @returns the current mesh
549
+ */
550
+ render(subMesh, enableAlphaMode, effectiveMeshReplacement) {
551
+ this._postToWorker();
552
+ // geometry used for shadows, bind the first found in the camera view infos
553
+ if (!this._geometry && this._cameraViewInfos.size) {
554
+ this._geometry = this._cameraViewInfos.values().next().value.mesh.geometry;
555
+ }
556
+ const cameraId = this._scene.activeCamera.uniqueId;
557
+ const cameraViewInfos = this._cameraViewInfos.get(cameraId);
558
+ if (!cameraViewInfos || !cameraViewInfos.splatIndexBufferSet) {
559
+ return this;
560
+ }
561
+ if (this.onBeforeRenderObservable) {
562
+ this.onBeforeRenderObservable.notifyObservers(this);
563
+ }
564
+ const mesh = cameraViewInfos.mesh;
565
+ mesh.getWorldMatrix().copyFrom(this.getWorldMatrix());
566
+ // Propagate render pass material overrides (e.g., GPU picking) to the inner camera mesh.
567
+ // When this mesh is rendered into a RenderTargetTexture with a material override (via setMaterialForRendering),
568
+ // the override is set on this proxy mesh but needs to be applied to the actual camera mesh that does the rendering.
569
+ const engine = this._scene.getEngine();
570
+ const renderPassId = engine.currentRenderPassId;
571
+ const renderPassMaterial = this.getMaterialForRenderPass(renderPassId);
572
+ if (renderPassMaterial) {
573
+ mesh.setMaterialForRenderPass(renderPassId, renderPassMaterial);
574
+ }
575
+ const ret = mesh.render(subMesh, enableAlphaMode, effectiveMeshReplacement);
576
+ // Clean up the temporary override to avoid affecting other render passes
577
+ if (renderPassMaterial) {
578
+ mesh.setMaterialForRenderPass(renderPassId, undefined);
579
+ }
580
+ if (this.onAfterRenderObservable) {
581
+ this.onAfterRenderObservable.notifyObservers(this);
582
+ }
583
+ return ret;
584
+ }
585
+ static _TypeNameToEnum(name) {
586
+ switch (name) {
587
+ case "float":
588
+ return 0 /* PLYType.FLOAT */;
589
+ case "int":
590
+ return 1 /* PLYType.INT */;
591
+ case "uint":
592
+ return 2 /* PLYType.UINT */;
593
+ case "double":
594
+ return 3 /* PLYType.DOUBLE */;
595
+ case "uchar":
596
+ return 4 /* PLYType.UCHAR */;
597
+ }
598
+ return 5 /* PLYType.UNDEFINED */;
599
+ }
600
+ static _ValueNameToEnum(name) {
601
+ switch (name) {
602
+ case "min_x":
603
+ return 0 /* PLYValue.MIN_X */;
604
+ case "min_y":
605
+ return 1 /* PLYValue.MIN_Y */;
606
+ case "min_z":
607
+ return 2 /* PLYValue.MIN_Z */;
608
+ case "max_x":
609
+ return 3 /* PLYValue.MAX_X */;
610
+ case "max_y":
611
+ return 4 /* PLYValue.MAX_Y */;
612
+ case "max_z":
613
+ return 5 /* PLYValue.MAX_Z */;
614
+ case "min_scale_x":
615
+ return 6 /* PLYValue.MIN_SCALE_X */;
616
+ case "min_scale_y":
617
+ return 7 /* PLYValue.MIN_SCALE_Y */;
618
+ case "min_scale_z":
619
+ return 8 /* PLYValue.MIN_SCALE_Z */;
620
+ case "max_scale_x":
621
+ return 9 /* PLYValue.MAX_SCALE_X */;
622
+ case "max_scale_y":
623
+ return 10 /* PLYValue.MAX_SCALE_Y */;
624
+ case "max_scale_z":
625
+ return 11 /* PLYValue.MAX_SCALE_Z */;
626
+ case "packed_position":
627
+ return 12 /* PLYValue.PACKED_POSITION */;
628
+ case "packed_rotation":
629
+ return 13 /* PLYValue.PACKED_ROTATION */;
630
+ case "packed_scale":
631
+ return 14 /* PLYValue.PACKED_SCALE */;
632
+ case "packed_color":
633
+ return 15 /* PLYValue.PACKED_COLOR */;
634
+ case "x":
635
+ return 16 /* PLYValue.X */;
636
+ case "y":
637
+ return 17 /* PLYValue.Y */;
638
+ case "z":
639
+ return 18 /* PLYValue.Z */;
640
+ case "scale_0":
641
+ return 19 /* PLYValue.SCALE_0 */;
642
+ case "scale_1":
643
+ return 20 /* PLYValue.SCALE_1 */;
644
+ case "scale_2":
645
+ return 21 /* PLYValue.SCALE_2 */;
646
+ case "diffuse_red":
647
+ case "red":
648
+ return 22 /* PLYValue.DIFFUSE_RED */;
649
+ case "diffuse_green":
650
+ case "green":
651
+ return 23 /* PLYValue.DIFFUSE_GREEN */;
652
+ case "diffuse_blue":
653
+ case "blue":
654
+ return 24 /* PLYValue.DIFFUSE_BLUE */;
655
+ case "f_dc_0":
656
+ return 26 /* PLYValue.F_DC_0 */;
657
+ case "f_dc_1":
658
+ return 27 /* PLYValue.F_DC_1 */;
659
+ case "f_dc_2":
660
+ return 28 /* PLYValue.F_DC_2 */;
661
+ case "f_dc_3":
662
+ return 29 /* PLYValue.F_DC_3 */;
663
+ case "opacity":
664
+ return 25 /* PLYValue.OPACITY */;
665
+ case "rot_0":
666
+ return 30 /* PLYValue.ROT_0 */;
667
+ case "rot_1":
668
+ return 31 /* PLYValue.ROT_1 */;
669
+ case "rot_2":
670
+ return 32 /* PLYValue.ROT_2 */;
671
+ case "rot_3":
672
+ return 33 /* PLYValue.ROT_3 */;
673
+ case "min_r":
674
+ return 34 /* PLYValue.MIN_COLOR_R */;
675
+ case "min_g":
676
+ return 35 /* PLYValue.MIN_COLOR_G */;
677
+ case "min_b":
678
+ return 36 /* PLYValue.MIN_COLOR_B */;
679
+ case "max_r":
680
+ return 37 /* PLYValue.MAX_COLOR_R */;
681
+ case "max_g":
682
+ return 38 /* PLYValue.MAX_COLOR_G */;
683
+ case "max_b":
684
+ return 39 /* PLYValue.MAX_COLOR_B */;
685
+ case "f_rest_0":
686
+ return 40 /* PLYValue.SH_0 */;
687
+ case "f_rest_1":
688
+ return 41 /* PLYValue.SH_1 */;
689
+ case "f_rest_2":
690
+ return 42 /* PLYValue.SH_2 */;
691
+ case "f_rest_3":
692
+ return 43 /* PLYValue.SH_3 */;
693
+ case "f_rest_4":
694
+ return 44 /* PLYValue.SH_4 */;
695
+ case "f_rest_5":
696
+ return 45 /* PLYValue.SH_5 */;
697
+ case "f_rest_6":
698
+ return 46 /* PLYValue.SH_6 */;
699
+ case "f_rest_7":
700
+ return 47 /* PLYValue.SH_7 */;
701
+ case "f_rest_8":
702
+ return 48 /* PLYValue.SH_8 */;
703
+ case "f_rest_9":
704
+ return 49 /* PLYValue.SH_9 */;
705
+ case "f_rest_10":
706
+ return 50 /* PLYValue.SH_10 */;
707
+ case "f_rest_11":
708
+ return 51 /* PLYValue.SH_11 */;
709
+ case "f_rest_12":
710
+ return 52 /* PLYValue.SH_12 */;
711
+ case "f_rest_13":
712
+ return 53 /* PLYValue.SH_13 */;
713
+ case "f_rest_14":
714
+ return 54 /* PLYValue.SH_14 */;
715
+ case "f_rest_15":
716
+ return 55 /* PLYValue.SH_15 */;
717
+ case "f_rest_16":
718
+ return 56 /* PLYValue.SH_16 */;
719
+ case "f_rest_17":
720
+ return 57 /* PLYValue.SH_17 */;
721
+ case "f_rest_18":
722
+ return 58 /* PLYValue.SH_18 */;
723
+ case "f_rest_19":
724
+ return 59 /* PLYValue.SH_19 */;
725
+ case "f_rest_20":
726
+ return 60 /* PLYValue.SH_20 */;
727
+ case "f_rest_21":
728
+ return 61 /* PLYValue.SH_21 */;
729
+ case "f_rest_22":
730
+ return 62 /* PLYValue.SH_22 */;
731
+ case "f_rest_23":
732
+ return 63 /* PLYValue.SH_23 */;
733
+ case "f_rest_24":
734
+ return 64 /* PLYValue.SH_24 */;
735
+ case "f_rest_25":
736
+ return 65 /* PLYValue.SH_25 */;
737
+ case "f_rest_26":
738
+ return 66 /* PLYValue.SH_26 */;
739
+ case "f_rest_27":
740
+ return 67 /* PLYValue.SH_27 */;
741
+ case "f_rest_28":
742
+ return 68 /* PLYValue.SH_28 */;
743
+ case "f_rest_29":
744
+ return 69 /* PLYValue.SH_29 */;
745
+ case "f_rest_30":
746
+ return 70 /* PLYValue.SH_30 */;
747
+ case "f_rest_31":
748
+ return 71 /* PLYValue.SH_31 */;
749
+ case "f_rest_32":
750
+ return 72 /* PLYValue.SH_32 */;
751
+ case "f_rest_33":
752
+ return 73 /* PLYValue.SH_33 */;
753
+ case "f_rest_34":
754
+ return 74 /* PLYValue.SH_34 */;
755
+ case "f_rest_35":
756
+ return 75 /* PLYValue.SH_35 */;
757
+ case "f_rest_36":
758
+ return 76 /* PLYValue.SH_36 */;
759
+ case "f_rest_37":
760
+ return 77 /* PLYValue.SH_37 */;
761
+ case "f_rest_38":
762
+ return 78 /* PLYValue.SH_38 */;
763
+ case "f_rest_39":
764
+ return 79 /* PLYValue.SH_39 */;
765
+ case "f_rest_40":
766
+ return 80 /* PLYValue.SH_40 */;
767
+ case "f_rest_41":
768
+ return 81 /* PLYValue.SH_41 */;
769
+ case "f_rest_42":
770
+ return 82 /* PLYValue.SH_42 */;
771
+ case "f_rest_43":
772
+ return 83 /* PLYValue.SH_43 */;
773
+ case "f_rest_44":
774
+ return 84 /* PLYValue.SH_44 */;
775
+ }
776
+ return 85 /* PLYValue.UNDEFINED */;
777
+ }
778
+ /**
779
+ * Parse a PLY file header and returns metas infos on splats and chunks
780
+ * @param data the loaded buffer
781
+ * @returns a PLYHeader
782
+ */
783
+ static ParseHeader(data) {
784
+ const ubuf = new Uint8Array(data);
785
+ const header = new TextDecoder().decode(ubuf.slice(0, 1024 * 10));
786
+ const headerEnd = "end_header\n";
787
+ const headerEndIndex = header.indexOf(headerEnd);
788
+ if (headerEndIndex < 0 || !header) {
789
+ // standard splat
790
+ return null;
791
+ }
792
+ const vertexCount = parseInt(/element vertex (\d+)\n/.exec(header)[1]);
793
+ const chunkElement = /element chunk (\d+)\n/.exec(header);
794
+ let chunkCount = 0;
795
+ if (chunkElement) {
796
+ chunkCount = parseInt(chunkElement[1]);
797
+ }
798
+ let rowVertexOffset = 0;
799
+ let rowChunkOffset = 0;
800
+ const offsets = {
801
+ double: 8,
802
+ int: 4,
803
+ uint: 4,
804
+ float: 4,
805
+ short: 2,
806
+ ushort: 2,
807
+ uchar: 1,
808
+ list: 0,
809
+ };
810
+ let ElementMode;
811
+ (function (ElementMode) {
812
+ ElementMode[ElementMode["Vertex"] = 0] = "Vertex";
813
+ ElementMode[ElementMode["Chunk"] = 1] = "Chunk";
814
+ ElementMode[ElementMode["SH"] = 2] = "SH";
815
+ ElementMode[ElementMode["Unused"] = 3] = "Unused";
816
+ })(ElementMode || (ElementMode = {}));
817
+ let chunkMode = 1 /* ElementMode.Chunk */;
818
+ const vertexProperties = [];
819
+ const chunkProperties = [];
820
+ const filtered = header.slice(0, headerEndIndex).split("\n");
821
+ let shDegree = 0;
822
+ for (const prop of filtered) {
823
+ if (prop.startsWith("property ")) {
824
+ const [, typeName, name] = prop.split(" ");
825
+ const value = GaussianSplattingMeshBase._ValueNameToEnum(name);
826
+ if (value != 85 /* PLYValue.UNDEFINED */) {
827
+ // SH degree 1,2 or 3 for 9, 24 or 45 values
828
+ if (value >= 84 /* PLYValue.SH_44 */) {
829
+ shDegree = 3;
830
+ }
831
+ else if (value >= 64 /* PLYValue.SH_24 */) {
832
+ shDegree = Math.max(shDegree, 2);
833
+ }
834
+ else if (value >= 48 /* PLYValue.SH_8 */) {
835
+ shDegree = Math.max(shDegree, 1);
836
+ }
837
+ }
838
+ const type = GaussianSplattingMeshBase._TypeNameToEnum(typeName);
839
+ if (chunkMode == 1 /* ElementMode.Chunk */) {
840
+ chunkProperties.push({ value, type, offset: rowChunkOffset });
841
+ rowChunkOffset += offsets[typeName];
842
+ }
843
+ else if (chunkMode == 0 /* ElementMode.Vertex */) {
844
+ vertexProperties.push({ value, type, offset: rowVertexOffset });
845
+ rowVertexOffset += offsets[typeName];
846
+ }
847
+ else if (chunkMode == 2 /* ElementMode.SH */) {
848
+ // SH doesn't count for vertex row size but its properties are used to retrieve SH
849
+ vertexProperties.push({ value, type, offset: rowVertexOffset });
850
+ }
851
+ if (!offsets[typeName]) {
852
+ Logger.Warn(`Unsupported property type: ${typeName}.`);
853
+ }
854
+ }
855
+ else if (prop.startsWith("element ")) {
856
+ const [, type] = prop.split(" ");
857
+ if (type == "chunk") {
858
+ chunkMode = 1 /* ElementMode.Chunk */;
859
+ }
860
+ else if (type == "vertex") {
861
+ chunkMode = 0 /* ElementMode.Vertex */;
862
+ }
863
+ else if (type == "sh") {
864
+ chunkMode = 2 /* ElementMode.SH */;
865
+ }
866
+ else {
867
+ chunkMode = 3 /* ElementMode.Unused */;
868
+ }
869
+ }
870
+ }
871
+ const dataView = new DataView(data, headerEndIndex + headerEnd.length);
872
+ const buffer = new ArrayBuffer(GaussianSplattingMeshBase._RowOutputLength * vertexCount);
873
+ let shBuffer = null;
874
+ let shCoefficientCount = 0;
875
+ if (shDegree) {
876
+ const shVectorCount = (shDegree + 1) * (shDegree + 1) - 1;
877
+ shCoefficientCount = shVectorCount * 3;
878
+ shBuffer = new ArrayBuffer(shCoefficientCount * vertexCount);
879
+ }
880
+ return {
881
+ vertexCount: vertexCount,
882
+ chunkCount: chunkCount,
883
+ rowVertexLength: rowVertexOffset,
884
+ rowChunkLength: rowChunkOffset,
885
+ vertexProperties: vertexProperties,
886
+ chunkProperties: chunkProperties,
887
+ dataView: dataView,
888
+ buffer: buffer,
889
+ shDegree: shDegree,
890
+ shCoefficientCount: shCoefficientCount,
891
+ shBuffer: shBuffer,
892
+ };
893
+ }
894
+ static _GetCompressedChunks(header, offset) {
895
+ if (!header.chunkCount) {
896
+ return null;
897
+ }
898
+ const dataView = header.dataView;
899
+ const compressedChunks = new Array(header.chunkCount);
900
+ for (let i = 0; i < header.chunkCount; i++) {
901
+ const currentChunk = {
902
+ min: new Vector3(),
903
+ max: new Vector3(),
904
+ minScale: new Vector3(),
905
+ maxScale: new Vector3(),
906
+ minColor: new Vector3(0, 0, 0),
907
+ maxColor: new Vector3(1, 1, 1),
908
+ };
909
+ compressedChunks[i] = currentChunk;
910
+ for (let propertyIndex = 0; propertyIndex < header.chunkProperties.length; propertyIndex++) {
911
+ const property = header.chunkProperties[propertyIndex];
912
+ let value;
913
+ switch (property.type) {
914
+ case 0 /* PLYType.FLOAT */:
915
+ value = dataView.getFloat32(property.offset + offset.value, true);
916
+ break;
917
+ default:
918
+ continue;
919
+ }
920
+ switch (property.value) {
921
+ case 0 /* PLYValue.MIN_X */:
922
+ currentChunk.min.x = value;
923
+ break;
924
+ case 1 /* PLYValue.MIN_Y */:
925
+ currentChunk.min.y = value;
926
+ break;
927
+ case 2 /* PLYValue.MIN_Z */:
928
+ currentChunk.min.z = value;
929
+ break;
930
+ case 3 /* PLYValue.MAX_X */:
931
+ currentChunk.max.x = value;
932
+ break;
933
+ case 4 /* PLYValue.MAX_Y */:
934
+ currentChunk.max.y = value;
935
+ break;
936
+ case 5 /* PLYValue.MAX_Z */:
937
+ currentChunk.max.z = value;
938
+ break;
939
+ case 6 /* PLYValue.MIN_SCALE_X */:
940
+ currentChunk.minScale.x = value;
941
+ break;
942
+ case 7 /* PLYValue.MIN_SCALE_Y */:
943
+ currentChunk.minScale.y = value;
944
+ break;
945
+ case 8 /* PLYValue.MIN_SCALE_Z */:
946
+ currentChunk.minScale.z = value;
947
+ break;
948
+ case 9 /* PLYValue.MAX_SCALE_X */:
949
+ currentChunk.maxScale.x = value;
950
+ break;
951
+ case 10 /* PLYValue.MAX_SCALE_Y */:
952
+ currentChunk.maxScale.y = value;
953
+ break;
954
+ case 11 /* PLYValue.MAX_SCALE_Z */:
955
+ currentChunk.maxScale.z = value;
956
+ break;
957
+ case 34 /* PLYValue.MIN_COLOR_R */:
958
+ currentChunk.minColor.x = value;
959
+ break;
960
+ case 35 /* PLYValue.MIN_COLOR_G */:
961
+ currentChunk.minColor.y = value;
962
+ break;
963
+ case 36 /* PLYValue.MIN_COLOR_B */:
964
+ currentChunk.minColor.z = value;
965
+ break;
966
+ case 37 /* PLYValue.MAX_COLOR_R */:
967
+ currentChunk.maxColor.x = value;
968
+ break;
969
+ case 38 /* PLYValue.MAX_COLOR_G */:
970
+ currentChunk.maxColor.y = value;
971
+ break;
972
+ case 39 /* PLYValue.MAX_COLOR_B */:
973
+ currentChunk.maxColor.z = value;
974
+ break;
975
+ }
976
+ }
977
+ offset.value += header.rowChunkLength;
978
+ }
979
+ return compressedChunks;
980
+ }
981
+ static _GetSplat(header, index, compressedChunks, offset) {
982
+ const q = TmpVectors.Quaternion[0];
983
+ const temp3 = TmpVectors.Vector3[0];
984
+ const rowOutputLength = GaussianSplattingMeshBase._RowOutputLength;
985
+ const buffer = header.buffer;
986
+ const dataView = header.dataView;
987
+ const position = new Float32Array(buffer, index * rowOutputLength, 3);
988
+ const scale = new Float32Array(buffer, index * rowOutputLength + 12, 3);
989
+ const rgba = new Uint8ClampedArray(buffer, index * rowOutputLength + 24, 4);
990
+ const rot = new Uint8ClampedArray(buffer, index * rowOutputLength + 28, 4);
991
+ let sh = null;
992
+ if (header.shBuffer) {
993
+ sh = new Uint8ClampedArray(header.shBuffer, index * header.shCoefficientCount, header.shCoefficientCount);
994
+ }
995
+ const chunkIndex = index >> 8;
996
+ let r0 = 255;
997
+ let r1 = 0;
998
+ let r2 = 0;
999
+ let r3 = 0;
1000
+ const plySH = [];
1001
+ for (let propertyIndex = 0; propertyIndex < header.vertexProperties.length; propertyIndex++) {
1002
+ const property = header.vertexProperties[propertyIndex];
1003
+ let value;
1004
+ switch (property.type) {
1005
+ case 0 /* PLYType.FLOAT */:
1006
+ value = dataView.getFloat32(offset.value + property.offset, true);
1007
+ break;
1008
+ case 1 /* PLYType.INT */:
1009
+ value = dataView.getInt32(offset.value + property.offset, true);
1010
+ break;
1011
+ case 2 /* PLYType.UINT */:
1012
+ value = dataView.getUint32(offset.value + property.offset, true);
1013
+ break;
1014
+ case 3 /* PLYType.DOUBLE */:
1015
+ value = dataView.getFloat64(offset.value + property.offset, true);
1016
+ break;
1017
+ case 4 /* PLYType.UCHAR */:
1018
+ value = dataView.getUint8(offset.value + property.offset);
1019
+ break;
1020
+ default:
1021
+ continue;
1022
+ }
1023
+ switch (property.value) {
1024
+ case 12 /* PLYValue.PACKED_POSITION */:
1025
+ {
1026
+ const compressedChunk = compressedChunks[chunkIndex];
1027
+ Unpack111011(value, temp3);
1028
+ position[0] = Scalar.Lerp(compressedChunk.min.x, compressedChunk.max.x, temp3.x);
1029
+ position[1] = Scalar.Lerp(compressedChunk.min.y, compressedChunk.max.y, temp3.y);
1030
+ position[2] = Scalar.Lerp(compressedChunk.min.z, compressedChunk.max.z, temp3.z);
1031
+ }
1032
+ break;
1033
+ case 13 /* PLYValue.PACKED_ROTATION */:
1034
+ {
1035
+ UnpackRot(value, q);
1036
+ r0 = q.x;
1037
+ r1 = q.y;
1038
+ r2 = q.z;
1039
+ r3 = q.w;
1040
+ }
1041
+ break;
1042
+ case 14 /* PLYValue.PACKED_SCALE */:
1043
+ {
1044
+ const compressedChunk = compressedChunks[chunkIndex];
1045
+ Unpack111011(value, temp3);
1046
+ scale[0] = Math.exp(Scalar.Lerp(compressedChunk.minScale.x, compressedChunk.maxScale.x, temp3.x));
1047
+ scale[1] = Math.exp(Scalar.Lerp(compressedChunk.minScale.y, compressedChunk.maxScale.y, temp3.y));
1048
+ scale[2] = Math.exp(Scalar.Lerp(compressedChunk.minScale.z, compressedChunk.maxScale.z, temp3.z));
1049
+ }
1050
+ break;
1051
+ case 15 /* PLYValue.PACKED_COLOR */:
1052
+ {
1053
+ const compressedChunk = compressedChunks[chunkIndex];
1054
+ Unpack8888(value, rgba);
1055
+ rgba[0] = Scalar.Lerp(compressedChunk.minColor.x, compressedChunk.maxColor.x, rgba[0] / 255) * 255;
1056
+ rgba[1] = Scalar.Lerp(compressedChunk.minColor.y, compressedChunk.maxColor.y, rgba[1] / 255) * 255;
1057
+ rgba[2] = Scalar.Lerp(compressedChunk.minColor.z, compressedChunk.maxColor.z, rgba[2] / 255) * 255;
1058
+ }
1059
+ break;
1060
+ case 16 /* PLYValue.X */:
1061
+ position[0] = value;
1062
+ break;
1063
+ case 17 /* PLYValue.Y */:
1064
+ position[1] = value;
1065
+ break;
1066
+ case 18 /* PLYValue.Z */:
1067
+ position[2] = value;
1068
+ break;
1069
+ case 19 /* PLYValue.SCALE_0 */:
1070
+ scale[0] = Math.exp(value);
1071
+ break;
1072
+ case 20 /* PLYValue.SCALE_1 */:
1073
+ scale[1] = Math.exp(value);
1074
+ break;
1075
+ case 21 /* PLYValue.SCALE_2 */:
1076
+ scale[2] = Math.exp(value);
1077
+ break;
1078
+ case 22 /* PLYValue.DIFFUSE_RED */:
1079
+ rgba[0] = value;
1080
+ break;
1081
+ case 23 /* PLYValue.DIFFUSE_GREEN */:
1082
+ rgba[1] = value;
1083
+ break;
1084
+ case 24 /* PLYValue.DIFFUSE_BLUE */:
1085
+ rgba[2] = value;
1086
+ break;
1087
+ case 26 /* PLYValue.F_DC_0 */:
1088
+ rgba[0] = (0.5 + GaussianSplattingMeshBase._SH_C0 * value) * 255;
1089
+ break;
1090
+ case 27 /* PLYValue.F_DC_1 */:
1091
+ rgba[1] = (0.5 + GaussianSplattingMeshBase._SH_C0 * value) * 255;
1092
+ break;
1093
+ case 28 /* PLYValue.F_DC_2 */:
1094
+ rgba[2] = (0.5 + GaussianSplattingMeshBase._SH_C0 * value) * 255;
1095
+ break;
1096
+ case 29 /* PLYValue.F_DC_3 */:
1097
+ rgba[3] = (0.5 + GaussianSplattingMeshBase._SH_C0 * value) * 255;
1098
+ break;
1099
+ case 25 /* PLYValue.OPACITY */:
1100
+ rgba[3] = (1 / (1 + Math.exp(-value))) * 255;
1101
+ break;
1102
+ case 30 /* PLYValue.ROT_0 */:
1103
+ r0 = value;
1104
+ break;
1105
+ case 31 /* PLYValue.ROT_1 */:
1106
+ r1 = value;
1107
+ break;
1108
+ case 32 /* PLYValue.ROT_2 */:
1109
+ r2 = value;
1110
+ break;
1111
+ case 33 /* PLYValue.ROT_3 */:
1112
+ r3 = value;
1113
+ break;
1114
+ }
1115
+ if (sh && property.value >= 40 /* PLYValue.SH_0 */ && property.value <= 84 /* PLYValue.SH_44 */) {
1116
+ const shIndex = property.value - 40 /* PLYValue.SH_0 */;
1117
+ if (property.type == 4 /* PLYType.UCHAR */ && header.chunkCount) {
1118
+ // compressed ply. dataView points to beginning of vertex
1119
+ // could be improved with a direct copy instead of a per SH index computation + copy
1120
+ const compressedValue = dataView.getUint8(header.rowChunkLength * header.chunkCount + header.vertexCount * header.rowVertexLength + index * header.shCoefficientCount + shIndex);
1121
+ // compressed .ply SH import : https://github.com/playcanvas/engine/blob/fda3f0368b45d7381f0b5a1722bd2056128eaebe/src/scene/gsplat/gsplat-compressed-data.js#L88C81-L88C98
1122
+ plySH[shIndex] = (compressedValue * (8 / 255) - 4) * 127.5 + 127.5;
1123
+ }
1124
+ else {
1125
+ const clampedValue = Scalar.Clamp(value * 127.5 + 127.5, 0, 255);
1126
+ plySH[shIndex] = clampedValue;
1127
+ }
1128
+ }
1129
+ }
1130
+ if (sh) {
1131
+ const shDim = header.shDegree == 1 ? 3 : header.shDegree == 2 ? 8 : 15;
1132
+ for (let j = 0; j < shDim; j++) {
1133
+ sh[j * 3 + 0] = plySH[j];
1134
+ sh[j * 3 + 1] = plySH[j + shDim];
1135
+ sh[j * 3 + 2] = plySH[j + shDim * 2];
1136
+ }
1137
+ }
1138
+ q.set(r1, r2, r3, r0);
1139
+ q.normalize();
1140
+ rot[0] = q.w * 127.5 + 127.5;
1141
+ rot[1] = q.x * 127.5 + 127.5;
1142
+ rot[2] = q.y * 127.5 + 127.5;
1143
+ rot[3] = q.z * 127.5 + 127.5;
1144
+ offset.value += header.rowVertexLength;
1145
+ }
1146
+ /**
1147
+ * Converts a .ply data with SH coefficients splat
1148
+ * if data array buffer is not ply, returns the original buffer
1149
+ * @param data the .ply data to load
1150
+ * @param useCoroutine use coroutine and yield
1151
+ * @returns the loaded splat buffer and optional array of sh coefficients
1152
+ */
1153
+ static *ConvertPLYWithSHToSplat(data, useCoroutine = false) {
1154
+ const header = GaussianSplattingMeshBase.ParseHeader(data);
1155
+ if (!header) {
1156
+ return { buffer: data };
1157
+ }
1158
+ const offset = { value: 0 };
1159
+ const compressedChunks = GaussianSplattingMeshBase._GetCompressedChunks(header, offset);
1160
+ for (let i = 0; i < header.vertexCount; i++) {
1161
+ GaussianSplattingMeshBase._GetSplat(header, i, compressedChunks, offset);
1162
+ if (i % GaussianSplattingMeshBase._PlyConversionBatchSize === 0 && useCoroutine) {
1163
+ yield;
1164
+ }
1165
+ }
1166
+ let sh = null;
1167
+ // make SH texture buffers
1168
+ if (header.shDegree && header.shBuffer) {
1169
+ const textureCount = Math.ceil(header.shCoefficientCount / 16); // 4 components can be stored per texture, 4 sh per component
1170
+ let shIndexRead = 0;
1171
+ const ubuf = new Uint8Array(header.shBuffer);
1172
+ // sh is an array of uint8array that will be used to create sh textures
1173
+ sh = [];
1174
+ const splatCount = header.vertexCount;
1175
+ const engine = EngineStore.LastCreatedEngine;
1176
+ if (engine) {
1177
+ const width = engine.getCaps().maxTextureSize;
1178
+ const height = Math.ceil(splatCount / width);
1179
+ // create array for the number of textures needed.
1180
+ for (let textureIndex = 0; textureIndex < textureCount; textureIndex++) {
1181
+ const texture = new Uint8Array(height * width * 4 * 4); // 4 components per texture, 4 sh per component
1182
+ sh.push(texture);
1183
+ }
1184
+ for (let i = 0; i < splatCount; i++) {
1185
+ for (let shIndexWrite = 0; shIndexWrite < header.shCoefficientCount; shIndexWrite++) {
1186
+ const shValue = ubuf[shIndexRead++];
1187
+ const textureIndex = Math.floor(shIndexWrite / 16);
1188
+ const shArray = sh[textureIndex];
1189
+ const byteIndexInTexture = shIndexWrite % 16; // [0..15]
1190
+ const offsetPerSplat = i * 16; // 16 sh values per texture per splat.
1191
+ shArray[byteIndexInTexture + offsetPerSplat] = shValue;
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ return { buffer: header.buffer, sh: sh };
1197
+ }
1198
+ /**
1199
+ * Converts a .ply data array buffer to splat
1200
+ * if data array buffer is not ply, returns the original buffer
1201
+ * @param data the .ply data to load
1202
+ * @param useCoroutine use coroutine and yield
1203
+ * @returns the loaded splat buffer without SH coefficient, whether ply contains or not SH.
1204
+ */
1205
+ static *ConvertPLYToSplat(data, useCoroutine = false) {
1206
+ const header = GaussianSplattingMeshBase.ParseHeader(data);
1207
+ if (!header) {
1208
+ return data;
1209
+ }
1210
+ const offset = { value: 0 };
1211
+ const compressedChunks = GaussianSplattingMeshBase._GetCompressedChunks(header, offset);
1212
+ for (let i = 0; i < header.vertexCount; i++) {
1213
+ GaussianSplattingMeshBase._GetSplat(header, i, compressedChunks, offset);
1214
+ if (i % GaussianSplattingMeshBase._PlyConversionBatchSize === 0 && useCoroutine) {
1215
+ yield;
1216
+ }
1217
+ }
1218
+ return header.buffer;
1219
+ }
1220
+ /**
1221
+ * Converts a .ply data array buffer to splat
1222
+ * if data array buffer is not ply, returns the original buffer
1223
+ * @param data the .ply data to load
1224
+ * @returns the loaded splat buffer
1225
+ */
1226
+ static async ConvertPLYToSplatAsync(data) {
1227
+ return await runCoroutineAsync(GaussianSplattingMeshBase.ConvertPLYToSplat(data, true), createYieldingScheduler());
1228
+ }
1229
+ /**
1230
+ * Converts a .ply with SH data array buffer to splat
1231
+ * if data array buffer is not ply, returns the original buffer
1232
+ * @param data the .ply data to load
1233
+ * @returns the loaded splat buffer with SH
1234
+ */
1235
+ static async ConvertPLYWithSHToSplatAsync(data) {
1236
+ return await runCoroutineAsync(GaussianSplattingMeshBase.ConvertPLYWithSHToSplat(data, true), createYieldingScheduler());
1237
+ }
1238
+ /**
1239
+ * Loads a .splat Gaussian Splatting array buffer asynchronously
1240
+ * @param data arraybuffer containing splat file
1241
+ * @returns a promise that resolves when the operation is complete
1242
+ */
1243
+ async loadDataAsync(data) {
1244
+ return await this.updateDataAsync(data);
1245
+ }
1246
+ /**
1247
+ * Loads a Gaussian or Splatting file asynchronously
1248
+ * @param url path to the splat file to load
1249
+ * @param scene optional scene it belongs to
1250
+ * @returns a promise that resolves when the operation is complete
1251
+ * @deprecated Please use SceneLoader.ImportMeshAsync instead
1252
+ */
1253
+ async loadFileAsync(url, scene) {
1254
+ await ImportMeshAsync(url, (scene || EngineStore.LastCreatedScene), { pluginOptions: { splat: { gaussianSplattingMesh: this } } });
1255
+ }
1256
+ /**
1257
+ * Releases resources associated with this mesh.
1258
+ * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
1259
+ */
1260
+ dispose(doNotRecurse) {
1261
+ this._covariancesATexture?.dispose();
1262
+ this._covariancesBTexture?.dispose();
1263
+ this._centersTexture?.dispose();
1264
+ this._colorsTexture?.dispose();
1265
+ if (this._shTextures) {
1266
+ for (const shTexture of this._shTextures) {
1267
+ shTexture.dispose();
1268
+ }
1269
+ }
1270
+ this._covariancesATexture = null;
1271
+ this._covariancesBTexture = null;
1272
+ this._centersTexture = null;
1273
+ this._colorsTexture = null;
1274
+ this._shTextures = null;
1275
+ this._cachedBoundingMin = null;
1276
+ this._cachedBoundingMax = null;
1277
+ // Note: _splatsData and _shData are intentionally kept alive after dispose.
1278
+ // They serve as the source reference for the compound API (addPart/removePart)
1279
+ // when this mesh is held by a GaussianSplattingPartProxyMesh.compoundSplatMesh.
1280
+ this._worker?.terminate();
1281
+ this._worker = null;
1282
+ // delete meshes created for each camera
1283
+ this._cameraViewInfos.forEach((cameraViewInfo) => {
1284
+ cameraViewInfo.mesh.dispose();
1285
+ });
1286
+ super.dispose(doNotRecurse, true);
1287
+ }
1288
+ _copyTextures(source) {
1289
+ this._covariancesATexture = source.covariancesATexture?.clone();
1290
+ this._covariancesBTexture = source.covariancesBTexture?.clone();
1291
+ this._centersTexture = source.centersTexture?.clone();
1292
+ this._colorsTexture = source.colorsTexture?.clone();
1293
+ if (source._shTextures) {
1294
+ this._shTextures = [];
1295
+ for (const shTexture of source._shTextures) {
1296
+ this._shTextures?.push(shTexture.clone());
1297
+ }
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Returns a new Mesh object generated from the current mesh properties.
1302
+ * @param name is a string, the name given to the new mesh
1303
+ * @returns a new Gaussian Splatting Mesh
1304
+ */
1305
+ clone(name = "") {
1306
+ const newGS = new GaussianSplattingMeshBase(name, undefined, this.getScene());
1307
+ newGS._copySource(this);
1308
+ newGS.makeGeometryUnique();
1309
+ newGS._vertexCount = this._vertexCount;
1310
+ newGS._copyTextures(this);
1311
+ newGS._modelViewProjectionMatrix = Matrix.Identity();
1312
+ newGS._splatPositions = this._splatPositions;
1313
+ newGS._readyToDisplay = false;
1314
+ newGS._disableDepthSort = this._disableDepthSort;
1315
+ newGS._instantiateWorker();
1316
+ const binfo = this.getBoundingInfo();
1317
+ newGS.getBoundingInfo().reConstruct(binfo.minimum, binfo.maximum, this.getWorldMatrix());
1318
+ newGS.forcedInstanceCount = this.forcedInstanceCount;
1319
+ newGS.setEnabled(true);
1320
+ return newGS;
1321
+ }
1322
+ _makeEmptySplat(index, covA, covB, colorArray) {
1323
+ const covBSItemSize = this._useRGBACovariants ? 4 : 2;
1324
+ this._splatPositions[4 * index + 0] = 0;
1325
+ this._splatPositions[4 * index + 1] = 0;
1326
+ this._splatPositions[4 * index + 2] = 0;
1327
+ covA[index * 4 + 0] = ToHalfFloat(0);
1328
+ covA[index * 4 + 1] = ToHalfFloat(0);
1329
+ covA[index * 4 + 2] = ToHalfFloat(0);
1330
+ covA[index * 4 + 3] = ToHalfFloat(0);
1331
+ covB[index * covBSItemSize + 0] = ToHalfFloat(0);
1332
+ covB[index * covBSItemSize + 1] = ToHalfFloat(0);
1333
+ colorArray[index * 4 + 3] = 0;
1334
+ }
1335
+ /**
1336
+ * Processes a single splat from the source buffer (at srcIndex) and writes the result into
1337
+ * the destination texture arrays at dstIndex. This decoupling allows addPart to feed multiple
1338
+ * independent source buffers into a single set of destination arrays without merging them first.
1339
+ * @param dstIndex - destination splat index (into _splatPositions, covA, covB, colorArray)
1340
+ * @param fBuffer - float32 view of the source .splat buffer
1341
+ * @param uBuffer - uint8 view of the source .splat buffer
1342
+ * @param covA - destination covariancesA array
1343
+ * @param covB - destination covariancesB array
1344
+ * @param colorArray - destination color array
1345
+ * @param minimum - accumulated bounding minimum (updated in-place)
1346
+ * @param maximum - accumulated bounding maximum (updated in-place)
1347
+ * @param flipY - whether to negate the Y position
1348
+ * @param srcIndex - source splat index (defaults to dstIndex when omitted)
1349
+ */
1350
+ _makeSplat(dstIndex, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, flipY, srcIndex = dstIndex) {
1351
+ const matrixRotation = TmpVectors.Matrix[0];
1352
+ const matrixScale = TmpVectors.Matrix[1];
1353
+ const quaternion = TmpVectors.Quaternion[0];
1354
+ const covBSItemSize = this._useRGBACovariants ? 4 : 2;
1355
+ const x = fBuffer[8 * srcIndex + 0];
1356
+ const y = fBuffer[8 * srcIndex + 1] * (flipY ? -1 : 1);
1357
+ const z = fBuffer[8 * srcIndex + 2];
1358
+ this._splatPositions[4 * dstIndex + 0] = x;
1359
+ this._splatPositions[4 * dstIndex + 1] = y;
1360
+ this._splatPositions[4 * dstIndex + 2] = z;
1361
+ minimum.minimizeInPlaceFromFloats(x, y, z);
1362
+ maximum.maximizeInPlaceFromFloats(x, y, z);
1363
+ quaternion.set((uBuffer[32 * srcIndex + 28 + 1] - 127.5) / 127.5, (uBuffer[32 * srcIndex + 28 + 2] - 127.5) / 127.5, (uBuffer[32 * srcIndex + 28 + 3] - 127.5) / 127.5, -(uBuffer[32 * srcIndex + 28 + 0] - 127.5) / 127.5);
1364
+ quaternion.normalize();
1365
+ quaternion.toRotationMatrix(matrixRotation);
1366
+ Matrix.ScalingToRef(fBuffer[8 * srcIndex + 3 + 0] * 2, fBuffer[8 * srcIndex + 3 + 1] * 2, fBuffer[8 * srcIndex + 3 + 2] * 2, matrixScale);
1367
+ const m = matrixRotation.multiplyToRef(matrixScale, TmpVectors.Matrix[0]).m;
1368
+ const covariances = this._tmpCovariances;
1369
+ covariances[0] = m[0] * m[0] + m[1] * m[1] + m[2] * m[2];
1370
+ covariances[1] = m[0] * m[4] + m[1] * m[5] + m[2] * m[6];
1371
+ covariances[2] = m[0] * m[8] + m[1] * m[9] + m[2] * m[10];
1372
+ covariances[3] = m[4] * m[4] + m[5] * m[5] + m[6] * m[6];
1373
+ covariances[4] = m[4] * m[8] + m[5] * m[9] + m[6] * m[10];
1374
+ covariances[5] = m[8] * m[8] + m[9] * m[9] + m[10] * m[10];
1375
+ // normalize covA, covB
1376
+ let factor = -10000;
1377
+ for (let covIndex = 0; covIndex < 6; covIndex++) {
1378
+ factor = Math.max(factor, Math.abs(covariances[covIndex]));
1379
+ }
1380
+ this._splatPositions[4 * dstIndex + 3] = factor;
1381
+ const transform = factor;
1382
+ covA[dstIndex * 4 + 0] = ToHalfFloat(covariances[0] / transform);
1383
+ covA[dstIndex * 4 + 1] = ToHalfFloat(covariances[1] / transform);
1384
+ covA[dstIndex * 4 + 2] = ToHalfFloat(covariances[2] / transform);
1385
+ covA[dstIndex * 4 + 3] = ToHalfFloat(covariances[3] / transform);
1386
+ covB[dstIndex * covBSItemSize + 0] = ToHalfFloat(covariances[4] / transform);
1387
+ covB[dstIndex * covBSItemSize + 1] = ToHalfFloat(covariances[5] / transform);
1388
+ // colors
1389
+ colorArray[dstIndex * 4 + 0] = uBuffer[32 * srcIndex + 24 + 0];
1390
+ colorArray[dstIndex * 4 + 1] = uBuffer[32 * srcIndex + 24 + 1];
1391
+ colorArray[dstIndex * 4 + 2] = uBuffer[32 * srcIndex + 24 + 2];
1392
+ colorArray[dstIndex * 4 + 3] = uBuffer[32 * srcIndex + 24 + 3];
1393
+ }
1394
+ _onUpdateTextures(_textureSize) { }
1395
+ /**
1396
+ * Called when part index data is received during a data load. Override to store and manage
1397
+ * part index state (e.g. allocating the padded Uint8Array).
1398
+ * No-op in the base class.
1399
+ * @param _partIndices - the raw part indices passed in by the caller
1400
+ * @param _textureLength - the padded texture length (width × height) to allocate into
1401
+ */
1402
+ _onIndexDataReceived(_partIndices, _textureLength) { }
1403
+ /**
1404
+ * Called at the start of an incremental texture update, before any splats are processed.
1405
+ * Override to perform incremental-specific setup, such as ensuring the part-index GPU texture
1406
+ * exists before the sub-texture upload begins.
1407
+ * No-op in the base class.
1408
+ * @param _textureSize - current texture dimensions
1409
+ */
1410
+ _onIncrementalUpdateStart(_textureSize) { }
1411
+ /**
1412
+ * Whether this mesh is in compound mode (has at least one part added via addPart).
1413
+ * Returns `false` in the base class; overridden to return `true` in the compound subclass.
1414
+ * Consumed by the material and depth renderer to toggle compound-specific shader paths.
1415
+ * @internal
1416
+ */
1417
+ get isCompound() {
1418
+ return false;
1419
+ }
1420
+ _setDelayedTextureUpdate(covA, covB, colorArray, sh) {
1421
+ this._delayedTextureUpdate = { covA, covB, colors: colorArray, centers: this._splatPositions, sh };
1422
+ }
1423
+ // NB: partIndices is assumed to be padded to a round texture size
1424
+ _updateTextures(covA, covB, colorArray, sh) {
1425
+ const textureSize = this._getTextureSize(this._vertexCount);
1426
+ // Update the textures
1427
+ const createTextureFromData = (data, width, height, format) => {
1428
+ return new RawTexture(data, width, height, format, this._scene, false, false, 2, 1);
1429
+ };
1430
+ const createTextureFromDataU8 = (data, width, height, format) => {
1431
+ return new RawTexture(data, width, height, format, this._scene, false, false, 2, 0);
1432
+ };
1433
+ const createTextureFromDataU32 = (data, width, height, format) => {
1434
+ return new RawTexture(data, width, height, format, this._scene, false, false, 1, 7);
1435
+ };
1436
+ const createTextureFromDataF16 = (data, width, height, format) => {
1437
+ return new RawTexture(data, width, height, format, this._scene, false, false, 2, 2);
1438
+ };
1439
+ const firstTime = this._covariancesATexture === null;
1440
+ const textureSizeChanged = this._textureSize.y != textureSize.y;
1441
+ if (!firstTime && !textureSizeChanged) {
1442
+ this._setDelayedTextureUpdate(covA, covB, colorArray, sh);
1443
+ const positions = Float32Array.from(this._splatPositions);
1444
+ const vertexCount = this._vertexCount;
1445
+ if (this._worker) {
1446
+ this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1447
+ }
1448
+ // Handle SH textures in update path - create if they don't exist
1449
+ if (sh && !this._shTextures) {
1450
+ this._shTextures = [];
1451
+ for (const shData of sh) {
1452
+ const buffer = new Uint32Array(shData.buffer);
1453
+ const shTexture = createTextureFromDataU32(buffer, textureSize.x, textureSize.y, 11);
1454
+ shTexture.wrapU = 0;
1455
+ shTexture.wrapV = 0;
1456
+ this._shTextures.push(shTexture);
1457
+ }
1458
+ }
1459
+ this._onUpdateTextures(textureSize);
1460
+ this._postToWorker(true);
1461
+ }
1462
+ else {
1463
+ this._textureSize = textureSize;
1464
+ this._covariancesATexture = createTextureFromDataF16(covA, textureSize.x, textureSize.y, 5);
1465
+ this._covariancesBTexture = createTextureFromDataF16(covB, textureSize.x, textureSize.y, this._useRGBACovariants ? 5 : 7);
1466
+ this._centersTexture = createTextureFromData(this._splatPositions, textureSize.x, textureSize.y, 5);
1467
+ this._colorsTexture = createTextureFromDataU8(colorArray, textureSize.x, textureSize.y, 5);
1468
+ if (sh) {
1469
+ this._shTextures = [];
1470
+ for (const shData of sh) {
1471
+ const buffer = new Uint32Array(shData.buffer);
1472
+ const shTexture = createTextureFromDataU32(buffer, textureSize.x, textureSize.y, 11);
1473
+ shTexture.wrapU = 0;
1474
+ shTexture.wrapV = 0;
1475
+ this._shTextures.push(shTexture);
1476
+ }
1477
+ }
1478
+ this._onUpdateTextures(textureSize);
1479
+ if (firstTime) {
1480
+ this._instantiateWorker();
1481
+ }
1482
+ else {
1483
+ if (this._worker) {
1484
+ const positions = Float32Array.from(this._splatPositions);
1485
+ const vertexCount = this._vertexCount;
1486
+ this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1487
+ }
1488
+ this._postToWorker(true);
1489
+ }
1490
+ }
1491
+ }
1492
+ /**
1493
+ * Checks whether the GPU textures can be incrementally updated for a new addPart operation,
1494
+ * avoiding a full texture re-upload for existing splats.
1495
+ * Requires that the GPU textures already exist and the texture height won't change.
1496
+ * @param previousVertexCount - The number of splats previously committed to GPU
1497
+ * @param vertexCount - The new total number of splats
1498
+ * @returns true when only the new splat region needs to be uploaded
1499
+ */
1500
+ _canReuseCachedData(previousVertexCount, vertexCount) {
1501
+ if (previousVertexCount <= 0 || previousVertexCount > vertexCount) {
1502
+ return false;
1503
+ }
1504
+ if (this._splatPositions === null || this._cachedBoundingMin === null || this._cachedBoundingMax === null) {
1505
+ return false;
1506
+ }
1507
+ if (this._covariancesATexture === null) {
1508
+ return false;
1509
+ }
1510
+ // Can only do an incremental GPU update if texture height doesn't need to grow
1511
+ const newTextureSize = this._getTextureSize(vertexCount);
1512
+ return newTextureSize.y === this._textureSize.y;
1513
+ }
1514
+ /**
1515
+ * Posts updated positions to the sort worker and marks the sort as dirty.
1516
+ * Called after processing new splats so the worker can re-sort with the complete position set.
1517
+ * Subclasses (e.g. compound) may override to additionally post part-index data.
1518
+ */
1519
+ _notifyWorkerNewData() {
1520
+ if (this._worker) {
1521
+ const positions = Float32Array.from(this._splatPositions);
1522
+ const vertexCount = this._vertexCount;
1523
+ this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1524
+ }
1525
+ this._sortIsDirty = true;
1526
+ }
1527
+ *_updateData(data, isAsync, sh, partIndices, { flipY = false, previousVertexCount = 0 } = {}) {
1528
+ if (!this._covariancesATexture) {
1529
+ this._readyToDisplay = false;
1530
+ }
1531
+ const uBuffer = new Uint8Array(data);
1532
+ const fBuffer = new Float32Array(uBuffer.buffer);
1533
+ // Optionally store the raw splat buffer as an ArrayBuffer. This is the source reference
1534
+ // used by _addPartsInternal when a full texture rebuild is needed. Use uBuffer.buffer as
1535
+ // the canonical backing store — it is always a tightly packed ArrayBuffer containing
1536
+ // exactly the bytes we processed, avoiding issues with ArrayBufferView.byteOffset.
1537
+ if (this._keepInRam || this._alwaysRetainSplatsData) {
1538
+ this._splatsData = uBuffer.buffer;
1539
+ this._shData = sh ? sh.map((arr) => new Uint8Array(arr)) : null;
1540
+ }
1541
+ else {
1542
+ this._splatsData = null;
1543
+ this._shData = null;
1544
+ }
1545
+ const vertexCount = uBuffer.length / GaussianSplattingMeshBase._RowOutputLength;
1546
+ if (vertexCount != this._vertexCount) {
1547
+ this._updateSplatIndexBuffer(vertexCount);
1548
+ }
1549
+ this._vertexCount = vertexCount;
1550
+ // degree == 1 for 1 texture (3 terms), 2 for 2 textures (8 terms) and 3 for 3 textures (15 terms)
1551
+ this._shDegree = sh ? sh.length : 0;
1552
+ const textureSize = this._getTextureSize(vertexCount);
1553
+ const textureLength = textureSize.x * textureSize.y;
1554
+ const lineCountUpdate = GaussianSplattingMeshBase.ProgressiveUpdateAmount ?? textureSize.y;
1555
+ // Delegate part index storage to subclasses (e.g. GaussianSplattingMesh compound mode).
1556
+ if (partIndices) {
1557
+ this._onIndexDataReceived(partIndices, textureLength);
1558
+ }
1559
+ const minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
1560
+ const maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
1561
+ const covBSItemSize = this._useRGBACovariants ? 4 : 2;
1562
+ const covA = new Uint16Array(textureLength * 4);
1563
+ const covB = new Uint16Array(covBSItemSize * textureLength);
1564
+ const colorArray = new Uint8Array(textureLength * 4);
1565
+ // Incremental path: only upload the rows that contain new splats, leaving the already-committed
1566
+ // GPU region untouched. Falls through to the full-rebuild path when textures don't exist yet
1567
+ // or the texture height needs to grow.
1568
+ const incremental = this._canReuseCachedData(previousVertexCount, vertexCount);
1569
+ // The first texture line/texel that must be (re-)processed and uploaded.
1570
+ // For a full rebuild this is 0. For an incremental update it is the row boundary just before
1571
+ // previousVertexCount so that any partial old row is re-processed as a complete row.
1572
+ const firstNewLine = incremental ? Math.floor(previousVertexCount / textureSize.x) : 0;
1573
+ const firstNewTexel = firstNewLine * textureSize.x;
1574
+ // Preserve old positions before replacing the array (incremental only)
1575
+ const oldPositions = this._splatPositions;
1576
+ this._splatPositions = new Float32Array(4 * textureLength);
1577
+ if (incremental) {
1578
+ this._splatPositions.set(oldPositions.subarray(0, previousVertexCount * 4));
1579
+ minimum.copyFrom(this._cachedBoundingMin);
1580
+ maximum.copyFrom(this._cachedBoundingMax);
1581
+ // Let subclasses handle any incremental-specific setup (e.g. ensuring part-index textures)
1582
+ this._onIncrementalUpdateStart(textureSize);
1583
+ }
1584
+ if (GaussianSplattingMeshBase.ProgressiveUpdateAmount) {
1585
+ // Full rebuild: create GPU textures upfront with empty data; the loop fills them in batches via _updateSubTextures
1586
+ if (!incremental) {
1587
+ this._updateTextures(covA, covB, colorArray, sh);
1588
+ }
1589
+ this.setEnabled(true);
1590
+ const partCount = Math.ceil(textureSize.y / lineCountUpdate);
1591
+ for (let partIndex = 0; partIndex < partCount; partIndex++) {
1592
+ const updateLine = partIndex * lineCountUpdate;
1593
+ const batchEndLine = Math.min(updateLine + lineCountUpdate, textureSize.y);
1594
+ // Skip batches that lie entirely within the already-committed GPU region
1595
+ if (batchEndLine <= firstNewLine) {
1596
+ continue;
1597
+ }
1598
+ // Clip upload start to firstNewLine to avoid overwriting committed data with zeros
1599
+ const uploadStartLine = Math.max(updateLine, firstNewLine);
1600
+ const uploadStartTexel = uploadStartLine * textureSize.x;
1601
+ const batchEndTexel = batchEndLine * textureSize.x;
1602
+ for (let splatIdx = uploadStartTexel; splatIdx < batchEndTexel; splatIdx++) {
1603
+ if (splatIdx < vertexCount) {
1604
+ this._makeSplat(splatIdx, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, flipY);
1605
+ }
1606
+ else {
1607
+ this._makeEmptySplat(splatIdx, covA, covB, colorArray);
1608
+ }
1609
+ }
1610
+ this._updateSubTextures(this._splatPositions, covA, covB, colorArray, uploadStartLine, batchEndLine - uploadStartLine, sh);
1611
+ this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
1612
+ if (isAsync) {
1613
+ yield;
1614
+ }
1615
+ }
1616
+ this._notifyWorkerNewData();
1617
+ }
1618
+ else {
1619
+ // Process splats from firstNewTexel: re-processes the partial old row (incremental) or processes everything from 0 (full rebuild)
1620
+ for (let i = firstNewTexel; i < vertexCount; i++) {
1621
+ this._makeSplat(i, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, flipY);
1622
+ if (isAsync && i % GaussianSplattingMeshBase._SplatBatchSize === 0) {
1623
+ yield;
1624
+ }
1625
+ }
1626
+ // Incremental pads the full texture; full rebuild pads only to the next 16-splat boundary
1627
+ const paddedEnd = incremental ? textureLength : (vertexCount + 15) & ~0xf;
1628
+ for (let i = vertexCount; i < paddedEnd; i++) {
1629
+ this._makeEmptySplat(i, covA, covB, colorArray);
1630
+ }
1631
+ if (incremental) {
1632
+ // Partial upload: only rows from firstNewLine onwards; existing rows stay on GPU
1633
+ this._updateSubTextures(this._splatPositions, covA, covB, colorArray, firstNewLine, textureSize.y - firstNewLine, sh);
1634
+ this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
1635
+ this.setEnabled(true);
1636
+ this._notifyWorkerNewData();
1637
+ }
1638
+ else {
1639
+ // Full upload: create or replace all GPU textures
1640
+ this._updateTextures(covA, covB, colorArray, sh);
1641
+ this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
1642
+ this.setEnabled(true);
1643
+ this._sortIsDirty = true;
1644
+ }
1645
+ }
1646
+ // Cache bounding box for the next incremental addPart call
1647
+ this._cachedBoundingMin = minimum.clone();
1648
+ this._cachedBoundingMax = maximum.clone();
1649
+ this._postToWorker(true);
1650
+ }
1651
+ /**
1652
+ * Update asynchronously the buffer
1653
+ * @param data array buffer containing center, color, orientation and scale of splats
1654
+ * @param sh optional array of uint8 array for SH data
1655
+ * @param partIndices optional array of uint8 for rig node indices
1656
+ * @returns a promise
1657
+ */
1658
+ async updateDataAsync(data, sh, partIndices) {
1659
+ return await runCoroutineAsync(this._updateData(data, true, sh, partIndices), createYieldingScheduler());
1660
+ }
1661
+ /**
1662
+ * @experimental
1663
+ * Update data from GS (position, orientation, color, scaling)
1664
+ * @param data array that contain all the datas
1665
+ * @param sh optional array of uint8 array for SH data
1666
+ * @param options optional informations on how to treat data (needs to be 3rd for backward compatibility)
1667
+ * @param partIndices optional array of uint8 for rig node indices
1668
+ */
1669
+ updateData(data, sh, options = { flipY: true }, partIndices) {
1670
+ runCoroutineSync(this._updateData(data, false, sh, partIndices, options));
1671
+ }
1672
+ /**
1673
+ * Refreshes the bounding info, taking into account all the thin instances defined
1674
+ * @returns the current Gaussian Splatting
1675
+ */
1676
+ refreshBoundingInfo() {
1677
+ this.thinInstanceRefreshBoundingInfo(false);
1678
+ return this;
1679
+ }
1680
+ // in case size is different
1681
+ _updateSplatIndexBuffer(vertexCount) {
1682
+ const paddedVertexCount = (vertexCount + 15) & ~0xf;
1683
+ if (!this._splatIndex || vertexCount != this._splatIndex.length) {
1684
+ this._splatIndex = new Float32Array(paddedVertexCount);
1685
+ for (let i = 0; i < paddedVertexCount; i++) {
1686
+ this._splatIndex[i] = i;
1687
+ }
1688
+ // update meshes for knowns cameras
1689
+ this._cameraViewInfos.forEach((cameraViewInfos) => {
1690
+ cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
1691
+ });
1692
+ }
1693
+ // Update depthMix
1694
+ if ((!this._depthMix || vertexCount != this._depthMix.length) && !IsNative) {
1695
+ this._depthMix = new BigInt64Array(paddedVertexCount);
1696
+ }
1697
+ this.forcedInstanceCount = Math.max(paddedVertexCount >> 4, 1);
1698
+ }
1699
+ _updateSubTextures(centers, covA, covB, colors, lineStart, lineCount, sh) {
1700
+ const textureSize = this._getTextureSize(this._vertexCount);
1701
+ const covBSItemSize = this._useRGBACovariants ? 4 : 2;
1702
+ const texelStart = lineStart * textureSize.x;
1703
+ const texelCount = lineCount * textureSize.x;
1704
+ const covAView = new Uint16Array(covA.buffer, texelStart * 4 * Uint16Array.BYTES_PER_ELEMENT, texelCount * 4);
1705
+ const covBView = new Uint16Array(covB.buffer, texelStart * covBSItemSize * Uint16Array.BYTES_PER_ELEMENT, texelCount * covBSItemSize);
1706
+ const colorsView = new Uint8Array(colors.buffer, texelStart * 4, texelCount * 4);
1707
+ const centersView = new Float32Array(centers.buffer, texelStart * 4 * Float32Array.BYTES_PER_ELEMENT, texelCount * 4);
1708
+ this._updateTextureFromData(this._covariancesATexture, covAView, textureSize.x, lineStart, lineCount);
1709
+ this._updateTextureFromData(this._covariancesBTexture, covBView, textureSize.x, lineStart, lineCount);
1710
+ this._updateTextureFromData(this._centersTexture, centersView, textureSize.x, lineStart, lineCount);
1711
+ this._updateTextureFromData(this._colorsTexture, colorsView, textureSize.x, lineStart, lineCount);
1712
+ if (sh) {
1713
+ for (let i = 0; i < sh.length; i++) {
1714
+ const componentCount = 4;
1715
+ const shView = new Uint32Array(sh[i].buffer, texelStart * componentCount * 4, texelCount * componentCount);
1716
+ this._updateTextureFromData(this._shTextures[i], shView, textureSize.x, lineStart, lineCount);
1717
+ }
1718
+ }
1719
+ }
1720
+ _instantiateWorker() {
1721
+ if (!this._vertexCount) {
1722
+ return;
1723
+ }
1724
+ if (this._disableDepthSort) {
1725
+ return;
1726
+ }
1727
+ this._updateSplatIndexBuffer(this._vertexCount);
1728
+ // no worker in native
1729
+ if (IsNative) {
1730
+ return;
1731
+ }
1732
+ // Start the worker thread
1733
+ this._worker?.terminate();
1734
+ // Reset the posting gate so the new worker can immediately receive sort requests.
1735
+ // If the previous worker was terminated mid-sort it would never have set _canPostToWorker
1736
+ // back to true, leaving the sort permanently frozen on the new worker.
1737
+ this._canPostToWorker = true;
1738
+ this._worker = new Worker(URL.createObjectURL(new Blob(["(", GaussianSplattingMeshBase._CreateWorker.toString(), ")(self)"], {
1739
+ type: "application/javascript",
1740
+ })));
1741
+ const positions = Float32Array.from(this._splatPositions);
1742
+ this._worker.postMessage({ positions }, [positions.buffer]);
1743
+ this._onWorkerCreated(this._worker);
1744
+ this._worker.onmessage = (e) => {
1745
+ // Recompute vertexCountPadded in case _vertexCount has changed since the last update
1746
+ const vertexCountPadded = (this._vertexCount + 15) & ~0xf;
1747
+ // If the vertex count changed, we discard this result and trigger a new sort
1748
+ if (e.data.depthMix.length != vertexCountPadded) {
1749
+ this._canPostToWorker = true;
1750
+ this._postToWorker(true);
1751
+ this._sortIsDirty = false;
1752
+ return;
1753
+ }
1754
+ this._depthMix = e.data.depthMix;
1755
+ const cameraId = e.data.cameraId;
1756
+ const indexMix = new Uint32Array(e.data.depthMix.buffer);
1757
+ if (this._splatIndex) {
1758
+ for (let j = 0; j < vertexCountPadded; j++) {
1759
+ this._splatIndex[j] = indexMix[2 * j];
1760
+ }
1761
+ }
1762
+ if (this._delayedTextureUpdate) {
1763
+ const textureSize = this._getTextureSize(vertexCountPadded);
1764
+ this._updateSubTextures(this._delayedTextureUpdate.centers, this._delayedTextureUpdate.covA, this._delayedTextureUpdate.covB, this._delayedTextureUpdate.colors, 0, textureSize.y, this._delayedTextureUpdate.sh);
1765
+ this._delayedTextureUpdate = null;
1766
+ }
1767
+ // get mesh for camera and update its instance buffer
1768
+ const cameraViewInfos = this._cameraViewInfos.get(cameraId);
1769
+ if (cameraViewInfos) {
1770
+ if (cameraViewInfos.splatIndexBufferSet) {
1771
+ cameraViewInfos.mesh.thinInstanceBufferUpdated("splatIndex");
1772
+ }
1773
+ else {
1774
+ cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
1775
+ cameraViewInfos.splatIndexBufferSet = true;
1776
+ }
1777
+ }
1778
+ this._canPostToWorker = true;
1779
+ this._readyToDisplay = true;
1780
+ // sort is dirty when GS is visible for progressive update with a this message arriving but positions were partially filled
1781
+ // another update needs to be kicked. The kick can't happen just when the position buffer is ready because _canPostToWorker might be false.
1782
+ if (this._sortIsDirty) {
1783
+ this._postToWorker(true);
1784
+ this._sortIsDirty = false;
1785
+ }
1786
+ };
1787
+ }
1788
+ _getTextureSize(length) {
1789
+ const engine = this._scene.getEngine();
1790
+ const width = engine.getCaps().maxTextureSize;
1791
+ let height = 1;
1792
+ if (engine.version === 1 && !engine.isWebGPU) {
1793
+ while (width * height < length) {
1794
+ height *= 2;
1795
+ }
1796
+ }
1797
+ else {
1798
+ height = Math.ceil(length / width);
1799
+ }
1800
+ if (height > width) {
1801
+ Logger.Error("GaussianSplatting texture size: (" + width + ", " + height + "), maxTextureSize: " + width);
1802
+ height = width;
1803
+ }
1804
+ return new Vector2(width, height);
1805
+ }
1806
+ /**
1807
+ * Called after the sort worker has been created and the initial positions message has been sent.
1808
+ * Override in subclasses to post any additional setup messages the worker needs (e.g. group
1809
+ * indices, per-part matrices, etc.).
1810
+ * @param _worker the newly created worker
1811
+ */
1812
+ _onWorkerCreated(_worker) { }
1813
+ /**
1814
+ * Called by the material to bind any extra shader uniforms that are specific to this mesh type.
1815
+ * The base implementation is a no-op; override in subclasses to bind additional data.
1816
+ * @param _effect the shader effect that is being bound
1817
+ * @internal
1818
+ */
1819
+ bindExtraEffectUniforms(_effect) { }
1820
+ /**
1821
+ * Processes all splats from a source GaussianSplattingMesh directly into the destination
1822
+ * texture arrays starting at dstOffset. This is the core of the texture-direct compound API:
1823
+ * no merged CPU buffer is ever created; each source mesh is written straight into its region.
1824
+ *
1825
+ * @param source - The source mesh whose splats are appended
1826
+ * @param dstOffset - The destination splat index at which writing starts
1827
+ * @param covA - Destination covA array (full texture size)
1828
+ * @param covB - Destination covB array (full texture size)
1829
+ * @param colorArray - Destination color array (full texture size)
1830
+ * @param sh - Destination SH arrays (full texture size), or undefined
1831
+ * @param minimum - Accumulated bounding min (updated in-place)
1832
+ * @param maximum - Accumulated bounding max (updated in-place)
1833
+ * @internal Use GaussianSplattingMesh.addPart instead
1834
+ */
1835
+ _appendSourceToArrays(source, dstOffset, covA, covB, colorArray, sh, minimum, maximum) {
1836
+ const srcCount = source._vertexCount;
1837
+ const bytesPerTexel = 16;
1838
+ const srcRaw = source._splatsData;
1839
+ if (!srcRaw || srcCount === 0) {
1840
+ return;
1841
+ }
1842
+ // _splatsData is typed as ArrayBuffer but callers may have stored a TypedArray before this
1843
+ // guard was added. Extract the underlying ArrayBuffer so Float32Array reinterprets bytes
1844
+ // correctly instead of value-converting each element.
1845
+ const srcBuffer = srcRaw instanceof ArrayBuffer ? srcRaw : srcRaw.buffer;
1846
+ const uBuffer = new Uint8Array(srcBuffer);
1847
+ const fBuffer = new Float32Array(srcBuffer);
1848
+ for (let i = 0; i < srcCount; i++) {
1849
+ this._makeSplat(dstOffset + i, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, false, i);
1850
+ }
1851
+ // Copy SH data if both source and destination have it
1852
+ if (sh && source._shData) {
1853
+ for (let texIdx = 0; texIdx < sh.length; texIdx++) {
1854
+ if (texIdx < source._shData.length) {
1855
+ sh[texIdx].set(source._shData[texIdx].subarray(0, srcCount * bytesPerTexel), dstOffset * bytesPerTexel);
1856
+ }
1857
+ }
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Modifies the splats according to the passed transformation matrix.
1862
+ * @param transform defines the transform matrix to use
1863
+ * @returns the current mesh
1864
+ */
1865
+ bakeTransformIntoVertices(transform) {
1866
+ const arrayBuffer = this.splatsData;
1867
+ if (!arrayBuffer) {
1868
+ Logger.Error("Cannot bake transform into vertices if splatsData is not kept in RAM");
1869
+ return this;
1870
+ }
1871
+ // Check for uniform scaling
1872
+ const m = transform.m;
1873
+ const scaleX = Math.sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]);
1874
+ const scaleY = Math.sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]);
1875
+ const scaleZ = Math.sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]);
1876
+ const epsilon = 0.001;
1877
+ if (Math.abs(scaleX - scaleY) > epsilon || Math.abs(scaleX - scaleZ) > epsilon) {
1878
+ Logger.Error("Gaussian Splatting bakeTransformIntoVertices does not support non-uniform scaling");
1879
+ return this;
1880
+ }
1881
+ const uBuffer = new Uint8Array(arrayBuffer);
1882
+ const fBuffer = new Float32Array(arrayBuffer);
1883
+ const temp = TmpVectors.Vector3[0];
1884
+ let index;
1885
+ const quaternion = TmpVectors.Quaternion[0];
1886
+ const transformedQuaternion = TmpVectors.Quaternion[1];
1887
+ transform.decompose(temp, transformedQuaternion, temp);
1888
+ for (index = 0; index < this._vertexCount; index++) {
1889
+ const floatIndex = index * 8; // 8 floats per splat (center.x, center.y, center.z, scale.x, scale.y, scale.z, ...)
1890
+ Vector3.TransformCoordinatesFromFloatsToRef(fBuffer[floatIndex], fBuffer[floatIndex + 1], fBuffer[floatIndex + 2], transform, temp);
1891
+ fBuffer[floatIndex] = temp.x;
1892
+ fBuffer[floatIndex + 1] = temp.y;
1893
+ fBuffer[floatIndex + 2] = temp.z;
1894
+ // Apply uniform scaling to splat scales
1895
+ fBuffer[floatIndex + 3] *= scaleX;
1896
+ fBuffer[floatIndex + 4] *= scaleX;
1897
+ fBuffer[floatIndex + 5] *= scaleX;
1898
+ // Unpack quaternion from uint8array (matching _GetSplat packing convention)
1899
+ quaternion.set((uBuffer[32 * index + 28 + 1] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 2] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 3] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 0] - 127.5) / 127.5);
1900
+ quaternion.normalize();
1901
+ // If there is a negative scaling, we need to flip the quaternion to keep the correct handedness
1902
+ if (this.scaling.x < 0) {
1903
+ quaternion.x = -quaternion.x;
1904
+ quaternion.w = -quaternion.w;
1905
+ }
1906
+ if (this.scaling.y < 0) {
1907
+ quaternion.y = -quaternion.y;
1908
+ quaternion.w = -quaternion.w;
1909
+ }
1910
+ if (this.scaling.z < 0) {
1911
+ quaternion.z = -quaternion.z;
1912
+ quaternion.w = -quaternion.w;
1913
+ }
1914
+ // Transform the quaternion
1915
+ transformedQuaternion.multiplyToRef(quaternion, quaternion);
1916
+ quaternion.normalize();
1917
+ // Pack quaternion back to uint8array (matching _GetSplat packing convention)
1918
+ uBuffer[32 * index + 28 + 0] = Math.round(quaternion.w * 127.5 + 127.5);
1919
+ uBuffer[32 * index + 28 + 1] = Math.round(quaternion.x * 127.5 + 127.5);
1920
+ uBuffer[32 * index + 28 + 2] = Math.round(quaternion.y * 127.5 + 127.5);
1921
+ uBuffer[32 * index + 28 + 3] = Math.round(quaternion.z * 127.5 + 127.5);
1922
+ }
1923
+ this.updateData(arrayBuffer, this.shData ?? undefined, { flipY: false });
1924
+ return this;
1925
+ }
1926
+ }
1927
+ GaussianSplattingMeshBase._RowOutputLength = 3 * 4 + 3 * 4 + 4 + 4; // Vector3 position, Vector3 scale, 1 u8 quaternion, 1 color with alpha
1928
+ GaussianSplattingMeshBase._SH_C0 = 0.28209479177387814;
1929
+ // batch size between 2 yield calls. This value is a tradeoff between updates overhead and framerate hiccups
1930
+ // This step is faster the PLY conversion. So batch size can be bigger
1931
+ GaussianSplattingMeshBase._SplatBatchSize = 327680;
1932
+ // batch size between 2 yield calls during the PLY to splat conversion.
1933
+ GaussianSplattingMeshBase._PlyConversionBatchSize = 32768;
1934
+ GaussianSplattingMeshBase._BatchSize = 16; // 16 splats per instance
1935
+ GaussianSplattingMeshBase._DefaultViewUpdateThreshold = 1e-4;
1936
+ /**
1937
+ * Set the number of batch (a batch is 16384 splats) after which a display update is performed
1938
+ * A value of 0 (default) means display update will not happens before splat is ready.
1939
+ */
1940
+ GaussianSplattingMeshBase.ProgressiveUpdateAmount = 0;
1941
+ GaussianSplattingMeshBase._CreateWorker = function (self) {
1942
+ let positions;
1943
+ let depthMix;
1944
+ let indices;
1945
+ let floatMix;
1946
+ let partIndices;
1947
+ let partMatrices;
1948
+ self.onmessage = (e) => {
1949
+ // updated on init
1950
+ if (e.data.positions) {
1951
+ positions = e.data.positions;
1952
+ }
1953
+ // update on rig node changed
1954
+ else if (e.data.partMatrices) {
1955
+ partMatrices = e.data.partMatrices;
1956
+ }
1957
+ // update on rig node indices changed
1958
+ else if (e.data.partIndices !== undefined) {
1959
+ partIndices = e.data.partIndices;
1960
+ }
1961
+ // update on view changed
1962
+ else {
1963
+ const cameraId = e.data.cameraId;
1964
+ const globalWorldMatrix = e.data.worldMatrix;
1965
+ const cameraForward = e.data.cameraForward;
1966
+ const cameraPosition = e.data.cameraPosition;
1967
+ const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
1968
+ if (!positions || !cameraForward) {
1969
+ // Sanity check, it shouldn't happen!
1970
+ throw new Error("positions or camera info is not defined!");
1971
+ }
1972
+ depthMix = e.data.depthMix;
1973
+ indices = new Uint32Array(depthMix.buffer);
1974
+ floatMix = new Float32Array(depthMix.buffer);
1975
+ // Sort
1976
+ for (let j = 0; j < vertexCountPadded; j++) {
1977
+ indices[2 * j] = j;
1978
+ }
1979
+ // depth = dot(cameraForward, worldPos - cameraPos)
1980
+ const camDot = cameraForward[0] * cameraPosition[0] + cameraForward[1] * cameraPosition[1] + cameraForward[2] * cameraPosition[2];
1981
+ const computeDepthCoeffs = (m) => {
1982
+ return [
1983
+ cameraForward[0] * m[0] + cameraForward[1] * m[1] + cameraForward[2] * m[2],
1984
+ cameraForward[0] * m[4] + cameraForward[1] * m[5] + cameraForward[2] * m[6],
1985
+ cameraForward[0] * m[8] + cameraForward[1] * m[9] + cameraForward[2] * m[10],
1986
+ cameraForward[0] * m[12] + cameraForward[1] * m[13] + cameraForward[2] * m[14] - camDot,
1987
+ ];
1988
+ };
1989
+ if (partMatrices && partIndices) {
1990
+ // Precompute depth coefficients for each rig node
1991
+ const depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));
1992
+ // NB: For performance reasons, we assume that part indices are valid
1993
+ const length = partIndices.length;
1994
+ for (let j = 0; j < vertexCountPadded; j++) {
1995
+ // NB: We need this 'min' because vertex array is padded, not partIndices
1996
+ const partIndex = partIndices[Math.min(j, length - 1)];
1997
+ const coeff = depthCoeffs[partIndex];
1998
+ floatMix[2 * j + 1] = coeff[0] * positions[4 * j + 0] + coeff[1] * positions[4 * j + 1] + coeff[2] * positions[4 * j + 2] + coeff[3];
1999
+ // instead of using minus to sort back to front, we use bitwise not operator to invert the order of indices
2000
+ // might not be faster but a minus sign implies a reference value that may not be enough and will decrease floatting precision
2001
+ indices[2 * j + 1] = ~indices[2 * j + 1];
2002
+ }
2003
+ }
2004
+ else {
2005
+ // Compute depth coefficients from global world matrix
2006
+ const [a, b, c, d] = computeDepthCoeffs(globalWorldMatrix);
2007
+ for (let j = 0; j < vertexCountPadded; j++) {
2008
+ floatMix[2 * j + 1] = a * positions[4 * j + 0] + b * positions[4 * j + 1] + c * positions[4 * j + 2] + d;
2009
+ indices[2 * j + 1] = ~indices[2 * j + 1];
2010
+ }
2011
+ }
2012
+ depthMix.sort();
2013
+ self.postMessage({ depthMix, cameraId }, [depthMix.buffer]);
2014
+ }
2015
+ };
2016
+ };
2017
+ //# sourceMappingURL=gaussianSplattingMeshBase.js.map