@onerjs/core 8.36.8 → 8.37.1

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 (99) hide show
  1. package/Audio/analyser.js.map +1 -1
  2. package/AudioV2/webAudio/subNodes/webAudioAnalyzerSubNode.js.map +1 -1
  3. package/Buffers/buffer.align.js +3 -3
  4. package/Buffers/buffer.align.js.map +1 -1
  5. package/Buffers/bufferUtils.d.ts +7 -0
  6. package/Buffers/bufferUtils.js +31 -13
  7. package/Buffers/bufferUtils.js.map +1 -1
  8. package/Engines/Extensions/engine.dynamicBuffer.js +3 -3
  9. package/Engines/Extensions/engine.dynamicBuffer.js.map +1 -1
  10. package/Engines/Native/nativeDataStream.d.ts +1 -1
  11. package/Engines/Native/nativeDataStream.js.map +1 -1
  12. package/Engines/Native/nativeInterfaces.d.ts +4 -4
  13. package/Engines/Native/nativeInterfaces.js.map +1 -1
  14. package/Engines/WebGPU/Extensions/engine.rawTexture.d.ts +13 -1
  15. package/Engines/WebGPU/Extensions/engine.rawTexture.js +26 -8
  16. package/Engines/WebGPU/Extensions/engine.rawTexture.js.map +1 -1
  17. package/Engines/WebGPU/webgpuTextureManager.d.ts +2 -1
  18. package/Engines/WebGPU/webgpuTextureManager.js +19 -6
  19. package/Engines/WebGPU/webgpuTextureManager.js.map +1 -1
  20. package/Engines/abstractEngine.d.ts +4 -2
  21. package/Engines/abstractEngine.functions.d.ts +1 -1
  22. package/Engines/abstractEngine.functions.js +8 -8
  23. package/Engines/abstractEngine.functions.js.map +1 -1
  24. package/Engines/abstractEngine.js +6 -4
  25. package/Engines/abstractEngine.js.map +1 -1
  26. package/Engines/engine.d.ts +12 -0
  27. package/Engines/thinNativeEngine.js +1 -1
  28. package/Engines/thinNativeEngine.js.map +1 -1
  29. package/Engines/thinWebGPUEngine.js +3 -0
  30. package/Engines/thinWebGPUEngine.js.map +1 -1
  31. package/Engines/webgpuEngine.js +18 -18
  32. package/Engines/webgpuEngine.js.map +1 -1
  33. package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
  34. package/Materials/Background/backgroundMaterial.d.ts +16 -8
  35. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +14 -1
  36. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
  37. package/Materials/Node/Blocks/Input/inputBlock.js +14 -0
  38. package/Materials/Node/Blocks/Input/inputBlock.js.map +1 -1
  39. package/Materials/Node/Blocks/debugBlock.d.ts +2 -0
  40. package/Materials/Node/Blocks/debugBlock.js +6 -2
  41. package/Materials/Node/Blocks/debugBlock.js.map +1 -1
  42. package/Materials/Node/Enums/nodeMaterialSystemValues.d.ts +3 -1
  43. package/Materials/Node/Enums/nodeMaterialSystemValues.js +2 -0
  44. package/Materials/Node/Enums/nodeMaterialSystemValues.js.map +1 -1
  45. package/Materials/Node/nodeMaterial.d.ts +16 -8
  46. package/Materials/Node/nodeMaterialBlock.js +2 -2
  47. package/Materials/Node/nodeMaterialBlock.js.map +1 -1
  48. package/Materials/PBR/openpbrMaterial.d.ts +16 -8
  49. package/Materials/PBR/pbrBaseMaterial.d.ts +16 -8
  50. package/Materials/Textures/Loaders/EXR/exrLoader.compression.rle.d.ts +1 -1
  51. package/Materials/Textures/Loaders/EXR/exrLoader.compression.rle.js.map +1 -1
  52. package/Materials/Textures/Loaders/EXR/exrLoader.core.d.ts +1 -1
  53. package/Materials/Textures/Loaders/EXR/exrLoader.core.js.map +1 -1
  54. package/Materials/Textures/internalTexture.d.ts +6 -0
  55. package/Materials/Textures/internalTexture.js +24 -2
  56. package/Materials/Textures/internalTexture.js.map +1 -1
  57. package/Materials/Textures/rawTexture.d.ts +8 -1
  58. package/Materials/Textures/rawTexture.js +12 -3
  59. package/Materials/Textures/rawTexture.js.map +1 -1
  60. package/Materials/Textures/rawTexture2DArray.d.ts +8 -1
  61. package/Materials/Textures/rawTexture2DArray.js +14 -3
  62. package/Materials/Textures/rawTexture2DArray.js.map +1 -1
  63. package/Materials/imageProcessing.d.ts +47 -8
  64. package/Materials/standardMaterial.d.ts +16 -8
  65. package/Meshes/Builders/shapeBuilder.d.ts +4 -0
  66. package/Meshes/Builders/shapeBuilder.js +12 -9
  67. package/Meshes/Builders/shapeBuilder.js.map +1 -1
  68. package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +68 -4
  69. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +349 -30
  70. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
  71. package/Meshes/geometry.js +15 -2
  72. package/Meshes/geometry.js.map +1 -1
  73. package/Meshes/mesh.js +23 -21
  74. package/Meshes/mesh.js.map +1 -1
  75. package/Misc/dds.js.map +1 -1
  76. package/Misc/environmentTextureTools.js +3 -1
  77. package/Misc/environmentTextureTools.js.map +1 -1
  78. package/Misc/fileTools.js +9 -1
  79. package/Misc/fileTools.js.map +1 -1
  80. package/Misc/sceneSerializer.d.ts +18 -0
  81. package/Misc/sceneSerializer.js +9 -0
  82. package/Misc/sceneSerializer.js.map +1 -1
  83. package/Particles/Node/nodeParticleSystemSet.helper.js +2 -2
  84. package/Particles/Node/nodeParticleSystemSet.helper.js.map +1 -1
  85. package/Physics/v1/physicsImpostor.d.ts +2 -2
  86. package/Physics/v1/physicsImpostor.js.map +1 -1
  87. package/Shaders/ShadersInclude/gaussianSplatting.js +16 -3
  88. package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
  89. package/Shaders/gaussianSplatting.vertex.js +17 -4
  90. package/Shaders/gaussianSplatting.vertex.js.map +1 -1
  91. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +11 -1
  92. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
  93. package/ShadersWGSL/gaussianSplatting.vertex.js +17 -4
  94. package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
  95. package/XR/native/nativeXRFrame.d.ts +1 -1
  96. package/XR/native/nativeXRFrame.js.map +1 -1
  97. package/package.json +1 -1
  98. package/types.d.ts +1 -1
  99. package/types.js.map +1 -1
