@babylonjs/core 9.3.1 → 9.3.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 (58) hide show
  1. package/Engines/abstractEngine.js +2 -2
  2. package/Engines/abstractEngine.js.map +1 -1
  3. package/Lights/Clustered/clusteredLightContainer.d.ts +1 -0
  4. package/Lights/Clustered/clusteredLightContainer.js +19 -0
  5. package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
  6. package/Lights/light.d.ts +6 -0
  7. package/Lights/light.js +8 -0
  8. package/Lights/light.js.map +1 -1
  9. package/Lights/spotLight.d.ts +2 -0
  10. package/Lights/spotLight.js +10 -0
  11. package/Lights/spotLight.js.map +1 -1
  12. package/Materials/Background/backgroundMaterial.js +4 -1
  13. package/Materials/Background/backgroundMaterial.js.map +1 -1
  14. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +6 -2
  15. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
  16. package/Materials/Node/Blocks/Dual/lightBlock.d.ts +8 -0
  17. package/Materials/Node/Blocks/Dual/lightBlock.js +16 -0
  18. package/Materials/Node/Blocks/Dual/lightBlock.js.map +1 -1
  19. package/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.js +3 -0
  20. package/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.js.map +1 -1
  21. package/Materials/Node/nodeMaterial.js +4 -1
  22. package/Materials/Node/nodeMaterial.js.map +1 -1
  23. package/Materials/PBR/openpbrMaterial.js +4 -1
  24. package/Materials/PBR/openpbrMaterial.js.map +1 -1
  25. package/Materials/PBR/pbrBaseMaterial.js +4 -1
  26. package/Materials/PBR/pbrBaseMaterial.js.map +1 -1
  27. package/Materials/materialHelper.functions.d.ts +12 -0
  28. package/Materials/materialHelper.functions.js +24 -0
  29. package/Materials/materialHelper.functions.js.map +1 -1
  30. package/Materials/standardMaterial.js +4 -1
  31. package/Materials/standardMaterial.js.map +1 -1
  32. package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +13 -0
  33. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +26 -0
  34. package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
  35. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.d.ts +3 -0
  36. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +113 -10
  37. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -1
  38. package/Misc/tools.js +1 -1
  39. package/Misc/tools.js.map +1 -1
  40. package/Rendering/depthRenderer.d.ts +8 -0
  41. package/Rendering/depthRenderer.js +48 -13
  42. package/Rendering/depthRenderer.js.map +1 -1
  43. package/Rendering/depthRendererSceneComponent.d.ts +1 -0
  44. package/Rendering/depthRendererSceneComponent.js +26 -0
  45. package/Rendering/depthRendererSceneComponent.js.map +1 -1
  46. package/XR/features/WebXRBodyTracking.d.ts +952 -0
  47. package/XR/features/WebXRBodyTracking.js +2221 -0
  48. package/XR/features/WebXRBodyTracking.js.map +1 -0
  49. package/XR/features/index.d.ts +1 -0
  50. package/XR/features/index.js +1 -0
  51. package/XR/features/index.js.map +1 -1
  52. package/XR/webXRFeaturesManager.d.ts +7 -0
  53. package/XR/webXRFeaturesManager.js +4 -0
  54. package/XR/webXRFeaturesManager.js.map +1 -1
  55. package/package.json +1 -1
  56. package/sceneComponent.d.ts +1 -0
  57. package/sceneComponent.js +1 -0
  58. package/sceneComponent.js.map +1 -1
