@babylonjs/core 9.2.1 → 9.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.
Files changed (81) hide show
  1. package/Animations/animation.d.ts +9 -0
  2. package/Animations/animation.js +9 -0
  3. package/Animations/animation.js.map +1 -1
  4. package/Animations/runtimeAnimation.js +28 -0
  5. package/Animations/runtimeAnimation.js.map +1 -1
  6. package/Cameras/geospatialCameraMovement.js +19 -19
  7. package/Cameras/geospatialCameraMovement.js.map +1 -1
  8. package/Debug/physicsViewer.js +2 -12
  9. package/Debug/physicsViewer.js.map +1 -1
  10. package/Engines/abstractEngine.js +2 -2
  11. package/Engines/abstractEngine.js.map +1 -1
  12. package/FlowGraph/Blocks/flowGraphBlockFactory.js +14 -1
  13. package/FlowGraph/Blocks/flowGraphBlockFactory.js.map +1 -1
  14. package/FlowGraph/flowGraph.js +6 -0
  15. package/FlowGraph/flowGraph.js.map +1 -1
  16. package/FlowGraph/flowGraphEventBlock.d.ts +10 -0
  17. package/FlowGraph/flowGraphEventBlock.js +24 -0
  18. package/FlowGraph/flowGraphEventBlock.js.map +1 -1
  19. package/FlowGraph/flowGraphParser.js +23 -4
  20. package/FlowGraph/flowGraphParser.js.map +1 -1
  21. package/FlowGraph/serialization.js +36 -14
  22. package/FlowGraph/serialization.js.map +1 -1
  23. package/Layers/thinEffectLayer.js +8 -1
  24. package/Layers/thinEffectLayer.js.map +1 -1
  25. package/Loading/Plugins/babylonFileLoader.js +26 -0
  26. package/Loading/Plugins/babylonFileLoader.js.map +1 -1
  27. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +15 -2
  28. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
  29. package/Materials/Node/Blocks/Fragment/fragmentOutputBlock.js +3 -1
  30. package/Materials/Node/Blocks/Fragment/fragmentOutputBlock.js.map +1 -1
  31. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.d.ts +18 -4
  32. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js +29 -4
  33. package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js.map +1 -1
  34. package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +48 -8
  35. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +276 -26
  36. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
  37. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.d.ts +39 -4
  38. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +113 -22
  39. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -1
  40. package/Meshes/GaussianSplatting/gaussianSplattingPartProxyMesh.d.ts +61 -7
  41. package/Meshes/GaussianSplatting/gaussianSplattingPartProxyMesh.js +94 -11
  42. package/Meshes/GaussianSplatting/gaussianSplattingPartProxyMesh.js.map +1 -1
  43. package/Meshes/mesh.d.ts +15 -0
  44. package/Meshes/mesh.js +40 -1
  45. package/Meshes/mesh.js.map +1 -1
  46. package/Meshes/transformNode.js +2 -2
  47. package/Meshes/transformNode.js.map +1 -1
  48. package/Misc/sceneSerializer.js +2 -1
  49. package/Misc/sceneSerializer.js.map +1 -1
  50. package/Misc/tools.js +1 -1
  51. package/Misc/tools.js.map +1 -1
  52. package/Particles/baseParticleSystem.d.ts +14 -0
  53. package/Particles/baseParticleSystem.js +23 -0
  54. package/Particles/baseParticleSystem.js.map +1 -1
  55. package/Particles/computeShaderParticleSystem.js +6 -0
  56. package/Particles/computeShaderParticleSystem.js.map +1 -1
  57. package/Particles/gpuParticleSystem.d.ts +37 -19
  58. package/Particles/gpuParticleSystem.js +164 -39
  59. package/Particles/gpuParticleSystem.js.map +1 -1
  60. package/Particles/thinParticleSystem.d.ts +0 -14
  61. package/Particles/thinParticleSystem.js +0 -23
  62. package/Particles/thinParticleSystem.js.map +1 -1
  63. package/Particles/webgl2ParticleSystem.d.ts +1 -0
  64. package/Particles/webgl2ParticleSystem.js +11 -2
  65. package/Particles/webgl2ParticleSystem.js.map +1 -1
  66. package/Rendering/IBLShadows/iblShadowsVoxelRenderer.js.map +1 -1
  67. package/Shaders/ShadersInclude/gaussianSplatting.js +25 -4
  68. package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
  69. package/Shaders/gaussianSplatting.vertex.js +3 -0
  70. package/Shaders/gaussianSplatting.vertex.js.map +1 -1
  71. package/Shaders/gpuRenderParticles.vertex.js +14 -2
  72. package/Shaders/gpuRenderParticles.vertex.js.map +1 -1
  73. package/Shaders/gpuUpdateParticles.vertex.js +12 -0
  74. package/Shaders/gpuUpdateParticles.vertex.js.map +1 -1
  75. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +37 -5
  76. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
  77. package/ShadersWGSL/gaussianSplatting.vertex.js +3 -0
  78. package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
  79. package/ShadersWGSL/gpuUpdateParticles.compute.js +15 -1
  80. package/ShadersWGSL/gpuUpdateParticles.compute.js.map +1 -1
  81. package/package.json +1 -1
