@babylonjs/core 9.12.0 → 9.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/Behaviors/Cameras/interpolatingBehavior.d.ts +17 -2
  2. package/Behaviors/Cameras/interpolatingBehavior.js +53 -23
  3. package/Behaviors/Cameras/interpolatingBehavior.js.map +1 -1
  4. package/Cameras/Inputs/flyCameraKeyboardInput.js +9 -1
  5. package/Cameras/Inputs/flyCameraKeyboardInput.js.map +1 -1
  6. package/Cameras/Inputs/flyCameraMouseInput.d.ts +2 -0
  7. package/Cameras/Inputs/flyCameraMouseInput.js +14 -2
  8. package/Cameras/Inputs/flyCameraMouseInput.js.map +1 -1
  9. package/Cameras/Inputs/freeCameraKeyboardMoveInput.js +25 -5
  10. package/Cameras/Inputs/freeCameraKeyboardMoveInput.js.map +1 -1
  11. package/Cameras/Inputs/freeCameraMouseInput.d.ts +14 -0
  12. package/Cameras/Inputs/freeCameraMouseInput.js +26 -4
  13. package/Cameras/Inputs/freeCameraMouseInput.js.map +1 -1
  14. package/Cameras/Inputs/freeCameraTouchInput.js +16 -5
  15. package/Cameras/Inputs/freeCameraTouchInput.js.map +1 -1
  16. package/Cameras/Inputs/geospatialCameraKeyboardInput.js +8 -1
  17. package/Cameras/Inputs/geospatialCameraKeyboardInput.js.map +1 -1
  18. package/Cameras/arcRotateCamera.pure.js.map +1 -1
  19. package/Cameras/flyCamera.pure.d.ts +8 -0
  20. package/Cameras/flyCamera.pure.js.map +1 -1
  21. package/Cameras/freeCamera.pure.d.ts +8 -0
  22. package/Cameras/freeCamera.pure.js.map +1 -1
  23. package/Cameras/geospatialCamera.pure.d.ts +30 -7
  24. package/Cameras/geospatialCamera.pure.js +39 -14
  25. package/Cameras/geospatialCamera.pure.js.map +1 -1
  26. package/Cameras/geospatialCameraMovement.js +2 -2
  27. package/Cameras/geospatialCameraMovement.js.map +1 -1
  28. package/Cameras/index.d.ts +1 -0
  29. package/Cameras/index.js +1 -0
  30. package/Cameras/index.js.map +1 -1
  31. package/Cameras/pure.d.ts +1 -0
  32. package/Cameras/pure.js +1 -0
  33. package/Cameras/pure.js.map +1 -1
  34. package/Cameras/targetCamera.pure.d.ts +25 -0
  35. package/Cameras/targetCamera.pure.js +83 -25
  36. package/Cameras/targetCamera.pure.js.map +1 -1
  37. package/Cameras/targetCameraMovement.d.ts +56 -0
  38. package/Cameras/targetCameraMovement.js +63 -0
  39. package/Cameras/targetCameraMovement.js.map +1 -0
  40. package/Collisions/gpuPicker.d.ts +111 -4
  41. package/Collisions/gpuPicker.js +673 -106
  42. package/Collisions/gpuPicker.js.map +1 -1
  43. package/Engines/AbstractEngine/abstractEngine.scissor.d.ts +18 -0
  44. package/Engines/AbstractEngine/abstractEngine.scissor.js +2 -0
  45. package/Engines/AbstractEngine/abstractEngine.scissor.js.map +1 -0
  46. package/Engines/Native/nativeInterfaces.d.ts +1 -0
  47. package/Engines/Native/nativeInterfaces.js.map +1 -1
  48. package/Engines/abstractEngine.pure.js +2 -2
  49. package/Engines/abstractEngine.pure.js.map +1 -1
  50. package/Engines/engine.d.ts +1 -0
  51. package/Engines/engine.js +1 -0
  52. package/Engines/engine.js.map +1 -1
  53. package/Engines/engine.pure.d.ts +0 -12
  54. package/Engines/engine.pure.js +0 -20
  55. package/Engines/engine.pure.js.map +1 -1
  56. package/Engines/nullEngine.pure.d.ts +66 -0
  57. package/Engines/nullEngine.pure.js +97 -0
  58. package/Engines/nullEngine.pure.js.map +1 -1
  59. package/Engines/pure.d.ts +1 -0
  60. package/Engines/pure.js +1 -0
  61. package/Engines/pure.js.map +1 -1
  62. package/Engines/thinEngine.scissor.d.ts +6 -0
  63. package/Engines/thinEngine.scissor.js +9 -0
  64. package/Engines/thinEngine.scissor.js.map +1 -0
  65. package/Engines/thinEngine.scissor.pure.d.ts +6 -0
  66. package/Engines/thinEngine.scissor.pure.js +24 -0
  67. package/Engines/thinEngine.scissor.pure.js.map +1 -0
  68. package/Engines/thinNativeEngine.d.ts +0 -1
  69. package/Engines/thinNativeEngine.js +0 -1
  70. package/Engines/thinNativeEngine.js.map +1 -1
  71. package/Engines/thinNativeEngine.pure.d.ts +3 -0
  72. package/Engines/thinNativeEngine.pure.js +50 -4
  73. package/Engines/thinNativeEngine.pure.js.map +1 -1
  74. package/Engines/webgpuEngine.pure.js.map +1 -1
  75. package/Loading/sceneLoader.d.ts +5 -1
  76. package/Loading/sceneLoader.js +298 -401
  77. package/Loading/sceneLoader.js.map +1 -1
  78. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.d.ts +15 -0
  79. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js +34 -1
  80. package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js.map +1 -1
  81. package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.d.ts +9 -0
  82. package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js +26 -2
  83. package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js.map +1 -1
  84. package/Materials/GreasedLine/greasedLinePluginMaterialShadersGLSL.js +10 -3
  85. package/Materials/GreasedLine/greasedLinePluginMaterialShadersGLSL.js.map +1 -1
  86. package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js +3 -1
  87. package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js.map +1 -1
  88. package/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.js +18 -8
  89. package/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.js.map +1 -1
  90. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.d.ts +77 -1
  91. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js +243 -104
  92. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js.map +1 -1
  93. package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.d.ts +39 -0
  94. package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js +308 -0
  95. package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js.map +1 -0
  96. package/Meshes/geometry.js +30 -15
  97. package/Meshes/geometry.js.map +1 -1
  98. package/Meshes/mesh.pure.js +26 -14
  99. package/Meshes/mesh.pure.js.map +1 -1
  100. package/Misc/tools.pure.js +1 -1
  101. package/Misc/tools.pure.js.map +1 -1
  102. package/Shaders/ShadersInclude/gaussianSplatting.js +1 -0
  103. package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
  104. package/Shaders/gaussianSplatting.fragment.js +17 -1
  105. package/Shaders/gaussianSplatting.fragment.js.map +1 -1
  106. package/Shaders/gaussianSplatting.vertex.js +1 -1
  107. package/Shaders/gaussianSplatting.vertex.js.map +1 -1
  108. package/Shaders/gaussianSplattingDepth.vertex.js +1 -1
  109. package/Shaders/gaussianSplattingDepth.vertex.js.map +1 -1
  110. package/Shaders/greasedLine.vertex.js +19 -3
  111. package/Shaders/greasedLine.vertex.js.map +1 -1
  112. package/Shaders/picking.fragment.js +39 -3
  113. package/Shaders/picking.fragment.js.map +1 -1
  114. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +3 -1
  115. package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
  116. package/ShadersWGSL/gaussianSplatting.fragment.js +14 -1
  117. package/ShadersWGSL/gaussianSplatting.fragment.js.map +1 -1
  118. package/ShadersWGSL/gaussianSplatting.vertex.js +2 -2
  119. package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
  120. package/ShadersWGSL/gaussianSplattingDepth.vertex.js +2 -2
  121. package/ShadersWGSL/gaussianSplattingDepth.vertex.js.map +1 -1
  122. package/ShadersWGSL/picking.fragment.js +22 -1
  123. package/ShadersWGSL/picking.fragment.js.map +1 -1
  124. package/package.json +3 -2
  125. package/scene.pure.d.ts +12 -0
  126. package/scene.pure.js +36 -0
  127. package/scene.pure.js.map +1 -1