@@ -2,6 +2,7 @@ import { SubMesh } from "../subMesh.js";
2
2
  import { Mesh } from "../mesh.js";
3
3
  import { VertexData } from "../mesh.vertexData.js";
4
4
  import { Matrix, TmpVectors, Vector2, Vector3 } from "../../Maths/math.vector.js";
5
+ import { Quaternion } from "../../Maths/math.vector.js";
5
6
  import { Logger } from "../../Misc/logger.js";
6
7
  import { GaussianSplattingMaterial } from "../../Materials/GaussianSplatting/gaussianSplattingMaterial.js";
7
8
  import { RawTexture } from "../../Materials/Textures/rawTexture.js";
@@ -12,6 +13,8 @@ import { Scalar } from "../../Maths/math.scalar.js";
12
13
  import { runCoroutineSync, runCoroutineAsync, createYieldingScheduler } from "../../Misc/coroutine.js";
13
14
  import { EngineStore } from "../../Engines/engineStore.js";
14
15
  import { ImportMeshAsync } from "../../Loading/sceneLoader.js";
16
+ const IsNative = typeof _native !== "undefined";
17
+ const Native = IsNative ? _native : null;
15
18
  // @internal
16
19
  const UnpackUnorm = (value, bits) => {
17
20
  const t = (1 << bits) - 1;
@@ -204,6 +207,30 @@ export class GaussianSplattingMesh extends Mesh {
204
207
  get splatsData() {
205
208
  return this._splatsData;
206
209
  }
210
+ /**
211
+ * returns the SH data arrays
212
+ */
213
+ get shData() {
214
+ return this._shData;
215
+ }
216
+ /**
217
+ * True when this mesh is a compound that regroups multiple Gaussian splatting parts.
218
+ */
219
+ get isCompound() {
220
+ return this._partMatrices.length > 0;
221
+ }
222
+ /**
223
+ * returns the part indices array
224
+ */
225
+ get partIndices() {
226
+ return this._partIndices;
227
+ }
228
+ /**
229
+ * Gets the part indices texture, if the mesh is a compound
230
+ */
231
+ get partIndicesTexture() {
232
+ return this._partIndicesTexture;
233
+ }
207
234
  /**
208
235
  * Gets the covariancesA texture
209
236
  */
@@ -297,6 +324,7 @@ export class GaussianSplattingMesh extends Mesh {
297
324
  this._vertexCount = 0;
298
325
  this._worker = null;
299
326
  this._modelViewProjectionMatrix = Matrix.Identity();
327
+ this._viewProjectionMatrix = Matrix.Identity();
300
328
  this._canPostToWorker = true;
301
329
  this._readyToDisplay = false;
302
330
  this._covariancesATexture = null;
@@ -307,6 +335,11 @@ export class GaussianSplattingMesh extends Mesh {
307
335
  this._splatIndex = null;
308
336
  this._shTextures = null;
309
337
  this._splatsData = null;
338
+ this._shData = null;
339
+ this._partIndicesTexture = null;
340
+ this._partIndices = null;
341
+ this._partMatrices = [];
342
+ this._textureSize = new Vector2(0, 0);
310
343
  this._keepInRam = false;
311
344
  this._delayedTextureUpdate = null;
312
345
  this._useRGBACovariants = false;
@@ -386,10 +419,13 @@ export class GaussianSplattingMesh extends Mesh {
386
419
  const cameraProjectionMatrix = camera.getProjectionMatrix();
387
420
  const cameraViewProjectionMatrix = TmpVectors.Matrix[0];
388
421
  cameraViewMatrix.multiplyToRef(cameraProjectionMatrix, cameraViewProjectionMatrix);
389
- this.getWorldMatrix().multiplyToRef(cameraViewProjectionMatrix, this._modelViewProjectionMatrix);
422
+ this._viewProjectionMatrix.copyFrom(cameraViewProjectionMatrix);
423
+ const modelViewMatrix = TmpVectors.Matrix[1];
424
+ this.getWorldMatrix().multiplyToRef(cameraViewMatrix, modelViewMatrix);
425
+ modelViewMatrix.multiplyToRef(cameraProjectionMatrix, this._modelViewProjectionMatrix);
390
426
  // return vector used to compute distance to camera
391
427
  const localDirection = TmpVectors.Vector3[1];
392
- localDirection.set(this._modelViewProjectionMatrix.m[8], this._modelViewProjectionMatrix.m[9], this._modelViewProjectionMatrix.m[10]);
428
+ localDirection.set(modelViewMatrix.m[2], modelViewMatrix.m[6], modelViewMatrix.m[10]);
393
429
  localDirection.normalize();
394
430
  return localDirection;
395
431
  }
@@ -438,7 +474,7 @@ export class GaussianSplattingMesh extends Mesh {
438
474
  });
439
475
  // sort view infos by last updated frame id: first item is the least recently updated
440
476
  activeViewInfos.sort((a, b) => a.frameIdLastUpdate - b.frameIdLastUpdate);
441
- const hasSortFunction = this._worker || (_native && _native.sortSplats) || this._disableDepthSort;
477
+ const hasSortFunction = this._worker || Native?.sortSplats || this._disableDepthSort;
442
478
  if ((forced || outdated) && hasSortFunction && (this._scene.activeCameras?.length || this._scene.activeCamera) && this._canPostToWorker) {
443
479
  // view infos sorted by least recent updated frame id
444
480
  activeViewInfos.forEach((cameraViewInfos) => {
@@ -453,12 +489,13 @@ export class GaussianSplattingMesh extends Mesh {
453
489
  if (this._worker) {
454
490
  this._worker.postMessage({
455
491
  modelViewProjection: this._modelViewProjectionMatrix.m,
492
+ viewProjection: this._viewProjectionMatrix.m,
456
493
  depthMix: this._depthMix,
457
494
  cameraId: camera.uniqueId,
458
495
  }, [this._depthMix.buffer]);
459
496
  }
460
- else if (_native && _native.sortSplats) {
461
- _native.sortSplats(this._modelViewProjectionMatrix, this._splatPositions, this._splatIndex, this._scene.useRightHandedSystem);
497
+ else if (Native?.sortSplats) {
498
+ Native.sortSplats(this._modelViewProjectionMatrix, this._splatPositions, this._splatIndex, this._scene.useRightHandedSystem);
462
499
  if (cameraViewInfos.splatIndexBufferSet) {
463
500
  cameraViewInfos.mesh.thinInstanceBufferUpdated("splatIndex");
464
501
  }
@@ -1190,11 +1227,16 @@ export class GaussianSplattingMesh extends Mesh {
1190
1227
  shTexture.dispose();
1191
1228
  }
1192
1229
  }
1230
+ if (this._partIndicesTexture) {
1231
+ this._partIndicesTexture.dispose();
1232
+ }
1193
1233
  this._covariancesATexture = null;
1194
1234
  this._covariancesBTexture = null;
1195
1235
  this._centersTexture = null;
1196
1236
  this._colorsTexture = null;
1197
1237
  this._shTextures = null;
1238
+ this._partIndicesTexture = null;
1239
+ this._partMatrices = [];
1198
1240
  this._worker?.terminate();
1199
1241
  this._worker = null;
1200
1242
  // delete meshes created for each camera
@@ -1208,9 +1250,10 @@ export class GaussianSplattingMesh extends Mesh {
1208
1250
  this._covariancesBTexture = source.covariancesBTexture?.clone();
1209
1251
  this._centersTexture = source.centersTexture?.clone();
1210
1252
  this._colorsTexture = source.colorsTexture?.clone();
1253
+ this._partIndicesTexture = source._partIndicesTexture?.clone();
1211
1254
  if (source._shTextures) {
1212
1255
  this._shTextures = [];
1213
- for (const shTexture of this._shTextures) {
1256
+ for (const shTexture of source._shTextures) {
1214
1257
  this._shTextures?.push(shTexture.clone());
1215
1258
  }
1216
1259
  }
@@ -1227,9 +1270,11 @@ export class GaussianSplattingMesh extends Mesh {
1227
1270
  newGS._vertexCount = this._vertexCount;
1228
1271
  newGS._copyTextures(this);
1229
1272
  newGS._modelViewProjectionMatrix = Matrix.Identity();
1273
+ newGS._viewProjectionMatrix = Matrix.Identity();
1230
1274
  newGS._splatPositions = this._splatPositions;
1231
1275
  newGS._readyToDisplay = false;
1232
1276
  newGS._disableDepthSort = this._disableDepthSort;
1277
+ newGS._partMatrices = this._partMatrices.map((m) => m.clone());
1233
1278
  newGS._instanciateWorker();
1234
1279
  const binfo = this.getBoundingInfo();
1235
1280
  newGS.getBoundingInfo().reConstruct(binfo.minimum, binfo.maximum, this.getWorldMatrix());
@@ -1294,7 +1339,8 @@ export class GaussianSplattingMesh extends Mesh {
1294
1339
  colorArray[index * 4 + 2] = uBuffer[32 * index + 24 + 2];
1295
1340
  colorArray[index * 4 + 3] = uBuffer[32 * index + 24 + 3];
1296
1341
  }
1297
- _updateTextures(covA, covB, colorArray, sh) {
1342
+ // NB: partIndices is assumed to be padded to a round texture size
1343
+ _updateTextures(covA, covB, colorArray, sh, partIndices) {
1298
1344
  const textureSize = this._getTextureSize(this._vertexCount);
1299
1345
  // Update the textures
1300
1346
  const createTextureFromData = (data, width, height, format) => {
@@ -1309,16 +1355,40 @@ export class GaussianSplattingMesh extends Mesh {
1309
1355
  const createTextureFromDataF16 = (data, width, height, format) => {
1310
1356
  return new RawTexture(data, width, height, format, this._scene, false, false, 2, 2);
1311
1357
  };
1312
- if (this._covariancesATexture) {
1313
- this._delayedTextureUpdate = { covA: covA, covB: covB, colors: colorArray, centers: this._splatPositions, sh: sh };
1358
+ const firstTime = this._covariancesATexture === null;
1359
+ const textureSizeChanged = this._textureSize.y < textureSize.y;
1360
+ if (!firstTime && !textureSizeChanged) {
1361
+ this._delayedTextureUpdate = { covA, covB, colors: colorArray, centers: this._splatPositions, sh, partIndices };
1314
1362
  const positions = Float32Array.from(this._splatPositions);
1315
1363
  const vertexCount = this._vertexCount;
1316
1364
  if (this._worker) {
1317
1365
  this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1318
1366
  }
1367
+ // Handle SH textures in update path - create if they don't exist
1368
+ if (sh && !this._shTextures) {
1369
+ this._shTextures = [];
1370
+ for (const shData of sh) {
1371
+ const buffer = new Uint32Array(shData.buffer);
1372
+ const shTexture = createTextureFromDataU32(buffer, textureSize.x, textureSize.y, 11);
1373
+ shTexture.wrapU = 0;
1374
+ shTexture.wrapV = 0;
1375
+ this._shTextures.push(shTexture);
1376
+ }
1377
+ }
1378
+ // Handle compound data, if any
1379
+ if (partIndices && !this._partIndicesTexture) {
1380
+ const buffer = new Uint8Array(partIndices);
1381
+ this._partIndicesTexture = createTextureFromDataU8(buffer, textureSize.x, textureSize.y, 6);
1382
+ this._partIndicesTexture.wrapU = 0;
1383
+ this._partIndicesTexture.wrapV = 0;
1384
+ }
1385
+ if (this._worker) {
1386
+ this._worker.postMessage({ partIndices: partIndices ?? null });
1387
+ }
1319
1388
  this._postToWorker(true);
1320
1389
  }
1321
1390
  else {
1391
+ this._textureSize = textureSize;
1322
1392
  this._covariancesATexture = createTextureFromDataF16(covA, textureSize.x, textureSize.y, 5);
1323
1393
  this._covariancesBTexture = createTextureFromDataF16(covB, textureSize.x, textureSize.y, this._useRGBACovariants ? 5 : 7);
1324
1394
  this._centersTexture = createTextureFromData(this._splatPositions, textureSize.x, textureSize.y, 5);
@@ -1333,10 +1403,27 @@ export class GaussianSplattingMesh extends Mesh {
1333
1403
  this._shTextures.push(shTexture);
1334
1404
  }
1335
1405
  }
1336
- this._instanciateWorker();
1406
+ if (partIndices) {
1407
+ const buffer = new Uint8Array(partIndices);
1408
+ this._partIndicesTexture = createTextureFromDataU8(buffer, textureSize.x, textureSize.y, 6);
1409
+ this._partIndicesTexture.wrapU = 0;
1410
+ this._partIndicesTexture.wrapV = 0;
1411
+ }
1412
+ if (firstTime) {
1413
+ this._instanciateWorker();
1414
+ }
1415
+ else {
1416
+ if (this._worker) {
1417
+ const positions = Float32Array.from(this._splatPositions);
1418
+ const vertexCount = this._vertexCount;
1419
+ this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1420
+ this._worker.postMessage({ partIndices: partIndices ?? null });
1421
+ }
1422
+ this._postToWorker(true);
1423
+ }
1337
1424
  }
1338
1425
  }
1339
- *_updateData(data, isAsync, sh, options = { flipY: false }) {
1426
+ *_updateData(data, isAsync, sh, partIndices, options = { flipY: false }) {
1340
1427
  // if a covariance texture is present, then it's not a creation but an update
1341
1428
  if (!this._covariancesATexture) {
1342
1429
  this._readyToDisplay = false;
@@ -1346,7 +1433,7 @@ export class GaussianSplattingMesh extends Mesh {
1346
1433
  const fBuffer = new Float32Array(uBuffer.buffer);
1347
1434
  if (this._keepInRam) {
1348
1435
  this._splatsData = data;
1349
- // keep sh in ram too ?
1436
+ this._shData = sh ? sh.map((arr) => new Uint8Array(arr)) : null;
1350
1437
  }
1351
1438
  const vertexCount = uBuffer.length / GaussianSplattingMesh._RowOutputLength;
1352
1439
  if (vertexCount != this._vertexCount) {
@@ -1363,11 +1450,22 @@ export class GaussianSplattingMesh extends Mesh {
1363
1450
  const covA = new Uint16Array(textureLength * 4);
1364
1451
  const covB = new Uint16Array((this._useRGBACovariants ? 4 : 2) * textureLength);
1365
1452
  const colorArray = new Uint8Array(textureLength * 4);
1453
+ // Ensure that partMatrices.length is at least the maximum part index + 1
1454
+ if (partIndices) {
1455
+ // We always keep part indices in RAM because they are needed for sorting
1456
+ this._partIndices = new Uint8Array(textureLength);
1457
+ this._partIndices.set(partIndices);
1458
+ let maxPartIndex = -1;
1459
+ for (let i = 0; i < partIndices.length; i++) {
1460
+ maxPartIndex = Math.max(maxPartIndex, partIndices[i]);
1461
+ }
1462
+ this._ensureMinimumPartMatricesLength(maxPartIndex + 1);
1463
+ }
1366
1464
  const minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
1367
1465
  const maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
1368
1466
  if (GaussianSplattingMesh.ProgressiveUpdateAmount) {
1369
1467
  // create textures with not filled-yet array, then update directly portions of it
1370
- this._updateTextures(covA, covB, colorArray, sh);
1468
+ this._updateTextures(covA, covB, colorArray, sh, this._partIndices ? this._partIndices : undefined);
1371
1469
  this.setEnabled(true);
1372
1470
  const partCount = Math.ceil(textureSize.y / lineCountUpdate);
1373
1471
  for (let partIndex = 0; partIndex < partCount; partIndex++) {
@@ -1388,6 +1486,7 @@ export class GaussianSplattingMesh extends Mesh {
1388
1486
  const vertexCount = this._vertexCount;
1389
1487
  if (this._worker) {
1390
1488
  this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
1489
+ this._worker.postMessage({ partIndices });
1391
1490
  }
1392
1491
  this._sortIsDirty = true;
1393
1492
  }
@@ -1404,7 +1503,7 @@ export class GaussianSplattingMesh extends Mesh {
1404
1503
  this._makeEmptySplat(i, covA, covB, colorArray);
1405
1504
  }
1406
1505
  // textures
1407
- this._updateTextures(covA, covB, colorArray, sh);
1506
+ this._updateTextures(covA, covB, colorArray, sh, this._partIndices ? this._partIndices : undefined);
1408
1507
  // Update the binfo
1409
1508
  this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
1410
1509
  this.setEnabled(true);
@@ -1416,20 +1515,22 @@ export class GaussianSplattingMesh extends Mesh {
1416
1515
  * Update asynchronously the buffer
1417
1516
  * @param data array buffer containing center, color, orientation and scale of splats
1418
1517
  * @param sh optional array of uint8 array for SH data
1518
+ * @param partIndices optional array of uint8 for rig node indices
1419
1519
  * @returns a promise
1420
1520
  */
1421
- async updateDataAsync(data, sh) {
1422
- return await runCoroutineAsync(this._updateData(data, true, sh), createYieldingScheduler());
1521
+ async updateDataAsync(data, sh, partIndices) {
1522
+ return await runCoroutineAsync(this._updateData(data, true, sh, partIndices), createYieldingScheduler());
1423
1523
  }
1424
1524
  /**
1425
1525
  * @experimental
1426
1526
  * Update data from GS (position, orientation, color, scaling)
1427
1527
  * @param data array that contain all the datas
1428
1528
  * @param sh optional array of uint8 array for SH data
1429
- * @param options optional informations on how to treat data
1529
+ * @param options optional informations on how to treat data (needs to be 3rd for backward compatibility)
1530
+ * @param partIndices optional array of uint8 for rig node indices
1430
1531
  */
1431
- updateData(data, sh, options = { flipY: true }) {
1432
- runCoroutineSync(this._updateData(data, false, sh, options));
1532
+ updateData(data, sh, options = { flipY: true }, partIndices) {
1533
+ runCoroutineSync(this._updateData(data, false, sh, partIndices, options));
1433
1534
  }
1434
1535
  /**
1435
1536
  * Refreshes the bounding info, taking into account all the thin instances defined
@@ -1452,9 +1553,13 @@ export class GaussianSplattingMesh extends Mesh {
1452
1553
  cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
1453
1554
  });
1454
1555
  }
1556
+ // Update depthMix
1557
+ if ((!this._depthMix || vertexCount > this._depthMix.length) && !IsNative) {
1558
+ this._depthMix = new BigInt64Array(paddedVertexCount);
1559
+ }
1455
1560
  this.forcedInstanceCount = paddedVertexCount >> 4;
1456
1561
  }
1457
- _updateSubTextures(centers, covA, covB, colors, lineStart, lineCount, sh) {
1562
+ _updateSubTextures(centers, covA, covB, colors, lineStart, lineCount, sh, partIndices) {
1458
1563
  const updateTextureFromData = (texture, data, width, lineStart, lineCount) => {
1459
1564
  this.getEngine().updateTextureData(texture.getInternalTexture(), data, 0, lineStart, width, lineCount, 0, 0, false);
1460
1565
  };
@@ -1477,6 +1582,10 @@ export class GaussianSplattingMesh extends Mesh {
1477
1582
  updateTextureFromData(this._shTextures[i], shView, textureSize.x, lineStart, lineCount);
1478
1583
  }
1479
1584
  }
1585
+ if (partIndices && this._partIndicesTexture) {
1586
+ const partIndicesView = new Uint8Array(partIndices.buffer, texelStart, texelCount);
1587
+ updateTextureFromData(this._partIndicesTexture, partIndicesView, textureSize.x, lineStart, lineCount);
1588
+ }
1480
1589
  }
1481
1590
  _instanciateWorker() {
1482
1591
  if (!this._vertexCount) {
@@ -1487,7 +1596,7 @@ export class GaussianSplattingMesh extends Mesh {
1487
1596
  }
1488
1597
  this._updateSplatIndexBuffer(this._vertexCount);
1489
1598
  // no worker in native
1490
- if (_native) {
1599
+ if (IsNative) {
1491
1600
  return;
1492
1601
  }
1493
1602
  // Start the worker thread
@@ -1495,11 +1604,22 @@ export class GaussianSplattingMesh extends Mesh {
1495
1604
  this._worker = new Worker(URL.createObjectURL(new Blob(["(", GaussianSplattingMesh._CreateWorker.toString(), ")(self)"], {
1496
1605
  type: "application/javascript",
1497
1606
  })));
1498
- const vertexCountPadded = (this._vertexCount + 15) & ~0xf;
1499
- this._depthMix = new BigInt64Array(vertexCountPadded);
1500
1607
  const positions = Float32Array.from(this._splatPositions);
1608
+ const partIndices = this._partIndices ? new Uint8Array(this._partIndices) : null;
1609
+ const partMatrices = this._partMatrices.map((matrix) => new Float32Array(matrix.m));
1501
1610
  this._worker.postMessage({ positions }, [positions.buffer]);
1611
+ this._worker.postMessage({ partIndices });
1612
+ this._worker.postMessage({ partMatrices });
1502
1613
  this._worker.onmessage = (e) => {
1614
+ // Recompute vertexCountPadded in case _vertexCount has changed since the last update
1615
+ const vertexCountPadded = (this._vertexCount + 15) & ~0xf;
1616
+ // If the vertex count changed, we discard this result and trigger a new sort
1617
+ if (e.data.depthMix.length != vertexCountPadded) {
1618
+ this._canPostToWorker = true;
1619
+ this._postToWorker(true);
1620
+ this._sortIsDirty = false;
1621
+ return;
1622
+ }
1503
1623
  this._depthMix = e.data.depthMix;
1504
1624
  const cameraId = e.data.cameraId;
1505
1625
  const indexMix = new Uint32Array(e.data.depthMix.buffer);
@@ -1510,7 +1630,7 @@ export class GaussianSplattingMesh extends Mesh {
1510
1630
  }
1511
1631
  if (this._delayedTextureUpdate) {
1512
1632
  const textureSize = this._getTextureSize(vertexCountPadded);
1513
- this._updateSubTextures(this._delayedTextureUpdate.centers, this._delayedTextureUpdate.covA, this._delayedTextureUpdate.covB, this._delayedTextureUpdate.colors, 0, textureSize.y, this._delayedTextureUpdate.sh);
1633
+ this._updateSubTextures(this._delayedTextureUpdate.centers, this._delayedTextureUpdate.covA, this._delayedTextureUpdate.covB, this._delayedTextureUpdate.colors, 0, textureSize.y, this._delayedTextureUpdate.sh, this._delayedTextureUpdate.partIndices);
1514
1634
  this._delayedTextureUpdate = null;
1515
1635
  }
1516
1636
  // get mesh for camera and update its instance buffer
@@ -1552,6 +1672,163 @@ export class GaussianSplattingMesh extends Mesh {
1552
1672
  }
1553
1673
  return new Vector2(width, height);
1554
1674
  }
1675
+ /**
1676
+ * Gets the number of parts in the compound
1677
+ * @returns the number of parts in the compound, or 0 if the mesh is not a compound
1678
+ */
1679
+ get partCount() {
1680
+ return this._partMatrices.length;
1681
+ }
1682
+ /**
1683
+ * Sets the world matrix for a specific part of the compound (if this mesh is a compound).
1684
+ * This will trigger a re-sort of the mesh.
1685
+ * @param partIndex index of the part, that must be between 0 and partCount - 1
1686
+ * @param worldMatrix the world matrix to set
1687
+ */
1688
+ setWorldMatrixForPart(partIndex, worldMatrix) {
1689
+ this._partMatrices[partIndex].copyFrom(worldMatrix);
1690
+ if (this._worker) {
1691
+ this._worker.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
1692
+ }
1693
+ this._postToWorker(true);
1694
+ }
1695
+ /**
1696
+ * Gets the world matrix for a specific part of the compound (if this mesh is a compound).
1697
+ * @param partIndex index of the part, that must be between 0 and partCount - 1
1698
+ * @returns the world matrix for the part, or the current world matrix of the mesh if the mesh is not a compound
1699
+ */
1700
+ getWorldMatrixForPart(partIndex) {
1701
+ return this._partMatrices[partIndex] ?? this.getWorldMatrix();
1702
+ }
1703
+ /**
1704
+ * Ensure that the part world matrix array is at least the given length.
1705
+ * NB: This length is used as reference for the number of parts in the compound.
1706
+ * Newly inserted parts are initialized with the current world matrix of the mesh.
1707
+ * @param length - The minimum length to ensure
1708
+ */
1709
+ _ensureMinimumPartMatricesLength(length) {
1710
+ if (this._partMatrices.length < length) {
1711
+ this._resizePartMatrices(length);
1712
+ }
1713
+ }
1714
+ /**
1715
+ * This sets the number of parts in the compound.
1716
+ * Warning: This must be consistent with the indices used in the partIndices texture.
1717
+ * Newly inserted parts are initialized with the current world matrix of the mesh.
1718
+ * @param length - The length to resize to
1719
+ */
1720
+ _resizePartMatrices(length) {
1721
+ if (this._partMatrices.length == length) {
1722
+ return;
1723
+ }
1724
+ else if (this._partMatrices.length > length) {
1725
+ this._partMatrices = this._partMatrices.slice(0, length);
1726
+ }
1727
+ else {
1728
+ this.computeWorldMatrix(true);
1729
+ const defaultMatrix = this.getWorldMatrix();
1730
+ while (this._partMatrices.length < length) {
1731
+ this._partMatrices.push(defaultMatrix.clone());
1732
+ }
1733
+ }
1734
+ if (this._worker) {
1735
+ this._worker.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
1736
+ }
1737
+ this._postToWorker(true);
1738
+ }
1739
+ /**
1740
+ * Add another mesh to this mesh, as a new part. This makes the current mesh a compound, if not already.
1741
+ * NB: The current mesh needs to be loaded with keepInRam: true.
1742
+ * @param other - The other mesh to add. This must be loaded with keepInRam: true.
1743
+ * @param disposeOther - Whether to dispose the other mesh after adding it to the current mesh.
1744
+ * @returns a placeholder mesh that can be used to manipulate the part transform
1745
+ */
1746
+ addPart(other, disposeOther = true) {
1747
+ const splatCountA = this._vertexCount;
1748
+ const splatsDataA = splatCountA == 0 ? new ArrayBuffer(0) : this.splatsData;
1749
+ const shDataA = this.shData;
1750
+ const splatCountB = other._vertexCount;
1751
+ const splatsDataB = other.splatsData;
1752
+ const shDataB = other.shData;
1753
+ const mergedShDataLength = Math.max(shDataA?.length || 0, shDataB?.length || 0);
1754
+ const hasMergedShData = shDataA !== null && shDataB !== null;
1755
+ // Sanity checks
1756
+ if (!splatsDataA) {
1757
+ throw new Error(`To call addPart(), the current mesh must be loaded with keepInRam: true`);
1758
+ }
1759
+ const expectedSplatsDataSizeA = splatCountA * GaussianSplattingMesh._RowOutputLength;
1760
+ if (splatsDataA.byteLength !== expectedSplatsDataSizeA) {
1761
+ throw new Error(`splatsDataA size (${splatsDataA.byteLength}) does not match expected size (${expectedSplatsDataSizeA})`);
1762
+ }
1763
+ if (!splatsDataB) {
1764
+ throw new Error(`To call addPart(), the other mesh must be loaded with keepInRam: true`);
1765
+ }
1766
+ const expectedSplatsDataSizeB = splatCountB * GaussianSplattingMesh._RowOutputLength;
1767
+ if (splatsDataB.byteLength !== expectedSplatsDataSizeB) {
1768
+ throw new Error(`splatsDataB size (${splatsDataB.byteLength}) does not match expected size (${expectedSplatsDataSizeB})`);
1769
+ }
1770
+ if (other.partIndices) {
1771
+ throw new Error(`To call addPart(), the other mesh must not be a compound`);
1772
+ }
1773
+ // Concatenate splatsData (ArrayBuffer)
1774
+ const mergedSplatsData = new Uint8Array(splatsDataA.byteLength + splatsDataB.byteLength);
1775
+ mergedSplatsData.set(new Uint8Array(splatsDataA), 0);
1776
+ mergedSplatsData.set(new Uint8Array(splatsDataB), splatsDataA.byteLength);
1777
+ let mergedShData = undefined;
1778
+ if (hasMergedShData) {
1779
+ // Note: We need to calculate the texture size and pad accordingly
1780
+ // Each SH texture texel stores 16 bytes (4 RGBA uint32 components)
1781
+ const bytesPerTexel = 16;
1782
+ const totalSplatCount = splatCountA + splatCountB;
1783
+ mergedShData = [];
1784
+ for (let i = 0; i < mergedShDataLength; i++) {
1785
+ const mergedShDataItem = new Uint8Array(totalSplatCount * bytesPerTexel);
1786
+ if (i < (shDataA?.length ?? 0)) {
1787
+ mergedShDataItem.set(shDataA[i], 0);
1788
+ }
1789
+ if (i < (shDataB?.length ?? 0)) {
1790
+ const byteOffset = bytesPerTexel * splatCountA;
1791
+ mergedShDataItem.set(shDataB[i], byteOffset);
1792
+ }
1793
+ mergedShData.push(mergedShDataItem);
1794
+ }
1795
+ }
1796
+ // Concatenate partIndices (Uint8Array)
1797
+ let newPartIndex = this.partCount;
1798
+ let partIndicesA = this.partIndices;
1799
+ if (!partIndicesA) {
1800
+ partIndicesA = new Uint8Array(splatCountA);
1801
+ newPartIndex = splatCountA > 0 ? 1 : 0;
1802
+ //newPartIndex = 1;
1803
+ }
1804
+ if (partIndicesA.length < splatCountA) {
1805
+ throw new Error(`partIndices length (${partIndicesA.length}) should be at least vertexCount (${splatCountA}) in the current mesh`);
1806
+ }
1807
+ const partIndicesB = new Uint8Array(splatCountB).fill(newPartIndex);
1808
+ const mergedPartIndices = new Uint8Array(splatCountA + splatCountB);
1809
+ mergedPartIndices.set(partIndicesA.slice(0, splatCountA), 0);
1810
+ mergedPartIndices.set(partIndicesB, splatCountA);
1811
+ this.updateData(mergedSplatsData.buffer, mergedShData, { flipY: false }, mergedPartIndices);
1812
+ // Merge part matrices (TODO)
1813
+ const partWorldMatrix = other.getWorldMatrix();
1814
+ this.setWorldMatrixForPart(newPartIndex, partWorldMatrix);
1815
+ // Create a placeholder mesh to manipulate the part transform
1816
+ // Remove splats from the original mesh
1817
+ if (disposeOther) {
1818
+ other.dispose();
1819
+ }
1820
+ const placeholderMesh = new Mesh(other.name, this.getScene());
1821
+ placeholderMesh.onAfterWorldMatrixUpdateObservable.add(() => {
1822
+ this.setWorldMatrixForPart(newPartIndex, placeholderMesh.getWorldMatrix());
1823
+ });
1824
+ // Directly set the world matrix using freezeWorldMatrix
1825
+ const quaternion = new Quaternion();
1826
+ partWorldMatrix.decompose(placeholderMesh.scaling, quaternion, placeholderMesh.position);
1827
+ placeholderMesh.rotationQuaternion = quaternion;
1828
+ placeholderMesh.computeWorldMatrix(true);
1829
+ placeholderMesh.metadata = { partIndex: newPartIndex };
1830
+ return placeholderMesh;
1831
+ }
1555
1832
  }
1556
1833
  GaussianSplattingMesh._RowOutputLength = 3 * 4 + 3 * 4 + 4 + 4; // Vector3 position, Vector3 scale, 1 u8 quaternion, 1 color with alpha
1557
1834
  GaussianSplattingMesh._SH_C0 = 0.28209479177387814;
@@ -1572,17 +1849,39 @@ GaussianSplattingMesh._CreateWorker = function (self) {
1572
1849
  let depthMix;
1573
1850
  let indices;
1574
1851
  let floatMix;
1852
+ let partIndices;
1853
+ let partMatrices;
1854
+ function multiplyMatrices(matrix1, matrix2) {
1855
+ const result = new Float32Array(16);
1856
+ for (let i = 0; i < 4; i++) {
1857
+ for (let j = 0; j < 4; j++) {
1858
+ for (let k = 0; k < 4; k++) {
1859
+ result[j * 4 + i] += matrix1[k * 4 + i] * matrix2[j * 4 + k];
1860
+ }
1861
+ }
1862
+ }
1863
+ return result;
1864
+ }
1575
1865
  self.onmessage = (e) => {
1576
1866
  // updated on init
1577
1867
  if (e.data.positions) {
1578
1868
  positions = e.data.positions;
1579
1869
  }
1580
- // udpate on view changed
1870
+ // update on rig node changed
1871
+ else if (e.data.partMatrices) {
1872
+ partMatrices = e.data.partMatrices;
1873
+ }
1874
+ // update on rig node indices changed
1875
+ else if (e.data.partIndices !== undefined) {
1876
+ partIndices = e.data.partIndices;
1877
+ }
1878
+ // update on view changed
1581
1879
  else {
1582
1880
  const cameraId = e.data.cameraId;
1583
- const modelViewProjection = e.data.modelViewProjection;
1881
+ const globalModelViewProjection = e.data.modelViewProjection;
1882
+ const viewProjection = e.data.viewProjection;
1584
1883
  const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
1585
- if (!positions || !modelViewProjection) {
1884
+ if (!positions || !globalModelViewProjection) {
1586
1885
  // Sanity check, it shouldn't happen!
1587
1886
  throw new Error("positions or modelViewProjection matrix is not defined!");
1588
1887
  }
@@ -1593,9 +1892,29 @@ GaussianSplattingMesh._CreateWorker = function (self) {
1593
1892
  for (let j = 0; j < vertexCountPadded; j++) {
1594
1893
  indices[2 * j] = j;
1595
1894
  }
1596
- for (let j = 0; j < vertexCountPadded; j++) {
1597
- floatMix[2 * j + 1] =
1598
- 10000 - (modelViewProjection[2] * positions[4 * j + 0] + modelViewProjection[6] * positions[4 * j + 1] + modelViewProjection[10] * positions[4 * j + 2]);
1895
+ let depthFactor = -1;
1896
+ if (e.data.useRightHandedSystem) {
1897
+ depthFactor = 1;
1898
+ }
1899
+ if (partMatrices && partIndices) {
1900
+ // If there are rig node matrices, we use them instead of the global model view proj
1901
+ // Precompute modelViewProj for each rig node
1902
+ const modelViewProjs = partMatrices.map((model) => multiplyMatrices(viewProjection, model));
1903
+ // NB: For performance reasons, we assume that part indices are valid
1904
+ const length = partIndices.length;
1905
+ for (let j = 0; j < vertexCountPadded; j++) {
1906
+ // NB: We need this 'min' because vertex array is padded, not partIndices
1907
+ const partIndex = partIndices[Math.min(j, length - 1)];
1908
+ const mvp = modelViewProjs[partIndex];
1909
+ floatMix[2 * j + 1] = 10000 + (mvp[2] * positions[4 * j + 0] + mvp[6] * positions[4 * j + 1] + mvp[10] * positions[4 * j + 2] + mvp[14]) * depthFactor;
1910
+ }
1911
+ }
1912
+ else {
1913
+ // If there are no rig node matrices, we use the global model view proj
1914
+ const mvp = globalModelViewProjection;
1915
+ for (let j = 0; j < vertexCountPadded; j++) {
1916
+ floatMix[2 * j + 1] = 10000 + (mvp[2] * positions[4 * j + 0] + mvp[6] * positions[4 * j + 1] + mvp[10] * positions[4 * j + 2] + mvp[14]) * depthFactor;
1917
+ }
1599
1918
  }
1600
1919
  depthMix.sort();
1601
1920
  self.postMessage({ depthMix, cameraId }, [depthMix.buffer]);