@@ -5,6 +5,18 @@ import { type Effect } from "../../Materials/effect.js";
5
5
  import { GaussianSplattingMeshBase } from "./gaussianSplattingMeshBase.js";
6
6
  import "../thinInstanceMesh.js";
7
7
  import { GaussianSplattingPartProxyMesh } from "./gaussianSplattingPartProxyMesh.js";
8
+ import { type BoundingInfo } from "../../Culling/boundingInfo.js";
9
+ interface IGaussianSplattingPartSource {
10
+ name: string;
11
+ _vertexCount: number;
12
+ _splatsData: Nullable<ArrayBuffer>;
13
+ _shData: Nullable<Uint8Array[]>;
14
+ _shDegree: number;
15
+ isCompound: boolean;
16
+ getWorldMatrix(): Matrix;
17
+ getBoundingInfo(): BoundingInfo;
18
+ dispose(): void;
19
+ }
8
20
  /**
9
21
  * Class used to render a Gaussian Splatting mesh. Supports both single-cloud and compound
10
22
  * (multi-part) rendering. In compound mode, multiple Gaussian Splatting source meshes are
@@ -140,22 +152,25 @@ export declare class GaussianSplattingMesh extends GaussianSplattingMeshBase {
140
152
  * @param partIndices - Part index data; if undefined the method is a no-op
141
153
  */
142
154
  protected _ensurePartIndicesTexture(textureSize: Vector2, partIndices: Uint8Array | undefined): void;
155
+ private _appendPartSourceToArrays;
156
+ private _createRetainedPartSource;
157
+ private _retainMergedPartData;
143
158
  /**
144
- * Core implementation for adding one or more external GaussianSplattingMesh objects as new
145
- * parts. Writes directly into texture-sized CPU arrays and uploads in one pass — no merged
146
- * CPU splat buffer is ever constructed.
159
+ * Core implementation for adding one or more source parts as new
160
+ * parts. Writes directly into texture-sized CPU arrays, updates the retained merged source
161
+ * buffers, and uploads in one pass.
147
162
  *
148
163
  * @param others - Source meshes to append (must each be non-compound and fully loaded)
149
164
  * @param disposeOthers - Dispose source meshes after appending
150
165
  * @returns Proxy meshes and their assigned part indices
151
166
  */