@@ -393,6 +393,8 @@ export class GaussianSplattingMeshBase extends Mesh {
393
393
  this._modelViewProjectionMatrix = Matrix.Identity();
394
394
  this._canPostToWorker = true;
395
395
  this._readyToDisplay = false;
396
+ this._sortRequestId = 0;
397
+ this._hasRenderedOnce = false;
396
398
  this._covariancesATexture = null;
397
399
  this._covariancesBTexture = null;
398
400
  this._centersTexture = null;
@@ -496,6 +498,66 @@ export class GaussianSplattingMeshBase extends Mesh {
496
498
  this._postToWorker(true);
497
499
  return false;
498
500
  }
501
+ // Before the first successful render, apply strict sort-state checks to ensure
502
+ // the first rendered frame uses correct splat ordering. Once the mesh has been
503
+ // rendered at least once, skip these checks — the render loop will continuously
504
+ // re-sort as the camera/world changes via _postToWorker() in render().
505
+ if (!this._hasRenderedOnce && !this._disableDepthSort) {
506
+ const cameras = this._scene.activeCameras?.length ? this._scene.activeCameras : [this._scene.activeCamera];
507
+ const worldMatrix = this.computeWorldMatrix(true);
508
+ let anyDirty = false;
509
+ for (const camera of cameras) {
510
+ if (!camera) {
511
+ continue;
512
+ }
513
+ const cameraViewInfo = this._cameraViewInfos.get(camera.uniqueId);
514
+ if (!cameraViewInfo || !cameraViewInfo.splatIndexBufferSet) {
515
+ anyDirty = true;
516
+ continue;
517
+ }
518
+ // Wait for the most recently requested sort to be applied so that the splat indices
519
+ // match the latest world/camera state.
520
+ if (cameraViewInfo.sortAppliedId !== cameraViewInfo.sortRequestId) {
521
+ anyDirty = true;
522
+ continue;
523
+ }
524
+ // Also detect drift: if the world or camera state has changed since the last post,
525
+ // mark dirty so the next render does not silently queue a new sort that completes
526
+ // after isReady has reported true.
527
+ if (this._isSortStateDirty(cameraViewInfo, worldMatrix, camera)) {
528
+ anyDirty = true;
529
+ }
530
+ }
531
+ if (anyDirty) {
532
+ // Try to post any pending sort so subsequent polling iterations make progress.
533
+ this._postToWorker(true);
534
+ return false;
535
+ }
536
+ }
537
+ // Attach the splat geometry to the GS top mesh so that the shadow generator (which renders
538
+ // shadow casters via the top mesh's subMeshes, NOT through this mesh's render() override)
539
+ // has valid geometry on the very first shadow pass. Without this, the first shadow render
540
+ // happens before render() is called and the GS produces no shadow caster output.
541
+ if (!this._geometry && this._cameraViewInfos.size) {
542
+ this._geometry = this._cameraViewInfos.values().next().value.mesh.geometry;
543
+ }
544
+ // If the material declares a shadow depth wrapper, make sure its effect is compiled for
545
+ // each subMesh against the scene's shadow generators. Otherwise the first shadow pass
546
+ // would be skipped (ShadowGenerator.isReady would return false) and we'd miss the shadow
547
+ // on a renderCount=1 capture.
548
+ if (this.material && this.material.shadowDepthWrapper) {
549
+ for (const light of this._scene.lights) {
550
+ const shadowGenerator = light.getShadowGenerator();
551
+ if (!shadowGenerator) {
552
+ continue;
553
+ }
554
+ for (const subMesh of this.subMeshes) {
555
+ if (!shadowGenerator.isReady(subMesh, true, false)) {
556
+ return false;
557
+ }
558
+ }
559
+ }
560
+ }
499
561
  return true;
500
562
  }