@@ -0,0 +1,39 @@
1
+ /** This file must only contain pure code and pure imports */
2
+ /**
3
+ * Message commands exchanged with the Gaussian Splatting depth-sort worker. Every message carries a
4
+ * `command` field naming the intended work, instead of inferring it from which payload is present.
5
+ */
6
+ export declare const GaussianSplattingSortWorkerCommand: {
7
+ /** Main -> worker: set the source splat centers (stride 4: xyz + 1). */
8
+ readonly POSITIONS: "positions";
9
+ /** Main -> worker: patch a contiguous sub-range of the existing source splat centers in place. */
10
+ readonly POSITIONS_UPDATE: "positionsUpdate";
11
+ /** Main -> worker: set the compound-mesh rig node matrices. */
12
+ readonly PART_MATRICES: "partMatrices";
13
+ /** Main -> worker: set the compound-mesh per-splat rig node indices. */
14
+ readonly PART_INDICES: "partIndices";
15
+ /** Main -> worker: set the active source-splat ranges (flat [start0, count0, ...]). */
16
+ readonly INTERVALS: "intervals";
17
+ /** Main -> worker: sort the active splats for a camera view. */
18
+ readonly SORT: "sort";
19
+ /** Worker -> main: a completed sort result. */
20
+ readonly SORTED: "sorted";
21
+ };
22
+ /**
23
+ * Depth-sort web worker body for Gaussian Splatting meshes.
24
+ *
25
+ * The function is self-contained: it is serialized with `Function.prototype.toString()` and run
26
+ * inside a `Blob`-backed `Worker`, so it must not reference anything from its enclosing module
27
+ * (including {@link GaussianSplattingSortWorkerCommand} — the command literals are duplicated here).
28
+ *
29
+ * The intended work for each message is selected explicitly via its `command` field:
30
+ * - `positions` set source splat centers (stride 4: xyz + 1).
31
+ * - `partMatrices` set compound-mesh rig node matrices.
32
+ * - `partIndices` set compound-mesh per-splat rig node indices.
33
+ * - `intervals` set active source-splat ranges (flat [start0, count0, ...]); persisted across sorts.
34
+ * - `sort` sort the active splats for `{ worldMatrix, cameraForward, cameraPosition, depthMix,
35
+ * cameraId, sortRequestId }` and post back a `sorted` result.
36
+ *
37
+ * @param self - the worker global scope
38
+ */
39
+ export declare const GaussianSplattingSortWorker: (self: Worker) => void;
@@ -0,0 +1,308 @@
1
+ /** This file must only contain pure code and pure imports */
2
+ /**
3
+ * Message commands exchanged with the Gaussian Splatting depth-sort worker. Every message carries a
4
+ * `command` field naming the intended work, instead of inferring it from which payload is present.
5
+ */
6
+ export const GaussianSplattingSortWorkerCommand = {
7
+ /** Main -> worker: set the source splat centers (stride 4: xyz + 1). */
8
+ POSITIONS: "positions",
9
+ /** Main -> worker: patch a contiguous sub-range of the existing source splat centers in place. */
10
+ POSITIONS_UPDATE: "positionsUpdate",
11
+ /** Main -> worker: set the compound-mesh rig node matrices. */
12
+ PART_MATRICES: "partMatrices",
13
+ /** Main -> worker: set the compound-mesh per-splat rig node indices. */
14
+ PART_INDICES: "partIndices",
15
+ /** Main -> worker: set the active source-splat ranges (flat [start0, count0, ...]). */
16
+ INTERVALS: "intervals",
17
+ /** Main -> worker: sort the active splats for a camera view. */
18
+ SORT: "sort",
19
+ /** Worker -> main: a completed sort result. */
20
+ SORTED: "sorted",
21
+ };
22
+ /**
23
+ * Depth-sort web worker body for Gaussian Splatting meshes.
24
+ *
25
+ * The function is self-contained: it is serialized with `Function.prototype.toString()` and run
26
+ * inside a `Blob`-backed `Worker`, so it must not reference anything from its enclosing module
27
+ * (including {@link GaussianSplattingSortWorkerCommand} — the command literals are duplicated here).
28
+ *
29
+ * The intended work for each message is selected explicitly via its `command` field:
30
+ * - `positions` set source splat centers (stride 4: xyz + 1).
31
+ * - `partMatrices` set compound-mesh rig node matrices.
32
+ * - `partIndices` set compound-mesh per-splat rig node indices.
33
+ * - `intervals` set active source-splat ranges (flat [start0, count0, ...]); persisted across sorts.
34
+ * - `sort` sort the active splats for `{ worldMatrix, cameraForward, cameraPosition, depthMix,
35
+ * cameraId, sortRequestId }` and post back a `sorted` result.
36
+ *
37
+ * @param self - the worker global scope
38
+ */
39
+ export const GaussianSplattingSortWorker = function (self) {
40
+ let positions;
41
+ let depthMix;
42
+ let partIndices;
43
+ let partMatrices;
44
+ // Active source-splat intervals as flat [start0, count0, start1, count1, ...]. Persisted between
45
+ // sort requests and refreshed by the main thread via the `intervals` message.
46
+ let intervals = null;
47
+ // Reusable counting-sort scratch buffers (grown on demand, never per-sort allocations).
48
+ let sortSourceIndices; // active source splat index for each compact slot
49
+ let sortDepths; // view-space depth for each compact slot
50
+ let sortKeys; // depth-derived integer bucket key for each compact slot
51
+ let sortCounts; // counting-sort histogram (one entry per bucket)
52
+ self.onmessage = (e) => {
53
+ // The intended work is selected explicitly via the message `command` field. These string
54
+ // literals must match GaussianSplattingSortWorkerCommand above (this body is serialized in
55
+ // isolation and cannot reference the shared constant).
56
+ const command = e.data.command;
57
+ if (command === "positions") {
58
+ positions = e.data.positions;
59
+ }
60
+ else if (command === "positionsUpdate") {
61
+ // Patch only the changed sub-range in place, avoiding a full re-copy/transfer of the entire
62
+ // (potentially hundreds-of-MB) position buffer on every streamed LOD decode.
63
+ if (positions && e.data.data) {
64
+ positions.set(e.data.data, e.data.offset);
65
+ }
66
+ }
67
+ else if (command === "partMatrices") {
68
+ partMatrices = e.data.partMatrices;
69
+ }
70
+ else if (command === "partIndices") {
71
+ partIndices = e.data.partIndices;
72
+ }
73
+ else if (command === "intervals") {
74
+ intervals = e.data.intervals;
75
+ }
76
+ else if (command === "sort") {
77
+ const cameraId = e.data.cameraId;
78
+ const sortRequestId = e.data.sortRequestId;
79
+ const rangeVersion = e.data.rangeVersion;
80
+ const globalWorldMatrix = e.data.worldMatrix;
81
+ const cameraForward = e.data.cameraForward;
82
+ const cameraPosition = e.data.cameraPosition;
83
+ depthMix = e.data.depthMix;
84
+ if (!positions || !cameraForward) {
85
+ // Sort request arrived before positions were initialized — return the buffer unchanged so the main thread can unlock _canPostToWorker.
86
+ self.postMessage({ command: "sorted", depthMix, cameraId, sortRequestId, rangeVersion }, [depthMix.buffer]);
87
+ return;
88
+ }
89
+ // Feature flags are forwarded by the main thread on each sort request (the worker body is
90
+ // serialized in isolation and cannot read shared statics directly). useCountingSort defaults to
91
+ // true; set it false to fall back to the legacy comparison sort.
92
+ const useCountingSort = e.data.useCountingSort !== false;
93
+ const logSortPerformance = !!e.data.logSortPerformance;
94
+ // Resolve the active interval set. The main thread always sends an `intervals` message,
95
+ // but fall back to "all source splats" if a sort somehow arrives before it.
96
+ const activeIntervals = intervals;
97
+ const totalSplats = positions.length / 4;
98
+ let renderSplatCount = totalSplats;
99
+ if (activeIntervals) {
100
+ renderSplatCount = 0;
101
+ for (let rangeIndex = 1; rangeIndex < activeIntervals.length; rangeIndex += 2) {
102
+ renderSplatCount += activeIntervals[rangeIndex];
103
+ }
104
+ }
105
+ const vertexCountPadded = Math.max((renderSplatCount + 15) & ~0xf, 16);
106
+ // depth = dot(cameraForward, worldPos - cameraPos)
107
+ const camDot = cameraForward[0] * cameraPosition[0] + cameraForward[1] * cameraPosition[1] + cameraForward[2] * cameraPosition[2];
108
+ const computeDepthCoeffs = (m) => {
109
+ return [
110
+ cameraForward[0] * m[0] + cameraForward[1] * m[1] + cameraForward[2] * m[2],
111
+ cameraForward[0] * m[4] + cameraForward[1] * m[5] + cameraForward[2] * m[6],
112
+ cameraForward[0] * m[8] + cameraForward[1] * m[9] + cameraForward[2] * m[10],
113
+ cameraForward[0] * m[12] + cameraForward[1] * m[13] + cameraForward[2] * m[14] - camDot,
114
+ ];
115
+ };
116
+ // Optional, flag-gated sort timing (a simple wall-clock delta logged to the worker console).
117
+ const perf = globalThis.performance;
118
+ const sortStart = logSortPerformance && perf ? perf.now() : 0;
119
+ try {
120
+ if (useCountingSort) {
121
+ // ===== Fast O(n) counting (radix) sort =====
122
+ // Output view: the even 32-bit words of the depthMix buffer hold the sorted source indices
123
+ // (the main thread reads indexMix[2*j]); the odd words are left unused.
124
+ const outIndices = new Uint32Array(depthMix.buffer);
125
+ // Grow the reusable scratch buffers when the active count increases (kept across sorts).
126
+ if (!sortSourceIndices || sortSourceIndices.length < renderSplatCount) {
127
+ const capacity = Math.max(renderSplatCount, 16);
128
+ sortSourceIndices = new Uint32Array(capacity);
129
+ sortDepths = new Float32Array(capacity);
130
+ sortKeys = new Uint32Array(capacity);
131
+ }
132
+ // Compound (rig) meshes give each splat its own world transform via its part: depth uses the
133
+ // splat's part coefficients. Single meshes use the one global world matrix. Both produce
134
+ // depths in the same camera-forward space, so all active splats sort together correctly.
135
+ const compound = !!(partMatrices && partIndices);
136
+ let depthCoeffs = [];
137
+ let partLen = 0;
138
+ let a = 0;
139
+ let b = 0;
140
+ let c = 0;
141
+ let d = 0;
142
+ if (compound) {
143
+ depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));
144
+ partLen = partIndices.length;
145
+ }
146
+ else {
147
+ const coeff = computeDepthCoeffs(globalWorldMatrix);
148
+ a = coeff[0];
149
+ b = coeff[1];
150
+ c = coeff[2];
151
+ d = coeff[3];
152
+ }
153
+ // Pass 1: gather the active source indices, compute each splat's view-space depth (signed
154
+ // distance from the camera along the forward axis), and track the depth range. The compound
155
+ // branch is hoisted out of the inner loop so the hot path stays straight-line.
156
+ let writeIndex = 0;
157
+ let minDepth = Infinity;
158
+ let maxDepth = -Infinity;
159
+ const rangeWords = activeIntervals ? activeIntervals.length : 2;
160
+ for (let r = 0; r < rangeWords; r += 2) {
161
+ const start = activeIntervals ? activeIntervals[r] : 0;
162
+ const end = start + (activeIntervals ? activeIntervals[r + 1] : totalSplats);
163
+ if (compound) {
164
+ for (let sourceIndex = start; sourceIndex < end; sourceIndex++) {
165
+ const o = 4 * sourceIndex;
166
+ const coeff = depthCoeffs[partIndices[sourceIndex < partLen ? sourceIndex : partLen - 1]];
167
+ const depth = coeff[0] * positions[o] + coeff[1] * positions[o + 1] + coeff[2] * positions[o + 2] + coeff[3];
168
+ sortSourceIndices[writeIndex] = sourceIndex;
169
+ sortDepths[writeIndex] = depth;
170
+ writeIndex++;
171
+ if (depth < minDepth) {
172
+ minDepth = depth;
173
+ }
174
+ if (depth > maxDepth) {
175
+ maxDepth = depth;
176
+ }
177
+ }
178
+ }
179
+ else {
180
+ for (let sourceIndex = start; sourceIndex < end; sourceIndex++) {
181
+ const o = 4 * sourceIndex;
182
+ const depth = a * positions[o] + b * positions[o + 1] + c * positions[o + 2] + d;
183
+ sortSourceIndices[writeIndex] = sourceIndex;
184
+ sortDepths[writeIndex] = depth;
185
+ writeIndex++;
186
+ if (depth < minDepth) {
187
+ minDepth = depth;
188
+ }
189
+ if (depth > maxDepth) {
190
+ maxDepth = depth;
191
+ }
192
+ }
193
+ }
194
+ }
195
+ // Counting (radix) sort by a depth-derived integer key — O(n) vs the legacy comparison sort.
196
+ // Bucket bit depth follows PlayCanvas: round(log2(n/4)) clamped to [10, 20]. The farthest splat
197
+ // gets key 0 so the ascending counting sort yields back-to-front order for alpha blending.
198
+ const compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(Math.max(1, renderSplatCount) / 4))));
199
+ const bucketCount = 2 ** compareBits + 1;
200
+ if (!sortCounts || sortCounts.length !== bucketCount) {
201
+ sortCounts = new Uint32Array(bucketCount);
202
+ }
203
+ else {
204
+ sortCounts.fill(0);
205
+ }
206
+ const range = maxDepth - minDepth;
207
+ if (!(range > 1e-12)) {
208
+ // All active splats share a depth (or none are active): identity order, single bucket.
209
+ for (let k = 0; k < renderSplatCount; k++) {
210
+ sortKeys[k] = 0;
211
+ }
212
+ sortCounts[0] = renderSplatCount;
213
+ }
214
+ else {
215
+ const scale = (bucketCount - 1) / range;
216
+ const maxKey = bucketCount - 1;
217
+ for (let k = 0; k < renderSplatCount; k++) {
218
+ // maxDepth -> 0 (rendered first), minDepth -> bucketCount-1 (rendered last). Clamp
219
+ // against float rounding (a tiny `range` makes `scale` huge), which could otherwise
220
+ // produce key >= bucketCount and corrupt the histogram with out-of-bounds writes.
221
+ let key = ((maxDepth - sortDepths[k]) * scale) >>> 0;
222
+ if (key > maxKey) {
223
+ key = maxKey;
224
+ }
225
+ sortKeys[k] = key;
226
+ sortCounts[key]++;
227
+ }
228
+ }
229
+ // Prefix-sum the histogram into bucket end offsets, then scatter source indices into place.
230
+ for (let i = 1; i < bucketCount; i++) {
231
+ sortCounts[i] += sortCounts[i - 1];
232
+ }
233
+ for (let k = 0; k < renderSplatCount; k++) {
234
+ const dest = --sortCounts[sortKeys[k]];
235
+ outIndices[2 * dest] = sortSourceIndices[k];
236
+ }
237
+ // Pad the remainder up to the 16-multiple with the reserved (invisible) splat index 0.
238
+ for (let dest = renderSplatCount; dest < vertexCountPadded; dest++) {
239
+ outIndices[2 * dest] = 0;
240
+ }
241
+ }
242
+ else {
243
+ // ===== Legacy comparison sort: pack (index, ~depthBits) into each 64-bit lane and sort it =====
244
+ const indices = new Uint32Array(depthMix.buffer);
245
+ const floatMix = new Float32Array(depthMix.buffer);
246
+ // Build the compact list of source-splat indices to sort.
247
+ if (activeIntervals) {
248
+ let writeIndex = 0;
249
+ for (let rangeIndex = 0; rangeIndex < activeIntervals.length; rangeIndex += 2) {
250
+ const start = activeIntervals[rangeIndex];
251
+ const count = activeIntervals[rangeIndex + 1];
252
+ for (let sourceIndex = start; sourceIndex < start + count; sourceIndex++) {
253
+ indices[2 * writeIndex++] = sourceIndex;
254
+ }
255
+ }
256
+ // Pad up to a multiple of 16 with the reserved (invisible) splat index 0.
257
+ for (; writeIndex < vertexCountPadded; writeIndex++) {
258
+ indices[2 * writeIndex] = 0;
259
+ }
260
+ }
261
+ else {
262
+ for (let j = 0; j < vertexCountPadded; j++) {
263
+ indices[2 * j] = j;
264
+ }
265
+ }
266
+ if (partMatrices && partIndices) {
267
+ // Precompute depth coefficients for each rig node.
268
+ const depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));
269
+ // NB: For performance reasons, we assume that part indices are valid.
270
+ const length = partIndices.length;
271
+ for (let j = 0; j < vertexCountPadded; j++) {
272
+ const sourceIndex = indices[2 * j];
273
+ // NB: We need this 'min' because the vertex array is padded, not partIndices.
274
+ const partIndex = partIndices[Math.min(sourceIndex, length - 1)];
275
+ const coeff = depthCoeffs[partIndex];
276
+ floatMix[2 * j + 1] =
277
+ coeff[0] * positions[4 * sourceIndex + 0] + coeff[1] * positions[4 * sourceIndex + 1] + coeff[2] * positions[4 * sourceIndex + 2] + coeff[3];
278
+ // Invert the depth bits (bitwise not) so the ascending sort renders back to front.
279
+ indices[2 * j + 1] = ~indices[2 * j + 1];
280
+ }
281
+ }
282
+ else {
283
+ const [a, b, c, d] = computeDepthCoeffs(globalWorldMatrix);
284
+ for (let j = 0; j < vertexCountPadded; j++) {
285
+ const sourceIndex = indices[2 * j];
286
+ floatMix[2 * j + 1] = a * positions[4 * sourceIndex + 0] + b * positions[4 * sourceIndex + 1] + c * positions[4 * sourceIndex + 2] + d;
287
+ indices[2 * j + 1] = ~indices[2 * j + 1];
288
+ }
289
+ }
290
+ depthMix.sort();
291
+ }
292
+ }
293
+ catch (sortError) {
294
+ // Transient data inconsistency (e.g. partIndices/partMatrices mismatch during addPart/removePart rebuild).
295
+ // Return the buffer unsorted so the main thread can unlock _canPostToWorker and retry next frame.
296
+ // Logger is unavailable inside the worker — console is the only option.
297
+ // eslint-disable-next-line no-console
298
+ console.error("Gaussian splat sort worker encountered an error (will retry next frame):", sortError);
299
+ }
300
+ if (logSortPerformance && perf) {
301
+ // eslint-disable-next-line no-console
302
+ console.log(`[GaussianSplatting] ${useCountingSort ? "counting" : "legacy"} sort: ${renderSplatCount} splats in ${(perf.now() - sortStart).toFixed(2)}ms`);
303
+ }
304
+ self.postMessage({ command: "sorted", depthMix, cameraId, sortRequestId, rangeVersion }, [depthMix.buffer]);
305
+ }
306
+ };
307
+ };
308
+ //# sourceMappingURL=gaussianSplattingSortWorker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaussianSplattingSortWorker.js","sourceRoot":"","sources":["../../../../../dev/core/src/Meshes/GaussianSplatting/gaussianSplattingSortWorker.ts"],"names":[],"mappings":"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","sourcesContent":["/** This file must only contain pure code and pure imports */\r\n\r\nimport { type Nullable } from \"core/types\";\r\n\r\n/**\r\n * Message commands exchanged with the Gaussian Splatting depth-sort worker. Every message carries a\r\n * `command` field naming the intended work, instead of inferring it from which payload is present.\r\n */\r\nexport const GaussianSplattingSortWorkerCommand = {\r\n /** Main -> worker: set the source splat centers (stride 4: xyz + 1). */\r\n POSITIONS: \"positions\",\r\n /** Main -> worker: patch a contiguous sub-range of the existing source splat centers in place. */\r\n POSITIONS_UPDATE: \"positionsUpdate\",\r\n /** Main -> worker: set the compound-mesh rig node matrices. */\r\n PART_MATRICES: \"partMatrices\",\r\n /** Main -> worker: set the compound-mesh per-splat rig node indices. */\r\n PART_INDICES: \"partIndices\",\r\n /** Main -> worker: set the active source-splat ranges (flat [start0, count0, ...]). */\r\n INTERVALS: \"intervals\",\r\n /** Main -> worker: sort the active splats for a camera view. */\r\n SORT: \"sort\",\r\n /** Worker -> main: a completed sort result. */\r\n SORTED: \"sorted\",\r\n} as const;\r\n\r\n/**\r\n * Depth-sort web worker body for Gaussian Splatting meshes.\r\n *\r\n * The function is self-contained: it is serialized with `Function.prototype.toString()` and run\r\n * inside a `Blob`-backed `Worker`, so it must not reference anything from its enclosing module\r\n * (including {@link GaussianSplattingSortWorkerCommand} — the command literals are duplicated here).\r\n *\r\n * The intended work for each message is selected explicitly via its `command` field:\r\n * - `positions` set source splat centers (stride 4: xyz + 1).\r\n * - `partMatrices` set compound-mesh rig node matrices.\r\n * - `partIndices` set compound-mesh per-splat rig node indices.\r\n * - `intervals` set active source-splat ranges (flat [start0, count0, ...]); persisted across sorts.\r\n * - `sort` sort the active splats for `{ worldMatrix, cameraForward, cameraPosition, depthMix,\r\n * cameraId, sortRequestId }` and post back a `sorted` result.\r\n *\r\n * @param self - the worker global scope\r\n */\r\nexport const GaussianSplattingSortWorker = function (self: Worker) {\r\n let positions: Float32Array;\r\n let depthMix: BigInt64Array;\r\n let partIndices: Uint8Array;\r\n let partMatrices: Float32Array[];\r\n // Active source-splat intervals as flat [start0, count0, start1, count1, ...]. Persisted between\r\n // sort requests and refreshed by the main thread via the `intervals` message.\r\n let intervals: Nullable<Uint32Array> = null;\r\n // Reusable counting-sort scratch buffers (grown on demand, never per-sort allocations).\r\n let sortSourceIndices: Uint32Array; // active source splat index for each compact slot\r\n let sortDepths: Float32Array; // view-space depth for each compact slot\r\n let sortKeys: Uint32Array; // depth-derived integer bucket key for each compact slot\r\n let sortCounts: Uint32Array; // counting-sort histogram (one entry per bucket)\r\n\r\n self.onmessage = (e: any) => {\r\n // The intended work is selected explicitly via the message `command` field. These string\r\n // literals must match GaussianSplattingSortWorkerCommand above (this body is serialized in\r\n // isolation and cannot reference the shared constant).\r\n const command = e.data.command;\r\n if (command === \"positions\") {\r\n positions = e.data.positions;\r\n } else if (command === \"positionsUpdate\") {\r\n // Patch only the changed sub-range in place, avoiding a full re-copy/transfer of the entire\r\n // (potentially hundreds-of-MB) position buffer on every streamed LOD decode.\r\n if (positions && e.data.data) {\r\n positions.set(e.data.data, e.data.offset);\r\n }\r\n } else if (command === \"partMatrices\") {\r\n partMatrices = e.data.partMatrices;\r\n } else if (command === \"partIndices\") {\r\n partIndices = e.data.partIndices;\r\n } else if (command === \"intervals\") {\r\n intervals = e.data.intervals;\r\n } else if (command === \"sort\") {\r\n const cameraId = e.data.cameraId;\r\n const sortRequestId = e.data.sortRequestId;\r\n const rangeVersion = e.data.rangeVersion;\r\n const globalWorldMatrix = e.data.worldMatrix;\r\n const cameraForward = e.data.cameraForward;\r\n const cameraPosition = e.data.cameraPosition;\r\n\r\n depthMix = e.data.depthMix;\r\n\r\n if (!positions || !cameraForward) {\r\n // Sort request arrived before positions were initialized — return the buffer unchanged so the main thread can unlock _canPostToWorker.\r\n self.postMessage({ command: \"sorted\", depthMix, cameraId, sortRequestId, rangeVersion }, [depthMix.buffer]);\r\n return;\r\n }\r\n\r\n // Feature flags are forwarded by the main thread on each sort request (the worker body is\r\n // serialized in isolation and cannot read shared statics directly). useCountingSort defaults to\r\n // true; set it false to fall back to the legacy comparison sort.\r\n const useCountingSort = e.data.useCountingSort !== false;\r\n const logSortPerformance = !!e.data.logSortPerformance;\r\n\r\n // Resolve the active interval set. The main thread always sends an `intervals` message,\r\n // but fall back to \"all source splats\" if a sort somehow arrives before it.\r\n const activeIntervals = intervals;\r\n const totalSplats = positions.length / 4;\r\n let renderSplatCount = totalSplats;\r\n if (activeIntervals) {\r\n renderSplatCount = 0;\r\n for (let rangeIndex = 1; rangeIndex < activeIntervals.length; rangeIndex += 2) {\r\n renderSplatCount += activeIntervals[rangeIndex];\r\n }\r\n }\r\n const vertexCountPadded = Math.max((renderSplatCount + 15) & ~0xf, 16);\r\n\r\n // depth = dot(cameraForward, worldPos - cameraPos)\r\n const camDot = cameraForward[0] * cameraPosition[0] + cameraForward[1] * cameraPosition[1] + cameraForward[2] * cameraPosition[2];\r\n\r\n const computeDepthCoeffs = (m: Float32Array): number[] => {\r\n return [\r\n cameraForward[0] * m[0] + cameraForward[1] * m[1] + cameraForward[2] * m[2],\r\n cameraForward[0] * m[4] + cameraForward[1] * m[5] + cameraForward[2] * m[6],\r\n cameraForward[0] * m[8] + cameraForward[1] * m[9] + cameraForward[2] * m[10],\r\n cameraForward[0] * m[12] + cameraForward[1] * m[13] + cameraForward[2] * m[14] - camDot,\r\n ];\r\n };\r\n\r\n // Optional, flag-gated sort timing (a simple wall-clock delta logged to the worker console).\r\n const perf = (globalThis as { performance?: { now(): number } }).performance;\r\n const sortStart = logSortPerformance && perf ? perf.now() : 0;\r\n\r\n try {\r\n if (useCountingSort) {\r\n // ===== Fast O(n) counting (radix) sort =====\r\n // Output view: the even 32-bit words of the depthMix buffer hold the sorted source indices\r\n // (the main thread reads indexMix[2*j]); the odd words are left unused.\r\n const outIndices = new Uint32Array(depthMix.buffer);\r\n\r\n // Grow the reusable scratch buffers when the active count increases (kept across sorts).\r\n if (!sortSourceIndices || sortSourceIndices.length < renderSplatCount) {\r\n const capacity = Math.max(renderSplatCount, 16);\r\n sortSourceIndices = new Uint32Array(capacity);\r\n sortDepths = new Float32Array(capacity);\r\n sortKeys = new Uint32Array(capacity);\r\n }\r\n\r\n // Compound (rig) meshes give each splat its own world transform via its part: depth uses the\r\n // splat's part coefficients. Single meshes use the one global world matrix. Both produce\r\n // depths in the same camera-forward space, so all active splats sort together correctly.\r\n const compound = !!(partMatrices && partIndices);\r\n let depthCoeffs: number[][] = [];\r\n let partLen = 0;\r\n let a = 0;\r\n let b = 0;\r\n let c = 0;\r\n let d = 0;\r\n if (compound) {\r\n depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));\r\n partLen = partIndices.length;\r\n } else {\r\n const coeff = computeDepthCoeffs(globalWorldMatrix);\r\n a = coeff[0];\r\n b = coeff[1];\r\n c = coeff[2];\r\n d = coeff[3];\r\n }\r\n\r\n // Pass 1: gather the active source indices, compute each splat's view-space depth (signed\r\n // distance from the camera along the forward axis), and track the depth range. The compound\r\n // branch is hoisted out of the inner loop so the hot path stays straight-line.\r\n let writeIndex = 0;\r\n let minDepth = Infinity;\r\n let maxDepth = -Infinity;\r\n const rangeWords = activeIntervals ? activeIntervals.length : 2;\r\n for (let r = 0; r < rangeWords; r += 2) {\r\n const start = activeIntervals ? activeIntervals[r] : 0;\r\n const end = start + (activeIntervals ? activeIntervals[r + 1] : totalSplats);\r\n if (compound) {\r\n for (let sourceIndex = start; sourceIndex < end; sourceIndex++) {\r\n const o = 4 * sourceIndex;\r\n const coeff = depthCoeffs[partIndices[sourceIndex < partLen ? sourceIndex : partLen - 1]];\r\n const depth = coeff[0] * positions[o] + coeff[1] * positions[o + 1] + coeff[2] * positions[o + 2] + coeff[3];\r\n sortSourceIndices[writeIndex] = sourceIndex;\r\n sortDepths[writeIndex] = depth;\r\n writeIndex++;\r\n if (depth < minDepth) {\r\n minDepth = depth;\r\n }\r\n if (depth > maxDepth) {\r\n maxDepth = depth;\r\n }\r\n }\r\n } else {\r\n for (let sourceIndex = start; sourceIndex < end; sourceIndex++) {\r\n const o = 4 * sourceIndex;\r\n const depth = a * positions[o] + b * positions[o + 1] + c * positions[o + 2] + d;\r\n sortSourceIndices[writeIndex] = sourceIndex;\r\n sortDepths[writeIndex] = depth;\r\n writeIndex++;\r\n if (depth < minDepth) {\r\n minDepth = depth;\r\n }\r\n if (depth > maxDepth) {\r\n maxDepth = depth;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Counting (radix) sort by a depth-derived integer key — O(n) vs the legacy comparison sort.\r\n // Bucket bit depth follows PlayCanvas: round(log2(n/4)) clamped to [10, 20]. The farthest splat\r\n // gets key 0 so the ascending counting sort yields back-to-front order for alpha blending.\r\n const compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(Math.max(1, renderSplatCount) / 4))));\r\n const bucketCount = 2 ** compareBits + 1;\r\n if (!sortCounts || sortCounts.length !== bucketCount) {\r\n sortCounts = new Uint32Array(bucketCount);\r\n } else {\r\n sortCounts.fill(0);\r\n }\r\n\r\n const range = maxDepth - minDepth;\r\n if (!(range > 1e-12)) {\r\n // All active splats share a depth (or none are active): identity order, single bucket.\r\n for (let k = 0; k < renderSplatCount; k++) {\r\n sortKeys[k] = 0;\r\n }\r\n sortCounts[0] = renderSplatCount;\r\n } else {\r\n const scale = (bucketCount - 1) / range;\r\n const maxKey = bucketCount - 1;\r\n for (let k = 0; k < renderSplatCount; k++) {\r\n // maxDepth -> 0 (rendered first), minDepth -> bucketCount-1 (rendered last). Clamp\r\n // against float rounding (a tiny `range` makes `scale` huge), which could otherwise\r\n // produce key >= bucketCount and corrupt the histogram with out-of-bounds writes.\r\n let key = ((maxDepth - sortDepths[k]) * scale) >>> 0;\r\n if (key > maxKey) {\r\n key = maxKey;\r\n }\r\n sortKeys[k] = key;\r\n sortCounts[key]++;\r\n }\r\n }\r\n\r\n // Prefix-sum the histogram into bucket end offsets, then scatter source indices into place.\r\n for (let i = 1; i < bucketCount; i++) {\r\n sortCounts[i] += sortCounts[i - 1];\r\n }\r\n for (let k = 0; k < renderSplatCount; k++) {\r\n const dest = --sortCounts[sortKeys[k]];\r\n outIndices[2 * dest] = sortSourceIndices[k];\r\n }\r\n\r\n // Pad the remainder up to the 16-multiple with the reserved (invisible) splat index 0.\r\n for (let dest = renderSplatCount; dest < vertexCountPadded; dest++) {\r\n outIndices[2 * dest] = 0;\r\n }\r\n } else {\r\n // ===== Legacy comparison sort: pack (index, ~depthBits) into each 64-bit lane and sort it =====\r\n const indices = new Uint32Array(depthMix.buffer);\r\n const floatMix = new Float32Array(depthMix.buffer);\r\n\r\n // Build the compact list of source-splat indices to sort.\r\n if (activeIntervals) {\r\n let writeIndex = 0;\r\n for (let rangeIndex = 0; rangeIndex < activeIntervals.length; rangeIndex += 2) {\r\n const start = activeIntervals[rangeIndex];\r\n const count = activeIntervals[rangeIndex + 1];\r\n for (let sourceIndex = start; sourceIndex < start + count; sourceIndex++) {\r\n indices[2 * writeIndex++] = sourceIndex;\r\n }\r\n }\r\n // Pad up to a multiple of 16 with the reserved (invisible) splat index 0.\r\n for (; writeIndex < vertexCountPadded; writeIndex++) {\r\n indices[2 * writeIndex] = 0;\r\n }\r\n } else {\r\n for (let j = 0; j < vertexCountPadded; j++) {\r\n indices[2 * j] = j;\r\n }\r\n }\r\n\r\n if (partMatrices && partIndices) {\r\n // Precompute depth coefficients for each rig node.\r\n const depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));\r\n // NB: For performance reasons, we assume that part indices are valid.\r\n const length = partIndices.length;\r\n for (let j = 0; j < vertexCountPadded; j++) {\r\n const sourceIndex = indices[2 * j];\r\n // NB: We need this 'min' because the vertex array is padded, not partIndices.\r\n const partIndex = partIndices[Math.min(sourceIndex, length - 1)];\r\n const coeff = depthCoeffs[partIndex];\r\n floatMix[2 * j + 1] =\r\n coeff[0] * positions[4 * sourceIndex + 0] + coeff[1] * positions[4 * sourceIndex + 1] + coeff[2] * positions[4 * sourceIndex + 2] + coeff[3];\r\n // Invert the depth bits (bitwise not) so the ascending sort renders back to front.\r\n indices[2 * j + 1] = ~indices[2 * j + 1];\r\n }\r\n } else {\r\n const [a, b, c, d] = computeDepthCoeffs(globalWorldMatrix);\r\n for (let j = 0; j < vertexCountPadded; j++) {\r\n const sourceIndex = indices[2 * j];\r\n floatMix[2 * j + 1] = a * positions[4 * sourceIndex + 0] + b * positions[4 * sourceIndex + 1] + c * positions[4 * sourceIndex + 2] + d;\r\n indices[2 * j + 1] = ~indices[2 * j + 1];\r\n }\r\n }\r\n\r\n depthMix.sort();\r\n }\r\n } catch (sortError) {\r\n // Transient data inconsistency (e.g. partIndices/partMatrices mismatch during addPart/removePart rebuild).\r\n // Return the buffer unsorted so the main thread can unlock _canPostToWorker and retry next frame.\r\n // Logger is unavailable inside the worker — console is the only option.\r\n // eslint-disable-next-line no-console\r\n console.error(\"Gaussian splat sort worker encountered an error (will retry next frame):\", sortError);\r\n }\r\n\r\n if (logSortPerformance && perf) {\r\n // eslint-disable-next-line no-console\r\n console.log(`[GaussianSplatting] ${useCountingSort ? \"counting\" : \"legacy\"} sort: ${renderSplatCount} splats in ${(perf.now() - sortStart).toFixed(2)}ms`);\r\n }\r\n\r\n self.postMessage({ command: \"sorted\", depthMix, cameraId, sortRequestId, rangeVersion }, [depthMix.buffer]);\r\n }\r\n };\r\n};\r\n"]}
@@ -7,6 +7,7 @@ import { SceneLoaderFlags } from "../Loading/sceneLoaderFlags.js";
7
7
  import { BoundingInfo } from "../Culling/boundingInfo.js";