152
- protected _addPartsInternal(others: GaussianSplattingMesh[], disposeOthers: boolean): {
167
+ protected _addPartsInternal(others: IGaussianSplattingPartSource[], disposeOthers: boolean): {
153
168
  proxyMeshes: GaussianSplattingPartProxyMesh[];
154
169
  assignedPartIndices: number[];
155
170
  };
156
171
  /**
157
172
  * Add another mesh to this mesh, as a new part. This makes the current mesh a compound, if not already.
158
- * The source mesh's splat data is read directly no merged CPU buffer is constructed.
173
+ * The source mesh's splat data is read directly and copied into the compound's retained source buffers.
159
174
  * @param other - The other mesh to add. Must be fully loaded before calling this method.
160
175
  * @param disposeOther - Whether to dispose the other mesh after adding it to the current mesh.
161
176
  * @returns a placeholder mesh that can be used to manipulate the part transform
@@ -164,11 +179,36 @@ export declare class GaussianSplattingMesh extends GaussianSplattingMeshBase {
164
179
  addPart(other: GaussianSplattingMesh, disposeOther?: boolean): GaussianSplattingPartProxyMesh;
165
180
  /**
166
181
  * Remove a part from this compound mesh.
167
- * The remaining parts are rebuilt directly from their stored source mesh references
168
- * no merged CPU splat buffer is read back. The current mesh is reset to a plain (single-part)
169
- * state and then each remaining source is re-added via addParts.
182
+ * The remaining parts are rebuilt directly from the compound mesh's retained source buffers.
183
+ * The current mesh is reset to a plain (single-part) state and then each remaining source is
184
+ * re-added via addParts.
170
185
  * @param index - The index of the part to remove
171
186
  * @deprecated Use {@link GaussianSplattingCompoundMesh.removePart} instead.
172
187
  */
173
188
  removePart(index: number): void;
189
+ /**
190
+ * Serialize current GaussianSplattingMesh
191
+ * @param serializationObject defines the object which will receive the serialization data
192
+ * @param encoding the encoding of binary data, defaults to base64 for json serialize,
193
+ * kept for future internal use like cloning where base64 encoding wastes cycles and memory
194
+ * @returns the serialized object
195
+ */
196
+ serialize(serializationObject?: any, encoding?: string): any;
197
+ /**
198
+ * Internal helper to parses a serialized GaussianSplattingMesh or GaussianSplattingCompoundMesh
199
+ * @param parsedMesh the serialized mesh
200
+ * @param scene the scene to create the GaussianSplattingMesh or GaussianSplattingCompoundMesh in
201
+ * @param ctor the constructor of the mesh to create
202
+ * @returns the created GaussianSplattingMesh
203
+ * @internal
204
+ */
205
+ static _ParseInternal<T extends GaussianSplattingMesh>(parsedMesh: any, scene: Scene, ctor: new (name: string, url: Nullable<string>, scene: Nullable<Scene>, keepInRam: boolean) => T): T;
206
+ /**
207
+ * Parses a serialized GaussianSplattingMesh
208
+ * @param parsedMesh the serialized mesh
209
+ * @param scene the scene to create the GaussianSplattingMesh in
210
+ * @returns the created GaussianSplattingMesh
211
+ */
212
+ static Parse(parsedMesh: any, scene: Scene): GaussianSplattingMesh;
174
213
  }
214
+ export {};
@@ -3,8 +3,56 @@ import { GetGaussianSplattingMaxPartCount } from "../../Materials/GaussianSplatt
3
3
  import { GaussianSplattingMeshBase } from "./gaussianSplattingMeshBase.js";
4
4
  import { RawTexture } from "../../Materials/Textures/rawTexture.js";
5
5
 
6
+ import { DecodeBase64ToBinary, EncodeArrayBufferToBase64 } from "../../Misc/stringTools.js";
7
+ import { Mesh } from "../mesh.js";
6
8
  import "../thinInstanceMesh.js";
7
9
  import { GaussianSplattingPartProxyMesh } from "./gaussianSplattingPartProxyMesh.js";
