@babylonjs/core 8.48.0 → 8.48.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.
- package/Audio/analyser.js.map +1 -1
- package/AudioV2/webAudio/subNodes/webAudioAnalyzerSubNode.js.map +1 -1
- package/Buffers/buffer.align.js +3 -3
- package/Buffers/buffer.align.js.map +1 -1
- package/Buffers/bufferUtils.d.ts +7 -0
- package/Buffers/bufferUtils.js +31 -13
- package/Buffers/bufferUtils.js.map +1 -1
- package/Engines/Extensions/engine.dynamicBuffer.js +3 -3
- package/Engines/Extensions/engine.dynamicBuffer.js.map +1 -1
- package/Engines/Native/nativeDataStream.d.ts +1 -1
- package/Engines/Native/nativeDataStream.js.map +1 -1
- package/Engines/Native/nativeInterfaces.d.ts +4 -4
- package/Engines/Native/nativeInterfaces.js.map +1 -1
- package/Engines/WebGPU/Extensions/engine.rawTexture.d.ts +13 -1
- package/Engines/WebGPU/Extensions/engine.rawTexture.js +26 -8
- package/Engines/WebGPU/Extensions/engine.rawTexture.js.map +1 -1
- package/Engines/WebGPU/webgpuTextureManager.d.ts +2 -1
- package/Engines/WebGPU/webgpuTextureManager.js +19 -6
- package/Engines/WebGPU/webgpuTextureManager.js.map +1 -1
- package/Engines/abstractEngine.d.ts +4 -2
- package/Engines/abstractEngine.functions.d.ts +1 -1
- package/Engines/abstractEngine.functions.js +8 -8
- package/Engines/abstractEngine.functions.js.map +1 -1
- package/Engines/abstractEngine.js +6 -4
- package/Engines/abstractEngine.js.map +1 -1
- package/Engines/engine.d.ts +12 -0
- package/Engines/thinNativeEngine.js +1 -1
- package/Engines/thinNativeEngine.js.map +1 -1
- package/Engines/thinWebGPUEngine.js +3 -0
- package/Engines/thinWebGPUEngine.js.map +1 -1
- package/Engines/webgpuEngine.js +18 -18
- package/Engines/webgpuEngine.js.map +1 -1
- package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
- package/Materials/Background/backgroundMaterial.d.ts +16 -8
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +14 -1
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
- package/Materials/Node/nodeMaterial.d.ts +16 -8
- package/Materials/PBR/openpbrMaterial.d.ts +16 -8
- package/Materials/PBR/pbrBaseMaterial.d.ts +16 -8
- package/Materials/Textures/Loaders/EXR/exrLoader.compression.rle.d.ts +1 -1
- package/Materials/Textures/Loaders/EXR/exrLoader.compression.rle.js.map +1 -1
- package/Materials/Textures/Loaders/EXR/exrLoader.core.d.ts +1 -1
- package/Materials/Textures/Loaders/EXR/exrLoader.core.js.map +1 -1
- package/Materials/Textures/internalTexture.d.ts +6 -0
- package/Materials/Textures/internalTexture.js +24 -2
- package/Materials/Textures/internalTexture.js.map +1 -1
- package/Materials/Textures/rawTexture.d.ts +8 -1
- package/Materials/Textures/rawTexture.js +12 -3
- package/Materials/Textures/rawTexture.js.map +1 -1
- package/Materials/Textures/rawTexture2DArray.d.ts +8 -1
- package/Materials/Textures/rawTexture2DArray.js +14 -3
- package/Materials/Textures/rawTexture2DArray.js.map +1 -1
- package/Materials/imageProcessing.d.ts +47 -8
- package/Materials/standardMaterial.d.ts +16 -8
- package/Meshes/Builders/shapeBuilder.d.ts +4 -0
- package/Meshes/Builders/shapeBuilder.js +12 -9
- package/Meshes/Builders/shapeBuilder.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +68 -4
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +343 -26
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
- package/Misc/dds.js.map +1 -1
- package/Misc/environmentTextureTools.js +3 -1
- package/Misc/environmentTextureTools.js.map +1 -1
- package/Misc/fileTools.js +9 -1
- package/Misc/fileTools.js.map +1 -1
- package/Physics/v1/physicsImpostor.d.ts +2 -2
- package/Physics/v1/physicsImpostor.js.map +1 -1
- package/Shaders/ShadersInclude/gaussianSplatting.js +16 -3
- package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/Shaders/gaussianSplatting.vertex.js +17 -4
- package/Shaders/gaussianSplatting.vertex.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +11 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/ShadersWGSL/gaussianSplatting.vertex.js +17 -4
- package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
- package/XR/native/nativeXRFrame.d.ts +1 -1
- package/XR/native/nativeXRFrame.js.map +1 -1
- package/package.json +1 -1
- package/types.d.ts +1 -1
- 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.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
1879
|
+
const globalModelViewProjection = e.data.modelViewProjection;
|
|
1880
|
+
const viewProjection = e.data.viewProjection;
|
|
1584
1881
|
const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
|
|
1585
|
-
if (!positions || !
|
|
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
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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]);
|