@babylonjs/core 8.48.0 → 8.48.2

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