10
+ const _GaussianSplattingBytesPerSplat = 32;
11
+ const _GaussianSplattingBytesPerShTexel = 16;
12
+ /**
13
+ * Run-Length Encoding (RLE) compression for serialization
14
+ * Compressed Uint32Array can be parsed using {@link ParsePartIndices}
15
+ * Some notes for devs: We do not expect Uint8Array larger than 4GB,
16
+ * so it should be safe to use Uint32Array.
17
+ * @param partIndices A view of partIndices from GaussianSplattingMesh
18
+ * @returns A compressed Uint32Array of [count, value, ...]
19
+ */
20
+ function CompressPartIndices(partIndices) {
21
+ const runs = [];
22
+ const length = partIndices.length;
23
+ let i = 0;
24
+ while (i < length) {
25
+ const value = partIndices[i];
26
+ let count = 1;
27
+ while (i + count < length && partIndices[i + count] === value) {
28
+ count++;
29
+ }
30
+ runs.push(count, value);
31
+ i += count;
32
+ }
33
+ return new Uint32Array(runs);
34
+ }
35
+ /**
36
+ * Parse partIndices compressed by {@link CompressPartIndices} to runtime array
37
+ * @param compressed The compressed partIndices of [count, value, ...]
38
+ * @returns runtime Uint8Array for GaussianSplattingMesh
39
+ */
40
+ function ParsePartIndices(compressed) {
41
+ let totalCount = 0;
42
+ const length = compressed.length;
43
+ for (let i = 0; i < length; i += 2) {
44
+ totalCount += compressed[i];
45
+ }
46
+ const partIndices = new Uint8Array(totalCount);
47
+ let offset = 0;
48
+ for (let i = 0; i < length; i += 2) {
49
+ const count = compressed[i];
50
+ const value = compressed[i + 1];
51
+ partIndices.fill(value, offset, offset + count);
52
+ offset += count;
53
+ }
54
+ return partIndices;
55
+ }
8
56
  /**
9
57
  * Class used to render a Gaussian Splatting mesh. Supports both single-cloud and compound
10
58
  * (multi-part) rendering. In compound mode, multiple Gaussian Splatting source meshes are
@@ -290,10 +338,88 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
290
338
  this._worker.postMessage({ partIndices: partIndices ?? null });
291
339
  }
292
340
  }
341
+ _appendPartSourceToArrays(source, dstOffset, covA, covB, colorArray, sh, minimum, maximum) {
342
+ this._appendSourceToArrays(source, dstOffset, covA, covB, colorArray, sh, minimum, maximum);
343
+ }
344
+ _createRetainedPartSource(proxy) {
345
+ if (!this._splatsData || (this._shDegree > 0 && !this._shData)) {
346
+ return null;
347
+ }
348
+ const splatByteOffset = proxy._splatsDataOffset * _GaussianSplattingBytesPerSplat;
349
+ const splatByteLength = proxy._vertexCount * _GaussianSplattingBytesPerSplat;
350
+ const shByteOffset = proxy._shDataOffset * _GaussianSplattingBytesPerShTexel;
351
+ const shByteLength = proxy._vertexCount * _GaussianSplattingBytesPerShTexel;
352
+ return {
353
+ name: proxy.name,
354
+ _vertexCount: proxy._vertexCount,
355
+ _splatsData: this._splatsData.slice(splatByteOffset, splatByteOffset + splatByteLength),
356
+ _shData: this._shData?.map((texture) => texture.slice(shByteOffset, shByteOffset + shByteLength)) ?? null,
357
+ _shDegree: this._shData?.length ?? 0,
358
+ isCompound: false,
359
+ getWorldMatrix: () => proxy.getWorldMatrix(),
360
+ getBoundingInfo: () => proxy.getBoundingInfo(),
361
+ dispose: () => { },
362
+ };
363
+ }
364
+ _retainMergedPartData(existingVertexCount, totalCount, others, shDegree) {
365
+ if (!this._keepInRam && !this._alwaysRetainSplatsData) {
366
+ this._splatsData = null;
367
+ this._shData = null;
368
+ return;
369
+ }
370
+ const getSourceBuffer = (data) => {
371
+ return data instanceof ArrayBuffer ? data : data.buffer;
372
+ };
373
+ const mergedSplatsData = new Uint8Array(totalCount * _GaussianSplattingBytesPerSplat);
374
+ let splatByteOffset = 0;
375
+ if (this._splatsData && existingVertexCount > 0) {
376
+ mergedSplatsData.set(new Uint8Array(getSourceBuffer(this._splatsData), 0, existingVertexCount * _GaussianSplattingBytesPerSplat), splatByteOffset);
377
+ splatByteOffset += existingVertexCount * _GaussianSplattingBytesPerSplat;
378
+ }
379
+ for (const other of others) {
380
+ if (!other._splatsData) {
381
+ continue;
382
+ }
383
+ const splatByteLength = other._vertexCount * _GaussianSplattingBytesPerSplat;
384
+ mergedSplatsData.set(new Uint8Array(getSourceBuffer(other._splatsData), 0, splatByteLength), splatByteOffset);
385
+ splatByteOffset += splatByteLength;
386
+ }
387
+ this._splatsData = mergedSplatsData.buffer;
388
+ if (shDegree <= 0) {
389
+ this._shData = null;
390
+ return;
391
+ }
392
+ const mergedShData = [];
393
+ for (let textureIndex = 0; textureIndex < shDegree; textureIndex++) {
394
+ mergedShData.push(new Uint8Array(totalCount * _GaussianSplattingBytesPerShTexel));
395
+ }
396
+ let shByteOffset = 0;
397
+ if (this._shData && existingVertexCount > 0) {
398
+ const existingShByteLength = existingVertexCount * _GaussianSplattingBytesPerShTexel;
399
+ for (let textureIndex = 0; textureIndex < mergedShData.length; textureIndex++) {
400
+ if (textureIndex < this._shData.length) {
401
+ mergedShData[textureIndex].set(this._shData[textureIndex].subarray(0, existingShByteLength), shByteOffset);
402
+ }
403
+ }
404
+ shByteOffset += existingShByteLength;
405
+ }
406
+ for (const other of others) {
407
+ const otherShByteLength = other._vertexCount * _GaussianSplattingBytesPerShTexel;
408
+ if (other._shData) {
409
+ for (let textureIndex = 0; textureIndex < mergedShData.length; textureIndex++) {
410
+ if (textureIndex < other._shData.length) {
411
+ mergedShData[textureIndex].set(other._shData[textureIndex].subarray(0, otherShByteLength), shByteOffset);
412
+ }
413
+ }
414
+ }
415
+ shByteOffset += otherShByteLength;
416
+ }
417
+ this._shData = mergedShData;
418
+ }
293
419
  /**
294
- * Core implementation for adding one or more external GaussianSplattingMesh objects as new
295
- * parts. Writes directly into texture-sized CPU arrays and uploads in one pass — no merged
296
- * CPU splat buffer is ever constructed.
420
+ * Core implementation for adding one or more source parts as new
421
+ * parts. Writes directly into texture-sized CPU arrays, updates the retained merged source
422
+ * buffers, and uploads in one pass.
297
423
  *
298
424
  * @param others - Source meshes to append (must each be non-compound and fully loaded)
299
425
  * @param disposeOthers - Dispose source meshes after appending
@@ -355,6 +481,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
355
481
  this._partIndices = new Uint8Array(textureLength);
356
482
  this._partIndices.set(partIndicesA.subarray(0, splatCountA));
357
483
  const assignedPartIndices = [];
484
+ const assignedSplatsDataOffsets = [];
358
485
  let dstOffset = splatCountA;
359
486
  const maxPartCount = GetGaussianSplattingMaxPartCount(this._scene.getEngine());
360
487
  for (const other of others) {
@@ -363,6 +490,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
363
490
  }
364
491
  const newPartIndex = nextPartIndex++;
365
492
  assignedPartIndices.push(newPartIndex);
493
+ assignedSplatsDataOffsets.push(dstOffset);
366
494
  this._partIndices.fill(newPartIndex, dstOffset, dstOffset + other._vertexCount);
367
495
  dstOffset += other._vertexCount;
368
496
  }
@@ -394,7 +522,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
394
522
  // Rebuild the compound's legacy "own" data at part 0 (scenario A only).
395
523
  // Skipped in the preferred empty-composer path (scenario B).
396
524
  if (!this._partProxies[0] && this._splatsData) {
397
- const proxyVertexCount = this._partProxies.reduce((sum, proxy) => sum + (proxy ? proxy.proxiedMesh._vertexCount : 0), 0);
525
+ const proxyVertexCount = this._partProxies.reduce((sum, proxy) => sum + (proxy ? proxy._vertexCount : 0), 0);
398
526
  const part0Count = splatCountA - proxyVertexCount;
399
527
  if (part0Count > 0) {
400
528
  const uBufA = new Uint8Array(this._splatsData);
@@ -417,11 +545,15 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
417
545
  // scenario B, part 0 is itself a proxied part with no implicit "own" data.
418
546
  for (let partIndex = 0; partIndex < this._partProxies.length; partIndex++) {
419
547
  const proxy = this._partProxies[partIndex];
420
- if (!proxy || !proxy.proxiedMesh) {
548
+ if (!proxy) {
421
549
  continue;
422
550
  }
423
- this._appendSourceToArrays(proxy.proxiedMesh, rebuildOffset, covA, covB, colorArray, sh, minimum, maximum);
424
- rebuildOffset += proxy.proxiedMesh._vertexCount;
551
+ const source = this._createRetainedPartSource(proxy);
552
+ if (!source) {
553
+ throw new Error(`Cannot rebuild compound part "${proxy.name}": the retained compound source data is not available.`);
554
+ }
555
+ this._appendPartSourceToArrays(source, rebuildOffset, covA, covB, colorArray, sh, minimum, maximum);
556
+ rebuildOffset += source._vertexCount;
425
557
  }
426
558
  }
427
559
  else {
@@ -474,7 +606,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
474
606
  // Handles both layouts (see full-rebuild comment above):
475
607
  // A) LEGACY: _partProxies[0] absent → seed lookup[0] with this._splatsData
476
608
  // B) PREFERRED: _partProxies[0] present → all entries filled from proxies
477
- const proxyTotal = this._partProxies.reduce((s, p) => s + (p ? p.proxiedMesh._vertexCount : 0), 0);
609
+ const proxyTotal = this._partProxies.reduce((s, p) => s + (p ? p._vertexCount : 0), 0);
478
610
  const part0Count = splatCountA - proxyTotal; // > 0 only in legacy scenario A
479
611
  const srcUBufs = new Array(this._partProxies.length).fill(null);
480
612
  const srcFBufs = new Array(this._partProxies.length).fill(null);
@@ -489,14 +621,17 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
489
621
  let cumOffset = part0Count;
490
622
  for (let pi = 0; pi < this._partProxies.length; pi++) {
491
623
  const proxy = this._partProxies[pi];
492
- if (!proxy?.proxiedMesh) {
624
+ if (!proxy) {
493
625
  continue;
494
626
  }
495
- const srcData = proxy.proxiedMesh._splatsData ?? null;
496
- srcUBufs[pi] = srcData ? new Uint8Array(srcData) : null;
497
- srcFBufs[pi] = srcData ? new Float32Array(srcData) : null;
627
+ const source = this._createRetainedPartSource(proxy);
628
+ if (!source || !source._splatsData) {
629
+ throw new Error(`Cannot rebuild compound part "${proxy.name}": the retained compound source data is not available.`);
630
+ }
631
+ srcUBufs[pi] = new Uint8Array(source._splatsData);
632
+ srcFBufs[pi] = new Float32Array(source._splatsData);
498
633
  partStarts[pi] = cumOffset;
499
- cumOffset += proxy.proxiedMesh._vertexCount;
634
+ cumOffset += source._vertexCount;
500
635
  }
501
636
  for (let splatIdx = firstNewTexel; splatIdx < splatCountA; splatIdx++) {
502
637
  const partIdx = this._partIndices ? this._partIndices[splatIdx] : 0;
@@ -512,7 +647,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
512
647
  // Append each new source
513
648
  dstOffset = splatCountA;
514
649
  for (const other of others) {
515
- this._appendSourceToArrays(other, dstOffset, covA, covB, colorArray, sh, minimum, maximum);
650
+ this._appendPartSourceToArrays(other, dstOffset, covA, covB, colorArray, sh, minimum, maximum);
516
651
  dstOffset += other._vertexCount;
517
652
  }
518
653
  // Pad empty splats to texture boundary
@@ -524,6 +659,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
524
659
  if (totalCount !== this._vertexCount) {
525
660
  this._updateSplatIndexBuffer(totalCount);
526
661
  }
662
+ this._retainMergedPartData(splatCountA, totalCount, others, shDegreeNew);
527
663
  this._vertexCount = totalCount;
528
664
  this._shDegree = shDegreeNew;
529
665
  // Gate the sort worker for the duration of this operation. _updateTextures (below) may create the worker and fire an
@@ -561,7 +697,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
561
697
  const newPartIndex = assignedPartIndices[i];
562
698
  const partWorldMatrix = other.getWorldMatrix();
563
699
  this.setWorldMatrixForPart(newPartIndex, partWorldMatrix);
564
- const proxyMesh = new GaussianSplattingPartProxyMesh(other.name, this.getScene(), this, other, newPartIndex);
700
+ const proxyMesh = new GaussianSplattingPartProxyMesh(other.name, this.getScene(), this, newPartIndex, other.getBoundingInfo(), other._vertexCount, assignedSplatsDataOffsets[i], assignedSplatsDataOffsets[i]);
565
701
  if (disposeOthers) {
566
702
  other.dispose();
567
703
  }
@@ -598,7 +734,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
598
734
  // ---------------------------------------------------------------------------
599
735
  /**
600
736
  * Add another mesh to this mesh, as a new part. This makes the current mesh a compound, if not already.
601
- * The source mesh's splat data is read directly no merged CPU buffer is constructed.
737
+ * The source mesh's splat data is read directly and copied into the compound's retained source buffers.
602
738
  * @param other - The other mesh to add. Must be fully loaded before calling this method.
603
739
  * @param disposeOther - Whether to dispose the other mesh after adding it to the current mesh.
604
740
  * @returns a placeholder mesh that can be used to manipulate the part transform
@@ -610,9 +746,9 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
610
746
  }
611
747
  /**
612
748
  * Remove a part from this compound mesh.
613
- * The remaining parts are rebuilt directly from their stored source mesh references
614
- * no merged CPU splat buffer is read back. The current mesh is reset to a plain (single-part)
615
- * state and then each remaining source is re-added via addParts.
749
+ * The remaining parts are rebuilt directly from the compound mesh's retained source buffers.
750
+ * The current mesh is reset to a plain (single-part) state and then each remaining source is
751
+ * re-added via addParts.
616
752
  * @param index - The index of the part to remove
617
753
  * @deprecated Use {@link GaussianSplattingCompoundMesh.removePart} instead.
618
754
  */