501
563
  _getCameraDirection(camera) {
@@ -503,7 +565,7 @@ export class GaussianSplattingMeshBase extends Mesh {
503
565
  const cameraProjectionMatrix = camera.getProjectionMatrix();
504
566
  const cameraViewProjectionMatrix = TmpVectors.Matrix[0];
505
567
  cameraViewMatrix.multiplyToRef(cameraProjectionMatrix, cameraViewProjectionMatrix);
506
- const modelMatrix = this.getWorldMatrix();
568
+ const modelMatrix = this.computeWorldMatrix(true);
507
569
  const modelViewMatrix = TmpVectors.Matrix[1];
508
570
  modelMatrix.multiplyToRef(cameraViewMatrix, modelViewMatrix);
509
571
  modelMatrix.multiplyToRef(cameraViewProjectionMatrix, this._modelViewProjectionMatrix);
@@ -513,6 +575,25 @@ export class GaussianSplattingMeshBase extends Mesh {
513
575
  localDirection.normalize();
514
576
  return localDirection;
515
577
  }
578
+ _isSortStateDirty(cameraViewInfo, worldMatrix, camera) {
579
+ const world = worldMatrix.m;
580
+ const previousWorld = cameraViewInfo.sortWorldMatrix.m;
581
+ for (let i = 0; i < previousWorld.length; i++) {
582
+ if (!Scalar.WithinEpsilon(previousWorld[i], world[i], this.viewUpdateThreshold)) {
583
+ return true;
584
+ }
585
+ }
586
+ const cameraViewMatrix = camera.getViewMatrix();
587
+ if (!Scalar.WithinEpsilon(cameraViewInfo.sortCameraForward.x, cameraViewMatrix.m[2], this.viewUpdateThreshold) ||
588
+ !Scalar.WithinEpsilon(cameraViewInfo.sortCameraForward.y, cameraViewMatrix.m[6], this.viewUpdateThreshold) ||
589
+ !Scalar.WithinEpsilon(cameraViewInfo.sortCameraForward.z, cameraViewMatrix.m[10], this.viewUpdateThreshold)) {
590
+ return true;
591
+ }
592
+ const cameraPosition = camera.globalPosition;
593
+ return (!Scalar.WithinEpsilon(cameraViewInfo.sortCameraPosition.x, cameraPosition.x, this.viewUpdateThreshold) ||
594
+ !Scalar.WithinEpsilon(cameraViewInfo.sortCameraPosition.y, cameraPosition.y, this.viewUpdateThreshold) ||
595
+ !Scalar.WithinEpsilon(cameraViewInfo.sortCameraPosition.z, cameraPosition.z, this.viewUpdateThreshold));
596
+ }
516
597
  /** @internal */
517
598
  _postToWorker(forced = false) {
518
599
  const scene = this._scene;
@@ -559,6 +640,11 @@ export class GaussianSplattingMeshBase extends Mesh {
559
640
  const newViewInfos = {
560
641
  camera: camera,
561
642
  cameraDirection: new Vector3(0, 0, 0),
643
+ sortWorldMatrix: Matrix.Identity(),
644
+ sortCameraForward: new Vector3(0, 0, 0),
645
+ sortCameraPosition: new Vector3(0, 0, 0),
646
+ sortRequestId: 0,
647
+ sortAppliedId: 0,
562
648
  mesh: cameraMesh,
563
649
  frameIdLastUpdate: frameId,
564
650
  splatIndexBufferSet: false,
@@ -567,28 +653,39 @@ export class GaussianSplattingMeshBase extends Mesh {
567
653
  this._cameraViewInfos.set(cameraId, newViewInfos);
568
654
  }
569
655
  });
570
- // sort view infos by last updated frame id: first item is the least recently updated
571
- activeViewInfos.sort((a, b) => a.frameIdLastUpdate - b.frameIdLastUpdate);
656
+ // sort view infos: cameras without an initial splat-index buffer come first so they don't get starved
657
+ // by a `forced` re-sort of an already-initialized camera (which would consume `_canPostToWorker`).
658
+ // Among initialized cameras, the least recently updated comes first.
659
+ activeViewInfos.sort((a, b) => {
660
+ if (a.splatIndexBufferSet !== b.splatIndexBufferSet) {
661
+ return a.splatIndexBufferSet ? 1 : -1;
662
+ }
663
+ return a.frameIdLastUpdate - b.frameIdLastUpdate;
664
+ });
572
665
  const hasSortFunction = this._worker || Native?.sortSplats || this._disableDepthSort;
573
666
  if ((forced || outdated) && hasSortFunction && (this._scene.activeCameras?.length || this._scene.activeCamera) && this._canPostToWorker) {
667
+ const worldMatrix = this.computeWorldMatrix(true);
574
668
  // view infos sorted by least recent updated frame id
575
669
  activeViewInfos.forEach((cameraViewInfos) => {
576
670
  const camera = cameraViewInfos.camera;
577
671
  const cameraDirection = this._getCameraDirection(camera);
578
- const previousCameraDirection = cameraViewInfos.cameraDirection;
579
- const dot = Vector3.Dot(cameraDirection, previousCameraDirection);
580
- if ((forced || Math.abs(dot - 1) >= this.viewUpdateThreshold) && this._canPostToWorker) {
672
+ if ((forced || this._isSortStateDirty(cameraViewInfos, worldMatrix, camera)) && this._canPostToWorker) {
673
+ const cameraViewMatrix = camera.getViewMatrix();
581
674
  cameraViewInfos.cameraDirection.copyFrom(cameraDirection);
675
+ cameraViewInfos.sortWorldMatrix.copyFrom(worldMatrix);
676
+ cameraViewInfos.sortCameraForward.set(cameraViewMatrix.m[2], cameraViewMatrix.m[6], cameraViewMatrix.m[10]);
677
+ cameraViewInfos.sortCameraPosition.copyFrom(camera.globalPosition);
678
+ cameraViewInfos.sortRequestId = ++this._sortRequestId;
582
679
  cameraViewInfos.frameIdLastUpdate = frameId;
583
680
  this._canPostToWorker = false;
584
681
  if (this._worker) {
585
- const cameraViewMatrix = camera.getViewMatrix();
586
682
  this._worker.postMessage({
587
- worldMatrix: this.getWorldMatrix().m,
683
+ worldMatrix: worldMatrix.m,
588
684
  cameraForward: [cameraViewMatrix.m[2], cameraViewMatrix.m[6], cameraViewMatrix.m[10]],
589
685
  cameraPosition: [camera.globalPosition.x, camera.globalPosition.y, camera.globalPosition.z],
590
686
  depthMix: this._depthMix,
591
687
  cameraId: camera.uniqueId,
688
+ sortRequestId: cameraViewInfos.sortRequestId,
592
689
  }, [this._depthMix.buffer]);
593
690
  }
594
691
  else if (Native?.sortSplats) {
@@ -600,6 +697,7 @@ export class GaussianSplattingMeshBase extends Mesh {
600
697
  cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
601
698
  cameraViewInfos.splatIndexBufferSet = true;
602
699
  }
700
+ cameraViewInfos.sortAppliedId = cameraViewInfos.sortRequestId;
603
701
  this._canPostToWorker = true;
604
702
  this._readyToDisplay = true;
605
703
  }
@@ -650,6 +748,7 @@ export class GaussianSplattingMeshBase extends Mesh {
650
748
  mesh.setMaterialForRenderPass(renderPassId, renderPassMaterial);
651
749
  }
652
750
  const ret = mesh.render(subMesh, enableAlphaMode, effectiveMeshReplacement);
751
+ this._hasRenderedOnce = true;
653
752
  // Clean up the temporary override to avoid affecting other render passes
654
753
  if (renderPassMaterial) {
655
754
  mesh.setMaterialForRenderPass(renderPassId, undefined);
@@ -1459,6 +1558,7 @@ export class GaussianSplattingMeshBase extends Mesh {
1459
1558
  newGS._modelViewProjectionMatrix = Matrix.Identity();
1460
1559
  newGS._splatPositions = this._splatPositions;
1461
1560
  newGS._readyToDisplay = false;
1561
+ newGS._hasRenderedOnce = false;
1462
1562
  newGS._disableDepthSort = this._disableDepthSort;
1463
1563
  newGS._instantiateWorker();
1464
1564
  const binfo = this.getBoundingInfo();
@@ -1976,6 +2076,7 @@ export class GaussianSplattingMeshBase extends Mesh {
1976
2076
  }
1977
2077
  this._depthMix = e.data.depthMix;
1978
2078
  const cameraId = e.data.cameraId;
2079
+ const sortRequestId = e.data.sortRequestId;
1979
2080
  const indexMix = new Uint32Array(e.data.depthMix.buffer);
1980
2081
  if (this._splatIndex) {
1981
2082
  for (let j = 0; j < vertexCountPadded; j++) {
@@ -1997,6 +2098,7 @@ export class GaussianSplattingMeshBase extends Mesh {
1997
2098
  cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
1998
2099
  cameraViewInfos.splatIndexBufferSet = true;
1999
2100
  }
2101
+ cameraViewInfos.sortAppliedId = sortRequestId;
2000
2102
  }
2001
2103
  this._canPostToWorker = true;
2002
2104
  this._readyToDisplay = true;
@@ -2184,13 +2286,14 @@ GaussianSplattingMeshBase._CreateWorker = function (self) {
2184
2286
  // update on view changed
2185
2287
  else {
2186
2288
  const cameraId = e.data.cameraId;
2289
+ const sortRequestId = e.data.sortRequestId;
2187
2290
  const globalWorldMatrix = e.data.worldMatrix;
2188
2291
  const cameraForward = e.data.cameraForward;
2189
2292
  const cameraPosition = e.data.cameraPosition;
2190
2293
  depthMix = e.data.depthMix;
2191
2294
  if (!positions || !cameraForward) {
2192
2295
  // Sort request arrived before positions were initialized — return the buffer unchanged so the main thread can unlock _canPostToWorker.
2193
- self.postMessage({ depthMix, cameraId }, [depthMix.buffer]);
2296
+ self.postMessage({ depthMix, cameraId, sortRequestId }, [depthMix.buffer]);
2194
2297
  return;
2195
2298
  }
2196
2299
  const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
@@ -2243,7 +2346,7 @@ GaussianSplattingMeshBase._CreateWorker = function (self) {
2243
2346
  // eslint-disable-next-line no-console
2244
2347
  console.error("Gaussian splat sort worker encountered an error (will retry next frame):", sortError);
2245
2348
  }
2246
- self.postMessage({ depthMix, cameraId }, [depthMix.buffer]);
2349
+ self.postMessage({ depthMix, cameraId, sortRequestId }, [depthMix.buffer]);
2247
2350
  }
2248
2351
  };
2249
2352
  };