8
8
 
9
9
  import { Tools } from "../Misc/tools.pure.js";
10
+ import { Logger } from "../Misc/logger.js";
10
11
  import { Tags } from "../Misc/tags.js";
11
12
  import { extractMinAndMax } from "../Maths/math.functions.js";
12
13
  import { EngineStore } from "../Engines/engineStore.js";
@@ -682,23 +683,37 @@ export class Geometry {
682
683
  return;
683
684
  }
684
685
  scene.addPendingData(this);
685
- scene._loadFile(this.delayLoadingFile, (data) => {
686
- if (!this._delayLoadingFunction) {
687
- return;
688
- }
689
- this._delayLoadingFunction(JSON.parse(data), this);
690
- this.delayLoadState = 1;
691
- this._delayInfo = [];
692
- scene.removePendingData(this);
693
- const meshes = this._meshes;
694
- const numOfMeshes = meshes.length;
695
- for (let index = 0; index < numOfMeshes; index++) {
696
- this._applyToMesh(meshes[index]);
686
+ void (async () => {
687
+ try {
688
+ const data = await scene._loadDelayedFileAsync(this.delayLoadingFile, false, true);
689
+ if (!this._delayLoadingFunction) {
690
+ // The geometry was disposed while the load was in flight; still release the pending data so the scene can become ready.
691
+ scene.removePendingData(this);
692
+ return;
693
+ }
694
+ this._delayLoadingFunction(JSON.parse(data), this);
695
+ this.delayLoadState = 1;
696
+ this._delayInfo = [];
697
+ scene.removePendingData(this);
698
+ const meshes = this._meshes;
699
+ const numOfMeshes = meshes.length;
700
+ for (let index = 0; index < numOfMeshes; index++) {
701
+ this._applyToMesh(meshes[index]);
702
+ }
703
+ if (onLoaded) {
704
+ onLoaded();
705
+ }
697
706
  }
698
- if (onLoaded) {
699
- onLoaded();
707
+ catch (error) {
708
+ // Remove the pending data so the scene can still become ready.
709
+ // The state is intentionally left as LOADING so a persistently failing file is not re-fetched every frame.
710
+ scene.removePendingData(this);
711
+ // Don't log when the scene is being disposed: in-flight requests are aborted as part of normal teardown.
712
+ if (!scene.isDisposed) {
713
+ Logger.Error(`Unable to delay load geometry "${this.id}" from "${this.delayLoadingFile}": ${error}`);
714
+ }
700
715
  }
701
- }, undefined, true);
716
+ })();
702
717
  }
703
718
  /**
704
719
  * Invert the geometry to move from a right handed system to a left handed one.