@@ -624,15 +760,23 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
624
760
  const survivors = [];
625
761
  for (let proxyIndex = 0; proxyIndex < this._partProxies.length; proxyIndex++) {
626
762
  const proxy = this._partProxies[proxyIndex];
627
- if (proxy && proxyIndex !== index) {
628
- survivors.push({ proxyMesh: proxy, oldIndex: proxyIndex, worldMatrix: proxy.getWorldMatrix().clone(), visibility: this._partVisibility[proxyIndex] ?? 1.0 });
763
+ if (!proxy || proxyIndex === index) {
764
+ continue;
765
+ }
766
+ const source = this._createRetainedPartSource(proxy);
767
+ if (!source) {
768
+ throw new Error(`Cannot remove part: the retained compound source data is not available for part "${proxy.name}".`);
629
769
  }
770
+ survivors.push({ proxyMesh: proxy, source, oldIndex: proxyIndex, worldMatrix: proxy.getWorldMatrix().clone(), visibility: this._partVisibility[proxyIndex] ?? 1.0 });
630
771
  }
631
772
  survivors.sort((a, b) => a.oldIndex - b.oldIndex);
632
773
  // Validate every survivor still has its source data. If even one is missing we cannot rebuild.
633
- for (const { proxyMesh } of survivors) {
634
- if (!proxyMesh.proxiedMesh._splatsData) {
635
- throw new Error(`Cannot remove part: the source mesh for part "${proxyMesh.name}" no longer has its splat data available.`);
774
+ for (const { proxyMesh, source } of survivors) {
775
+ if (!source._splatsData) {
776
+ throw new Error(`Cannot remove part: the source data for part "${proxyMesh.name}" is not available.`);
777
+ }
778
+ if (source._shDegree > 0 && !source._shData) {
779
+ throw new Error(`Cannot remove part: the SH data for part "${proxyMesh.name}" is not available.`);
636
780
  }
637
781
  }
638
782
  // --- Reset this mesh to an empty state ---
@@ -672,6 +816,9 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
672
816
  this._partVisibility = [];
673
817
  this._cachedBoundingMin = null;
674
818
  this._cachedBoundingMax = null;
819
+ this._splatsData = null;
820
+ this._shData = null;
821
+ this._shDegree = 0;
675
822
  // Remove the proxy for the removed part and dispose it
676
823
  const proxyToRemove = this._partProxies[index];
677
824
  if (proxyToRemove) {
@@ -689,7 +836,7 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
689
836
  this._rebuilding = true;
690
837
  this._canPostToWorker = false;
691
838
  try {
692
- const sources = survivors.map((s) => s.proxyMesh.proxiedMesh);
839
+ const sources = survivors.map((s) => s.source);
693
840
  const { proxyMeshes: newProxies } = this._addPartsInternal(sources, false);
694
841
  // Restore world matrices and re-map proxies
695
842
  for (let i = 0; i < survivors.length; i++) {
@@ -703,8 +850,9 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
703
850
  survivors[i].worldMatrix.decompose(newProxy.scaling, quaternion, newProxy.position);
704
851
  newProxy.rotationQuaternion = quaternion;
705
852
  newProxy.computeWorldMatrix(true);
706
- // Update the old proxy's index so any existing user references still work
853
+ // Update the old proxy's index and metadata so existing user references still work.
707
854
  oldProxy.updatePartIndex(newPartIndex);
855
+ oldProxy.updatePartMetadata(newProxy._vertexCount, newProxy._splatsDataOffset, newProxy._shDataOffset);
708
856
  this._partProxies[newPartIndex] = oldProxy;
709
857
  // newProxy is redundant — it was created inside _addPartsInternal; dispose it
710
858
  newProxy.dispose();
@@ -725,5 +873,107 @@ export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
725
873
  throw e;
726
874
  }
727
875
  }
876
+ /**
877
+ * Serialize current GaussianSplattingMesh
878
+ * @param serializationObject defines the object which will receive the serialization data
879
+ * @param encoding the encoding of binary data, defaults to base64 for json serialize,
880
+ * kept for future internal use like cloning where base64 encoding wastes cycles and memory
881
+ * @returns the serialized object
882
+ */
883
+ serialize(serializationObject = {}, encoding = "base64") {
884
+ serializationObject = super.serialize(serializationObject);
885
+ serializationObject.subMeshes = [];
886
+ serializationObject.geometryUniqueId = undefined;
887
+ serializationObject.geometryId = undefined;
888
+ serializationObject.materialUniqueId = undefined;
889
+ serializationObject.materialId = undefined;
890
+ serializationObject.instances = [];
891
+ serializationObject.actions = undefined;
892
+ serializationObject.type = this.getClassName();
893
+ serializationObject.keepInRam = this._keepInRam;
894
+ serializationObject.disableDepthSort = this._disableDepthSort;
895
+ serializationObject.viewUpdateThreshold = this.viewUpdateThreshold;
896
+ serializationObject._flipY = this._flipY;
897
+ if (this._splatsData) {
898
+ serializationObject.splatsData = encoding === "base64" ? EncodeArrayBufferToBase64(this._splatsData) : this._splatsData;
899
+ }
900
+ if (this._shData) {
901
+ serializationObject.shData = encoding === "base64" ? this._shData.map(EncodeArrayBufferToBase64) : this._shData;
902
+ }
903
+ if (this._partIndices) {
904
+ const compressedIndices = CompressPartIndices(this._partIndices.subarray(0, this._vertexCount));
905
+ serializationObject.partIndices = encoding === "base64" ? EncodeArrayBufferToBase64(compressedIndices) : compressedIndices;
906
+ }
907
+ if (this._partProxies.length) {
908
+ serializationObject.partProxies = this._partProxies.filter((proxy) => !!proxy).map((proxy) => proxy.serialize());
909
+ }
910
+ return serializationObject;
911
+ }
912
+ /**
913
+ * Internal helper to parses a serialized GaussianSplattingMesh or GaussianSplattingCompoundMesh
914
+ * @param parsedMesh the serialized mesh
915
+ * @param scene the scene to create the GaussianSplattingMesh or GaussianSplattingCompoundMesh in
916
+ * @param ctor the constructor of the mesh to create
917
+ * @returns the created GaussianSplattingMesh
918
+ * @internal
919
+ */
920
+ static _ParseInternal(parsedMesh, scene, ctor) {
921
+ const mesh = new ctor(parsedMesh.name, null, scene, parsedMesh.keepInRam);
922
+ mesh.disableDepthSort = parsedMesh.disableDepthSort ?? false;
923
+ mesh.viewUpdateThreshold = parsedMesh.viewUpdateThreshold ?? GaussianSplattingMeshBase._DefaultViewUpdateThreshold;
924
+ let splatsData = parsedMesh.splatsData;
925
+ if (typeof splatsData === "string") {
926
+ splatsData = DecodeBase64ToBinary(splatsData);
927
+ }
928
+ const shData = parsedMesh.shData;
929
+ let parsedShData;
930
+ if (Array.isArray(shData) && shData.length) {
931
+ const newData = [];
932
+ for (let i = 0, length = shData.length; i < length; i++) {
933
+ const data = shData[i];
934
+ if (typeof data === "string") {
935
+ newData[i] = new Uint8Array(DecodeBase64ToBinary(data));
936
+ }
937
+ else {
938
+ newData[i] = data;
939
+ }
940
+ }
941
+ parsedShData = newData;
942
+ }
943
+ let partIndices = parsedMesh.partIndices;
944
+ let parsedPartIndices;
945
+ if (typeof partIndices === "string") {
946
+ partIndices = new Uint32Array(DecodeBase64ToBinary(partIndices));
947
+ }
948
+ if (partIndices) {
949
+ parsedPartIndices = ParsePartIndices(partIndices);
950
+ }
951
+ if (splatsData) {
952
+ const flipY = parsedMesh._flipY ?? false;
953
+ mesh.updateData(splatsData, parsedShData, { flipY }, parsedPartIndices);
954
+ }
955
+ if (parsedMesh.partProxies) {
956
+ for (const serializedPart of parsedMesh.partProxies) {
957
+ const part = Object.assign({}, serializedPart);
958
+ part.compoundSplatMesh = mesh;
959
+ const proxyMesh = Mesh.Parse(part, scene, "");
960
+ const newPartIndex = proxyMesh.partIndex;
961
+ mesh._partProxies[newPartIndex] = proxyMesh;
962
+ mesh.setWorldMatrixForPart(newPartIndex, proxyMesh.getWorldMatrix());
963
+ mesh.setPartVisibility(newPartIndex, proxyMesh.visibility);
964
+ }
965
+ }
966
+ return mesh;
967
+ }
968
+ /**
969
+ * Parses a serialized GaussianSplattingMesh
970
+ * @param parsedMesh the serialized mesh
971
+ * @param scene the scene to create the GaussianSplattingMesh in
972
+ * @returns the created GaussianSplattingMesh
973
+ */
974
+ static Parse(parsedMesh, scene) {
975
+ return GaussianSplattingMesh._ParseInternal(parsedMesh, scene, GaussianSplattingMesh);
976
+ }
728
977
  }
978
+ Mesh._GaussianSplattingMeshParser = GaussianSplattingMesh.Parse;
729
979
  //# sourceMappingURL=gaussianSplattingMesh.js.map