@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.
- package/Behaviors/Cameras/interpolatingBehavior.d.ts +17 -2
- package/Behaviors/Cameras/interpolatingBehavior.js +53 -23
- package/Behaviors/Cameras/interpolatingBehavior.js.map +1 -1
- package/Cameras/Inputs/flyCameraKeyboardInput.js +9 -1
- package/Cameras/Inputs/flyCameraKeyboardInput.js.map +1 -1
- package/Cameras/Inputs/flyCameraMouseInput.d.ts +2 -0
- package/Cameras/Inputs/flyCameraMouseInput.js +14 -2
- package/Cameras/Inputs/flyCameraMouseInput.js.map +1 -1
- package/Cameras/Inputs/freeCameraKeyboardMoveInput.js +25 -5
- package/Cameras/Inputs/freeCameraKeyboardMoveInput.js.map +1 -1
- package/Cameras/Inputs/freeCameraMouseInput.d.ts +14 -0
- package/Cameras/Inputs/freeCameraMouseInput.js +26 -4
- package/Cameras/Inputs/freeCameraMouseInput.js.map +1 -1
- package/Cameras/Inputs/freeCameraTouchInput.js +16 -5
- package/Cameras/Inputs/freeCameraTouchInput.js.map +1 -1
- package/Cameras/Inputs/geospatialCameraKeyboardInput.js +8 -1
- package/Cameras/Inputs/geospatialCameraKeyboardInput.js.map +1 -1
- package/Cameras/arcRotateCamera.pure.js.map +1 -1
- package/Cameras/flyCamera.pure.d.ts +8 -0
- package/Cameras/flyCamera.pure.js.map +1 -1
- package/Cameras/freeCamera.pure.d.ts +8 -0
- package/Cameras/freeCamera.pure.js.map +1 -1
- package/Cameras/geospatialCamera.pure.d.ts +30 -7
- package/Cameras/geospatialCamera.pure.js +39 -14
- package/Cameras/geospatialCamera.pure.js.map +1 -1
- package/Cameras/geospatialCameraMovement.js +2 -2
- package/Cameras/geospatialCameraMovement.js.map +1 -1
- package/Cameras/index.d.ts +1 -0
- package/Cameras/index.js +1 -0
- package/Cameras/index.js.map +1 -1
- package/Cameras/pure.d.ts +1 -0
- package/Cameras/pure.js +1 -0
- package/Cameras/pure.js.map +1 -1
- package/Cameras/targetCamera.pure.d.ts +25 -0
- package/Cameras/targetCamera.pure.js +83 -25
- package/Cameras/targetCamera.pure.js.map +1 -1
- package/Cameras/targetCameraMovement.d.ts +56 -0
- package/Cameras/targetCameraMovement.js +63 -0
- package/Cameras/targetCameraMovement.js.map +1 -0
- package/Collisions/gpuPicker.d.ts +111 -4
- package/Collisions/gpuPicker.js +673 -106
- package/Collisions/gpuPicker.js.map +1 -1
- package/Engines/AbstractEngine/abstractEngine.scissor.d.ts +18 -0
- package/Engines/AbstractEngine/abstractEngine.scissor.js +2 -0
- package/Engines/AbstractEngine/abstractEngine.scissor.js.map +1 -0
- package/Engines/Native/nativeInterfaces.d.ts +1 -0
- package/Engines/Native/nativeInterfaces.js.map +1 -1
- package/Engines/abstractEngine.pure.js +2 -2
- package/Engines/abstractEngine.pure.js.map +1 -1
- package/Engines/engine.d.ts +1 -0
- package/Engines/engine.js +1 -0
- package/Engines/engine.js.map +1 -1
- package/Engines/engine.pure.d.ts +0 -12
- package/Engines/engine.pure.js +0 -20
- package/Engines/engine.pure.js.map +1 -1
- package/Engines/nullEngine.pure.d.ts +66 -0
- package/Engines/nullEngine.pure.js +97 -0
- package/Engines/nullEngine.pure.js.map +1 -1
- package/Engines/pure.d.ts +1 -0
- package/Engines/pure.js +1 -0
- package/Engines/pure.js.map +1 -1
- package/Engines/thinEngine.scissor.d.ts +6 -0
- package/Engines/thinEngine.scissor.js +9 -0
- package/Engines/thinEngine.scissor.js.map +1 -0
- package/Engines/thinEngine.scissor.pure.d.ts +6 -0
- package/Engines/thinEngine.scissor.pure.js +24 -0
- package/Engines/thinEngine.scissor.pure.js.map +1 -0
- package/Engines/thinNativeEngine.d.ts +0 -1
- package/Engines/thinNativeEngine.js +0 -1
- package/Engines/thinNativeEngine.js.map +1 -1
- package/Engines/thinNativeEngine.pure.d.ts +3 -0
- package/Engines/thinNativeEngine.pure.js +50 -4
- package/Engines/thinNativeEngine.pure.js.map +1 -1
- package/Engines/webgpuEngine.pure.js.map +1 -1
- package/Loading/sceneLoader.d.ts +5 -1
- package/Loading/sceneLoader.js +298 -401
- package/Loading/sceneLoader.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.d.ts +15 -0
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js +34 -1
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.pure.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.d.ts +9 -0
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js +26 -2
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.pure.js.map +1 -1
- package/Materials/GreasedLine/greasedLinePluginMaterialShadersGLSL.js +10 -3
- package/Materials/GreasedLine/greasedLinePluginMaterialShadersGLSL.js.map +1 -1
- package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js +3 -1
- package/Materials/Node/Blocks/GaussianSplatting/gaussianSplattingBlock.pure.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.js +18 -8
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.d.ts +77 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js +243 -104
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.d.ts +39 -0
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js +308 -0
- package/Meshes/GaussianSplatting/gaussianSplattingSortWorker.js.map +1 -0
- package/Meshes/geometry.js +30 -15
- package/Meshes/geometry.js.map +1 -1
- package/Meshes/mesh.pure.js +26 -14
- package/Meshes/mesh.pure.js.map +1 -1
- package/Misc/tools.pure.js +1 -1
- package/Misc/tools.pure.js.map +1 -1
- package/Shaders/ShadersInclude/gaussianSplatting.js +1 -0
- package/Shaders/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/Shaders/gaussianSplatting.fragment.js +17 -1
- package/Shaders/gaussianSplatting.fragment.js.map +1 -1
- package/Shaders/gaussianSplatting.vertex.js +1 -1
- package/Shaders/gaussianSplatting.vertex.js.map +1 -1
- package/Shaders/gaussianSplattingDepth.vertex.js +1 -1
- package/Shaders/gaussianSplattingDepth.vertex.js.map +1 -1
- package/Shaders/greasedLine.vertex.js +19 -3
- package/Shaders/greasedLine.vertex.js.map +1 -1
- package/Shaders/picking.fragment.js +39 -3
- package/Shaders/picking.fragment.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js +3 -1
- package/ShadersWGSL/ShadersInclude/gaussianSplatting.js.map +1 -1
- package/ShadersWGSL/gaussianSplatting.fragment.js +14 -1
- package/ShadersWGSL/gaussianSplatting.fragment.js.map +1 -1
- package/ShadersWGSL/gaussianSplatting.vertex.js +2 -2
- package/ShadersWGSL/gaussianSplatting.vertex.js.map +1 -1
- package/ShadersWGSL/gaussianSplattingDepth.vertex.js +2 -2
- package/ShadersWGSL/gaussianSplattingDepth.vertex.js.map +1 -1
- package/ShadersWGSL/picking.fragment.js +22 -1
- package/ShadersWGSL/picking.fragment.js.map +1 -1
- package/package.json +3 -2
- package/scene.pure.d.ts +12 -0
- package/scene.pure.js +36 -0
- package/scene.pure.js.map +1 -1
|
@@ -40,6 +40,19 @@ interface IUpdateOptions {
|
|
|
40
40
|
/** @internal When set, skips reprocessing splats [0, previousVertexCount) and copies from cached arrays instead. */
|
|
41
41
|
previousVertexCount?: number;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Defines a contiguous source-splat range to render from a GaussianSplattingMesh.
|
|
45
|
+
*/
|
|
46
|
+
export interface IGaussianSplattingSplatRange {
|
|
47
|
+
/**
|
|
48
|
+
* First source splat index to render.
|
|
49
|
+
*/
|
|
50
|
+
offset: number;
|
|
51
|
+
/**
|
|
52
|
+
* Number of source splats to render.
|
|
53
|
+
*/
|
|
54
|
+
count: number;
|
|
55
|
+
}
|
|
43
56
|
interface ITextureDataUpdateCapableEngine {
|
|
44
57
|
updateTextureData(texture: InternalTexture, imageData: ArrayBufferView, xOffset: number, yOffset: number, width: number, height: number, faceIndex?: number, lod?: number, generateMipMaps?: boolean): void;
|
|
45
58
|
updateRawTexture(texture: Nullable<InternalTexture>, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression?: Nullable<string>, type?: number, useSRGBBuffer?: boolean): void;
|
|
@@ -246,6 +259,17 @@ export interface PLYHeader {
|
|
|
246
259
|
* @internal Use GaussianSplattingMesh instead; this class is an internal implementation detail.
|
|
247
260
|
*/
|
|
248
261
|
export declare class GaussianSplattingMeshBase extends Mesh {
|
|
262
|
+
/**
|
|
263
|
+
* When true (default), the depth-sort worker uses the fast O(n) counting (radix) sort. Set to false to
|
|
264
|
+
* fall back to the legacy comparison sort (useful for A/B comparison or as a safety fallback). The change
|
|
265
|
+
* takes effect on the next sort.
|
|
266
|
+
*/
|
|
267
|
+
static UseCountingSort: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* When true, the depth-sort worker logs each sort's duration (ms) and active splat count to the console.
|
|
270
|
+
* Off by default; intended for performance investigation only.
|
|
271
|
+
*/
|
|
272
|
+
static LogSortPerformance: boolean;
|
|
249
273
|
/** @internal */
|
|
250
274
|
_vertexCount: number;
|
|
251
275
|
protected _worker: Nullable<Worker>;
|
|
@@ -254,6 +278,7 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
254
278
|
protected _canPostToWorker: boolean;
|
|
255
279
|
private _readyToDisplay;
|
|
256
280
|
private _sortRequestId;
|
|
281
|
+
private _activeRangeVersion;
|
|
257
282
|
private _hasRenderedOnce;
|
|
258
283
|
protected _covariancesATexture: Nullable<BaseTexture>;
|
|
259
284
|
protected _covariancesBTexture: Nullable<BaseTexture>;
|
|
@@ -286,6 +311,9 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
286
311
|
private _splatSizeMin;
|
|
287
312
|
private _splatSizeMax;
|
|
288
313
|
private _sortIsDirty;
|
|
314
|
+
private _activeSplatRanges;
|
|
315
|
+
private _activeSplatRangeKey;
|
|
316
|
+
private _activeSplatRenderCount;
|
|
289
317
|
protected _cachedBoundingMin: Nullable<Vector3>;
|
|
290
318
|
protected _cachedBoundingMax: Nullable<Vector3>;
|
|
291
319
|
private static _RowOutputLength;
|
|
@@ -317,6 +345,7 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
317
345
|
* @internal
|
|
318
346
|
*/
|
|
319
347
|
protected static _GetSplatDataFloats(data: ArrayBuffer | ArrayBufferView): Float32Array;
|
|
348
|
+
private static _BuildSplatRangeData;
|
|
320
349
|
/**
|
|
321
350
|
* Cosine value of the angle threshold to update view dependent splat sorting. Default is 0.0001.
|
|
322
351
|
*/
|
|
@@ -346,6 +375,38 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
346
375
|
* Number of splats in the mesh
|
|
347
376
|
*/
|
|
348
377
|
get splatCount(): number | undefined;
|
|
378
|
+
/**
|
|
379
|
+
* Number of source splats currently selected for rendering.
|
|
380
|
+
* When no range filter is active, this is the mesh's full source splat count.
|
|
381
|
+
*/
|
|
382
|
+
get renderedSplatCount(): number;
|
|
383
|
+
/**
|
|
384
|
+
* Restricts rendering to the provided source splat ranges.
|
|
385
|
+
* Passing `null` clears the range filter and renders the full source splat set.
|
|
386
|
+
* @param ranges contiguous source ranges to render, or null to render all splats
|
|
387
|
+
*/
|
|
388
|
+
setSplatIndexRanges(ranges: Nullable<readonly IGaussianSplattingSplatRange[]>): void;
|
|
389
|
+
private _setSplatIndexRanges;
|
|
390
|
+
private _ensureDepthMixSize;
|
|
391
|
+
/**
|
|
392
|
+
* Sends the active source-splat intervals to the sort worker. When no range filter is active,
|
|
393
|
+
* a single interval covering all source splats is sent so the worker never assumes the full set.
|
|
394
|
+
*/
|
|
395
|
+
private _postIntervalsToWorker;
|
|
396
|
+
/**
|
|
397
|
+
* Initializes this mesh to render from an externally-provided, GPU-decoded work buffer, bypassing the
|
|
398
|
+
* CPU `updateData` path. The four textures must hold the standard decoded GS layout addressed by linear
|
|
399
|
+
* splat index over a square texture: centers (x,y,z,1), covariance A (Sigma00,01,02,11) and B
|
|
400
|
+
* (Sigma12,22,*,*) as full covariance (so `center.w` is 1), and RGBA color. `splatPositions` are the
|
|
401
|
+
* stride-4 CPU centers consumed by the depth-sort worker.
|
|
402
|
+
* @param centers centers texture
|
|
403
|
+
* @param covariancesA covariance A texture
|
|
404
|
+
* @param covariancesB covariance B texture
|
|
405
|
+
* @param colors color texture
|
|
406
|
+
* @param splatPositions stride-4 CPU centers for depth sorting (length vertexCount*4)
|
|
407
|
+
* @param vertexCount number of splats addressable in the work buffer
|
|
408
|
+
*/
|
|
409
|
+
protected _setExternalWorkBuffer(centers: BaseTexture, covariancesA: BaseTexture, covariancesB: BaseTexture, colors: BaseTexture, splatPositions: Float32Array, vertexCount: number): void;
|
|
349
410
|
/**
|
|
350
411
|
* returns the splats data array buffer that contains in order : postions (3 floats), size (3 floats), color (4 bytes), orientation quaternion (4 bytes)
|
|
351
412
|
* Only available if the mesh was created with keepInRam: true
|
|
@@ -434,6 +495,12 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
434
495
|
* Get the compensation state
|
|
435
496
|
*/
|
|
436
497
|
get compensation(): boolean;
|
|
498
|
+
/**
|
|
499
|
+
* Minimum projected splat size, in pixels, below which a splat is discarded (0 = disabled).
|
|
500
|
+
* Applied in real time; no rebuild required.
|
|
501
|
+
*/
|
|
502
|
+
get minPixelSize(): number;
|
|
503
|
+
set minPixelSize(value: number);
|
|
437
504
|
private _loadingPromise;
|
|
438
505
|
/**
|
|
439
506
|
* set rendering material
|
|
@@ -559,7 +626,6 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
559
626
|
* @returns a new Gaussian Splatting Mesh
|
|
560
627
|
*/
|
|
561
628
|
clone(name?: string): GaussianSplattingMeshBase;
|
|
562
|
-
private static _CreateWorker;
|
|
563
629
|
protected _makeEmptySplat(index: number, covA: Uint16Array, covB: Uint16Array, colorArray: Uint8Array): void;
|
|
564
630
|
/**
|
|
565
631
|
* Processes a single splat from the source buffer (at srcIndex) and writes the result into
|
|
@@ -618,6 +684,16 @@ export declare class GaussianSplattingMeshBase extends Mesh {
|
|
|
618
684
|
* Subclasses (e.g. compound) may override to additionally post part-index data.
|
|
619
685
|
*/
|
|
620
686
|
protected _notifyWorkerNewData(): void;
|
|
687
|
+
/**
|
|
688
|
+
* Patches only a contiguous range of source-splat centers in the sort worker, instead of re-copying and
|
|
689
|
+
* transferring the entire position buffer (which is hundreds of MB for large streamed datasets and caused
|
|
690
|
+
* a multi-frame freeze on every LOD decode). The worker must already hold a full-size position buffer (from
|
|
691
|
+
* the initial {@link GaussianSplattingSortWorkerCommand.POSITIONS} message at worker creation). Marks the
|
|
692
|
+
* sort dirty so the new splats are sorted in; the caller is responsible for any interval/range refresh.
|
|
693
|
+
* @param splatOffset first splat index of the updated range
|
|
694
|
+
* @param splatCount number of splats in the updated range
|
|
695
|
+
*/
|
|
696
|
+
protected _postWorkerPositionsRange(splatOffset: number, splatCount: number): void;
|
|
621
697
|
private _updateData;
|
|
622
698
|
/**
|
|
623
699
|
* Update asynchronously the buffer
|
|
@@ -13,6 +13,7 @@ import { Scalar } from "../../Maths/math.scalar.js";
|
|
|
13
13
|
import { runCoroutineSync, runCoroutineAsync, createYieldingScheduler } from "../../Misc/coroutine.js";
|
|
14
14
|
import { EngineStore } from "../../Engines/engineStore.js";
|
|
15
15
|
import { ImportMeshAsync } from "../../Loading/sceneLoader.js";
|
|
16
|
+
import { GaussianSplattingSortWorker, GaussianSplattingSortWorkerCommand } from "./gaussianSplattingSortWorker.js";
|
|
16
17
|
const IsNative = typeof _native !== "undefined";
|
|
17
18
|
const Native = IsNative ? _native : null;
|
|
18
19
|
// @internal
|
|
@@ -273,6 +274,27 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
273
274
|
}
|
|
274
275
|
return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / floatSize);
|
|
275
276
|
}
|
|
277
|
+
static _BuildSplatRangeData(ranges, vertexCount) {
|
|
278
|
+
if (ranges === null) {
|
|
279
|
+
return { ranges: null, count: vertexCount, key: "" };
|
|
280
|
+
}
|
|
281
|
+
const rangePairs = [];
|
|
282
|
+
let totalCount = 0;
|
|
283
|
+
let key = "";
|
|
284
|
+
for (const range of ranges) {
|
|
285
|
+
const start = Math.max(0, Math.floor(range.offset));
|
|
286
|
+
const count = Math.max(0, Math.floor(range.count));
|
|
287
|
+
const end = Math.min(vertexCount, start + count);
|
|
288
|
+
const rangeCount = Math.max(0, end - start);
|
|
289
|
+
if (rangeCount === 0) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
rangePairs.push(start, rangeCount);
|
|
293
|
+
totalCount += rangeCount;
|
|
294
|
+
key += `${start}:${rangeCount};`;
|
|
295
|
+
}
|
|
296
|
+
return { ranges: new Uint32Array(rangePairs), count: totalCount, key };
|
|
297
|
+
}
|
|
276
298
|
/**
|
|
277
299
|
* If true, disables depth sorting of the splats (default: false)
|
|
278
300
|
*/
|
|
@@ -326,6 +348,100 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
326
348
|
get splatCount() {
|
|
327
349
|
return this._splatIndex?.length;
|
|
328
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Number of source splats currently selected for rendering.
|
|
353
|
+
* When no range filter is active, this is the mesh's full source splat count.
|
|
354
|
+
*/
|
|
355
|
+
get renderedSplatCount() {
|
|
356
|
+
return this._activeSplatRanges ? this._activeSplatRenderCount : this._vertexCount;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Restricts rendering to the provided source splat ranges.
|
|
360
|
+
* Passing `null` clears the range filter and renders the full source splat set.
|
|
361
|
+
* @param ranges contiguous source ranges to render, or null to render all splats
|
|
362
|
+
*/
|
|
363
|
+
setSplatIndexRanges(ranges) {
|
|
364
|
+
this._setSplatIndexRanges(ranges);
|
|
365
|
+
}
|
|
366
|
+
_setSplatIndexRanges(ranges) {
|
|
367
|
+
const rangeData = GaussianSplattingMeshBase._BuildSplatRangeData(ranges, this._vertexCount);
|
|
368
|
+
const isSameRangeSet = this._activeSplatRanges === null ? rangeData.ranges === null : rangeData.ranges !== null && this._activeSplatRangeKey === rangeData.key;
|
|
369
|
+
if (isSameRangeSet) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
this._activeSplatRanges = rangeData.ranges;
|
|
373
|
+
this._activeSplatRangeKey = rangeData.key;
|
|
374
|
+
this._activeSplatRenderCount = rangeData.count;
|
|
375
|
+
this._activeRangeVersion++;
|
|
376
|
+
this._postIntervalsToWorker();
|
|
377
|
+
if (this._worker) {
|
|
378
|
+
// Defer swapping the rendered index buffer until the worker returns a depth sort computed for
|
|
379
|
+
// this exact range set (see the worker `onmessage` handler). Until then we keep rendering the
|
|
380
|
+
// previous fully-sorted state, so a range/LOD change never shows an unsorted flash nor a stale
|
|
381
|
+
// index buffer (rendering indices that belong to a different active set, which caused the
|
|
382
|
+
// disappear/reappear flicker). Only the worker's depth buffer is resized here so the next sort
|
|
383
|
+
// produces the right number of entries.
|
|
384
|
+
this._ensureDepthMixSize(Math.max((this._activeSplatRenderCount + 15) & ~0xf, 16));
|
|
385
|
+
this._sortIsDirty = true;
|
|
386
|
+
this._postToWorker(true);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
// Synchronous sort paths (disabled depth sort / Native): swap the index buffer immediately.
|
|
390
|
+
this._updateSplatIndexBuffer(this._vertexCount);
|
|
391
|
+
this._postToWorker(true);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// (Re)allocates the worker depth buffer to the given padded size. A fresh array is allocated when the
|
|
395
|
+
// size differs or the current buffer is detached (in-flight in the worker), so a queued sort can be
|
|
396
|
+
// re-posted with a correctly-sized buffer without disturbing the in-flight one.
|
|
397
|
+
_ensureDepthMixSize(paddedCount) {
|
|
398
|
+
if (IsNative) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (!this._depthMix || this._depthMix.length !== paddedCount || this._depthMix.buffer.byteLength === 0) {
|
|
402
|
+
this._depthMix = new BigInt64Array(paddedCount);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Sends the active source-splat intervals to the sort worker. When no range filter is active,
|
|
407
|
+
* a single interval covering all source splats is sent so the worker never assumes the full set.
|
|
408
|
+
*/
|
|
409
|
+
_postIntervalsToWorker() {
|
|
410
|
+
if (!this._worker) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const intervals = this._activeSplatRanges ? new Uint32Array(this._activeSplatRanges) : new Uint32Array([0, this._vertexCount]);
|
|
414
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.INTERVALS, intervals }, [intervals.buffer]);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Initializes this mesh to render from an externally-provided, GPU-decoded work buffer, bypassing the
|
|
418
|
+
* CPU `updateData` path. The four textures must hold the standard decoded GS layout addressed by linear
|
|
419
|
+
* splat index over a square texture: centers (x,y,z,1), covariance A (Sigma00,01,02,11) and B
|
|
420
|
+
* (Sigma12,22,*,*) as full covariance (so `center.w` is 1), and RGBA color. `splatPositions` are the
|
|
421
|
+
* stride-4 CPU centers consumed by the depth-sort worker.
|
|
422
|
+
* @param centers centers texture
|
|
423
|
+
* @param covariancesA covariance A texture
|
|
424
|
+
* @param covariancesB covariance B texture
|
|
425
|
+
* @param colors color texture
|
|
426
|
+
* @param splatPositions stride-4 CPU centers for depth sorting (length vertexCount*4)
|
|
427
|
+
* @param vertexCount number of splats addressable in the work buffer
|
|
428
|
+
*/
|
|
429
|
+
_setExternalWorkBuffer(centers, covariancesA, covariancesB, colors, splatPositions, vertexCount) {
|
|
430
|
+
this._covariancesATexture = covariancesA;
|
|
431
|
+
this._covariancesBTexture = covariancesB;
|
|
432
|
+
this._centersTexture = centers;
|
|
433
|
+
this._colorsTexture = colors;
|
|
434
|
+
this._splatPositions = splatPositions;
|
|
435
|
+
this._vertexCount = vertexCount;
|
|
436
|
+
this._activeSplatRanges = null;
|
|
437
|
+
this._activeSplatRangeKey = "";
|
|
438
|
+
this._activeSplatRenderCount = 0;
|
|
439
|
+
this._readyToDisplay = false;
|
|
440
|
+
// Sizes _splatIndex/_depthMix, starts the sort worker, and posts positions + intervals.
|
|
441
|
+
this._instantiateWorker();
|
|
442
|
+
// updateData (bypassed here) normally enables the mesh; do it explicitly for the work-buffer path.
|
|
443
|
+
this.setEnabled(true);
|
|
444
|
+
}
|
|
329
445
|
/**
|
|
330
446
|
* returns the splats data array buffer that contains in order : postions (3 floats), size (3 floats), color (4 bytes), orientation quaternion (4 bytes)
|
|
331
447
|
* Only available if the mesh was created with keepInRam: true
|
|
@@ -525,6 +641,18 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
525
641
|
get compensation() {
|
|
526
642
|
return this._material instanceof GaussianSplattingMaterial ? this._material.compensation : false;
|
|
527
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Minimum projected splat size, in pixels, below which a splat is discarded (0 = disabled).
|
|
646
|
+
* Applied in real time; no rebuild required.
|
|
647
|
+
*/
|
|
648
|
+
get minPixelSize() {
|
|
649
|
+
return this._material instanceof GaussianSplattingMaterial ? this._material.minPixelSize : 0;
|
|
650
|
+
}
|
|
651
|
+
set minPixelSize(value) {
|
|
652
|
+
if (this._material instanceof GaussianSplattingMaterial) {
|
|
653
|
+
this._material.minPixelSize = Math.max(0, value);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
528
656
|
/**
|
|
529
657
|
* set rendering material
|
|
530
658
|
*/
|
|
@@ -577,6 +705,9 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
577
705
|
this._canPostToWorker = true;
|
|
578
706
|
this._readyToDisplay = false;
|
|
579
707
|
this._sortRequestId = 0;
|
|
708
|
+
// Incremented on every active-range change. A worker sort result is only applied when it was computed
|
|
709
|
+
// for the current range version (and size), so a sort for stale ranges is never rendered.
|
|
710
|
+
this._activeRangeVersion = 0;
|
|
580
711
|
this._hasRenderedOnce = false;
|
|
581
712
|
this._covariancesATexture = null;
|
|
582
713
|
this._covariancesBTexture = null;
|
|
@@ -609,6 +740,9 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
609
740
|
this._splatSizeMin = Infinity;
|
|
610
741
|
this._splatSizeMax = -Infinity;
|
|
611
742
|
this._sortIsDirty = false;
|
|
743
|
+
this._activeSplatRanges = null;
|
|
744
|
+
this._activeSplatRangeKey = "";
|
|
745
|
+
this._activeSplatRenderCount = 0;
|
|
612
746
|
// Cached bounding box for incremental addPart updates (O(1) vs O(N) scan of positions)
|
|
613
747
|
this._cachedBoundingMin = null;
|
|
614
748
|
this._cachedBoundingMax = null;
|
|
@@ -910,16 +1044,26 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
910
1044
|
this._canPostToWorker = false;
|
|
911
1045
|
if (this._worker) {
|
|
912
1046
|
this._worker.postMessage({
|
|
1047
|
+
command: GaussianSplattingSortWorkerCommand.SORT,
|
|
913
1048
|
worldMatrix: worldMatrix.m,
|
|
914
1049
|
cameraForward: [cameraViewMatrix.m[2], cameraViewMatrix.m[6], cameraViewMatrix.m[10]],
|
|
915
1050
|
cameraPosition: [camera.globalPosition.x, camera.globalPosition.y, camera.globalPosition.z],
|
|
916
1051
|
depthMix: this._depthMix,
|
|
917
1052
|
cameraId: camera.uniqueId,
|
|
918
1053
|
sortRequestId: cameraViewInfos.sortRequestId,
|
|
1054
|
+
rangeVersion: this._activeRangeVersion,
|
|
1055
|
+
useCountingSort: GaussianSplattingMeshBase.UseCountingSort,
|
|
1056
|
+
logSortPerformance: GaussianSplattingMeshBase.LogSortPerformance,
|
|
919
1057
|
}, [this._depthMix.buffer]);
|
|
920
1058
|
}
|
|
921
1059
|
else if (Native?.sortSplats) {
|
|
922
|
-
|
|
1060
|
+
if (this._activeSplatRanges) {
|
|
1061
|
+
// Native sort can't filter ranges: fall back to the (already range-filtered) identity index buffer.
|
|
1062
|
+
this._updateSplatIndexBuffer(this._vertexCount);
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
Native.sortSplats(this._modelViewProjectionMatrix, this._splatPositions, this._splatIndex, this._scene.useRightHandedSystem);
|
|
1066
|
+
}
|
|
923
1067
|
if (cameraViewInfos.splatIndexBufferSet) {
|
|
924
1068
|
cameraViewInfos.mesh.thinInstanceBufferUpdated("splatIndex");
|
|
925
1069
|
}
|
|
@@ -1960,7 +2104,9 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
1960
2104
|
const positions = Float32Array.from(this._splatPositions);
|
|
1961
2105
|
const vertexCount = this._vertexCount;
|
|
1962
2106
|
if (this._worker) {
|
|
1963
|
-
this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
|
|
2107
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.POSITIONS, positions, vertexCount }, [positions.buffer]);
|
|
2108
|
+
// Re-sync the active interval set in case the source splat count changed.
|
|
2109
|
+
this._postIntervalsToWorker();
|
|
1964
2110
|
}
|
|
1965
2111
|
// Handle SH textures in update path - create if they don't exist
|
|
1966
2112
|
if (sh && !this._shTextures) {
|
|
@@ -2038,7 +2184,9 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2038
2184
|
if (this._worker) {
|
|
2039
2185
|
const positions = Float32Array.from(this._splatPositions);
|
|
2040
2186
|
const vertexCount = this._vertexCount;
|
|
2041
|
-
this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
|
|
2187
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.POSITIONS, positions, vertexCount }, [positions.buffer]);
|
|
2188
|
+
// Re-sync the active interval set in case the source splat count changed.
|
|
2189
|
+
this._postIntervalsToWorker();
|
|
2042
2190
|
}
|
|
2043
2191
|
this._postToWorker(true);
|
|
2044
2192
|
}
|
|
@@ -2075,10 +2223,32 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2075
2223
|
if (this._worker) {
|
|
2076
2224
|
const positions = Float32Array.from(this._splatPositions);
|
|
2077
2225
|
const vertexCount = this._vertexCount;
|
|
2078
|
-
this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
|
|
2226
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.POSITIONS, positions, vertexCount }, [positions.buffer]);
|
|
2227
|
+
// The source splat count may have changed (e.g. addPart): re-sync the worker's active
|
|
2228
|
+
// interval set so it covers the current splats instead of a stale (smaller) range.
|
|
2229
|
+
this._postIntervalsToWorker();
|
|
2079
2230
|
}
|
|
2080
2231
|
this._sortIsDirty = true;
|
|
2081
2232
|
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Patches only a contiguous range of source-splat centers in the sort worker, instead of re-copying and
|
|
2235
|
+
* transferring the entire position buffer (which is hundreds of MB for large streamed datasets and caused
|
|
2236
|
+
* a multi-frame freeze on every LOD decode). The worker must already hold a full-size position buffer (from
|
|
2237
|
+
* the initial {@link GaussianSplattingSortWorkerCommand.POSITIONS} message at worker creation). Marks the
|
|
2238
|
+
* sort dirty so the new splats are sorted in; the caller is responsible for any interval/range refresh.
|
|
2239
|
+
* @param splatOffset first splat index of the updated range
|
|
2240
|
+
* @param splatCount number of splats in the updated range
|
|
2241
|
+
*/
|
|
2242
|
+
_postWorkerPositionsRange(splatOffset, splatCount) {
|
|
2243
|
+
if (!this._worker || !this._splatPositions || splatCount <= 0) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
const floatOffset = splatOffset * 4;
|
|
2247
|
+
// Copy just the changed region (stride 4) so the main thread keeps its own _splatPositions intact.
|
|
2248
|
+
const data = this._splatPositions.slice(floatOffset, floatOffset + splatCount * 4);
|
|
2249
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.POSITIONS_UPDATE, offset: floatOffset, data }, [data.buffer]);
|
|
2250
|
+
this._sortIsDirty = true;
|
|
2251
|
+
}
|
|
2082
2252
|
*_updateData(data, isAsync, sh, partIndices, { flipY = false, previousVertexCount = 0 } = {}, shDegree) {
|
|
2083
2253
|
if (!this._covariancesATexture) {
|
|
2084
2254
|
this._readyToDisplay = false;
|
|
@@ -2100,6 +2270,11 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2100
2270
|
}
|
|
2101
2271
|
const vertexCount = uBuffer.length / GaussianSplattingMeshBase._RowOutputLength;
|
|
2102
2272
|
if (vertexCount != this._vertexCount) {
|
|
2273
|
+
// The source set changed: drop any active range filter so the buffers resize to the full
|
|
2274
|
+
// set. Callers (e.g. streaming LOD) re-apply their ranges after the data is committed.
|
|
2275
|
+
this._activeSplatRanges = null;
|
|
2276
|
+
this._activeSplatRangeKey = "";
|
|
2277
|
+
this._activeSplatRenderCount = 0;
|
|
2103
2278
|
this._updateSplatIndexBuffer(vertexCount);
|
|
2104
2279
|
}
|
|
2105
2280
|
this._vertexCount = vertexCount;
|
|
@@ -2241,19 +2416,40 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2241
2416
|
}
|
|
2242
2417
|
// in case size is different
|
|
2243
2418
|
_updateSplatIndexBuffer(vertexCount) {
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2419
|
+
const renderedSplatCount = this._activeSplatRanges ? this._activeSplatRenderCount : vertexCount;
|
|
2420
|
+
const paddedVertexCount = Math.max((renderedSplatCount + 15) & ~0xf, 16);
|
|
2421
|
+
const previousIndex = this._splatIndex;
|
|
2422
|
+
if (!previousIndex || paddedVertexCount !== previousIndex.length) {
|
|
2246
2423
|
this._splatIndex = new Float32Array(paddedVertexCount);
|
|
2247
|
-
for (let i = 0; i < paddedVertexCount; i++) {
|
|
2248
|
-
this._splatIndex[i] = i;
|
|
2249
|
-
}
|
|
2250
2424
|
// update meshes for knowns cameras
|
|
2251
2425
|
this._cameraViewInfos.forEach((cameraViewInfos) => {
|
|
2252
2426
|
cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
|
|
2253
2427
|
});
|
|
2254
2428
|
}
|
|
2429
|
+
const splatIndex = this._splatIndex;
|
|
2430
|
+
// Populate the buffer with the current active source indices (identity when unfiltered) so every
|
|
2431
|
+
// rendered slot points at a valid splat. This is the synchronous (no async sort worker) path; the
|
|
2432
|
+
// worker path instead defers the index-buffer swap until a matching sort lands (see _onWorkerCreated).
|
|
2433
|
+
if (this._activeSplatRanges) {
|
|
2434
|
+
let index = 0;
|
|
2435
|
+
for (let rangeIndex = 0; rangeIndex < this._activeSplatRanges.length; rangeIndex += 2) {
|
|
2436
|
+
const start = this._activeSplatRanges[rangeIndex];
|
|
2437
|
+
const count = this._activeSplatRanges[rangeIndex + 1];
|
|
2438
|
+
for (let sourceIndex = start; sourceIndex < start + count; sourceIndex++) {
|
|
2439
|
+
splatIndex[index++] = sourceIndex;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
for (; index < paddedVertexCount; index++) {
|
|
2443
|
+
splatIndex[index] = 0;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
else {
|
|
2447
|
+
for (let i = 0; i < paddedVertexCount; i++) {
|
|
2448
|
+
splatIndex[i] = i;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2255
2451
|
// Update depthMix
|
|
2256
|
-
if ((!this._depthMix ||
|
|
2452
|
+
if ((!this._depthMix || paddedVertexCount !== this._depthMix.length) && !IsNative) {
|
|
2257
2453
|
this._depthMix = new BigInt64Array(paddedVertexCount);
|
|
2258
2454
|
}
|
|
2259
2455
|
this.forcedInstanceCount = Math.max(paddedVertexCount >> 4, 1);
|
|
@@ -2349,21 +2545,28 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2349
2545
|
// If the previous worker was terminated mid-sort it would never have set _canPostToWorker
|
|
2350
2546
|
// back to true, leaving the sort permanently frozen on the new worker.
|
|
2351
2547
|
this._canPostToWorker = true;
|
|
2352
|
-
this._worker = new Worker(URL.createObjectURL(new Blob(["(",
|
|
2548
|
+
this._worker = new Worker(URL.createObjectURL(new Blob(["(", GaussianSplattingSortWorker.toString(), ")(self)"], {
|
|
2353
2549
|
type: "application/javascript",
|
|
2354
2550
|
})));
|
|
2355
2551
|
const positions = Float32Array.from(this._splatPositions);
|
|
2356
|
-
this._worker.postMessage({ positions }, [positions.buffer]);
|
|
2552
|
+
this._worker.postMessage({ command: GaussianSplattingSortWorkerCommand.POSITIONS, positions }, [positions.buffer]);
|
|
2553
|
+
// The main thread owns the active interval set: send it explicitly (covering all indices when
|
|
2554
|
+
// no LOD filter is active) rather than letting the worker assume the full source set.
|
|
2555
|
+
this._postIntervalsToWorker();
|
|
2357
2556
|
this._onWorkerCreated(this._worker);
|
|
2358
2557
|
this._worker.onerror = () => {
|
|
2359
2558
|
// If the worker throws an unhandled error, unlock the posting gate so the next frame can retry the sort.
|
|
2360
2559
|
this._canPostToWorker = true;
|
|
2361
2560
|
};
|
|
2362
2561
|
this._worker.onmessage = (e) => {
|
|
2363
|
-
//
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
if
|
|
2562
|
+
// Size the result against the active (rendered) splat count, which may be a subset of the
|
|
2563
|
+
// full source set when an interval/LOD filter is active.
|
|
2564
|
+
const renderedPadded = Math.max((this.renderedSplatCount + 15) & ~0xf, 16);
|
|
2565
|
+
// Discard the result and trigger a fresh sort if it no longer matches the current active set:
|
|
2566
|
+
// either the active count changed since it was posted, or it was computed for a stale range
|
|
2567
|
+
// version (the active ranges changed while it was in flight). Applying it would render indices
|
|
2568
|
+
// that belong to a different active set.
|
|
2569
|
+
if (e.data.depthMix.length != renderedPadded || e.data.rangeVersion !== this._activeRangeVersion) {
|
|
2367
2570
|
// Only re-enable posting and trigger a re-sort if the buffer is available.
|
|
2368
2571
|
// If byteLength === 0 the buffer is already in-flight for a newer sort;
|
|
2369
2572
|
// that sort's onmessage will handle things when it returns.
|
|
@@ -2377,13 +2580,25 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2377
2580
|
this._depthMix = e.data.depthMix;
|
|
2378
2581
|
const cameraId = e.data.cameraId;
|
|
2379
2582
|
const sortRequestId = e.data.sortRequestId;
|
|
2583
|
+
// The index buffer swap is deferred to here so the previous fully-sorted state stays on screen
|
|
2584
|
+
// until this matching sort lands. Resize it now (the active count may have grown/shrunk since the
|
|
2585
|
+
// last applied sort) and rebind it for every known camera.
|
|
2586
|
+
if (!this._splatIndex || this._splatIndex.length !== renderedPadded) {
|
|
2587
|
+
this._splatIndex = new Float32Array(renderedPadded);
|
|
2588
|
+
this._cameraViewInfos.forEach((info) => {
|
|
2589
|
+
info.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
|
|
2590
|
+
info.splatIndexBufferSet = true;
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2380
2593
|
const indexMix = new Uint32Array(e.data.depthMix.buffer);
|
|
2381
2594
|
if (this._splatIndex) {
|
|
2382
|
-
for (let j = 0; j <
|
|
2595
|
+
for (let j = 0; j < renderedPadded; j++) {
|
|
2383
2596
|
this._splatIndex[j] = indexMix[2 * j];
|
|
2384
2597
|
}
|
|
2385
2598
|
}
|
|
2599
|
+
this.forcedInstanceCount = Math.max(renderedPadded >> 4, 1);
|
|
2386
2600
|
if (this._delayedTextureUpdate) {
|
|
2601
|
+
const vertexCountPadded = (this._vertexCount + 15) & ~0xf;
|
|
2387
2602
|
const textureSize = this._getTextureSize(vertexCountPadded);
|
|
2388
2603
|
this._updateSubTextures(this._delayedTextureUpdate.centers, this._delayedTextureUpdate.covA, this._delayedTextureUpdate.covB, this._delayedTextureUpdate.colors, 0, textureSize.y, this._delayedTextureUpdate.sh);
|
|
2389
2604
|
this._delayedTextureUpdate = null;
|
|
@@ -2545,6 +2760,17 @@ export class GaussianSplattingMeshBase extends Mesh {
|
|
|
2545
2760
|
return this;
|
|
2546
2761
|
}
|
|
2547
2762
|
}
|
|
2763
|
+
/**
|
|
2764
|
+
* When true (default), the depth-sort worker uses the fast O(n) counting (radix) sort. Set to false to
|
|
2765
|
+
* fall back to the legacy comparison sort (useful for A/B comparison or as a safety fallback). The change
|
|
2766
|
+
* takes effect on the next sort.
|
|
2767
|
+
*/
|
|
2768
|
+
GaussianSplattingMeshBase.UseCountingSort = true;
|
|
2769
|
+
/**
|
|
2770
|
+
* When true, the depth-sort worker logs each sort's duration (ms) and active splat count to the console.
|
|
2771
|
+
* Off by default; intended for performance investigation only.
|
|
2772
|
+
*/
|
|
2773
|
+
GaussianSplattingMeshBase.LogSortPerformance = false;
|
|
2548
2774
|
GaussianSplattingMeshBase._RowOutputLength = 3 * 4 + 3 * 4 + 4 + 4; // Vector3 position, Vector3 scale, 1 u8 quaternion, 1 color with alpha
|
|
2549
2775
|
GaussianSplattingMeshBase._SH_C0 = 0.28209479177387814;
|
|
2550
2776
|
// batch size between 2 yield calls. This value is a tradeoff between updates overhead and framerate hiccups
|
|
@@ -2559,93 +2785,6 @@ GaussianSplattingMeshBase._DefaultViewUpdateThreshold = 1e-4;
|
|
|
2559
2785
|
* A value of 0 (default) means display update will not happens before splat is ready.
|
|
2560
2786
|
*/
|
|
2561
2787
|
GaussianSplattingMeshBase.ProgressiveUpdateAmount = 0;
|
|
2562
|
-
GaussianSplattingMeshBase._CreateWorker = function (self) {
|
|
2563
|
-
let positions;
|
|
2564
|
-
let depthMix;
|
|
2565
|
-
let indices;
|
|
2566
|
-
let floatMix;
|
|
2567
|
-
let partIndices;
|
|
2568
|
-
let partMatrices;
|
|
2569
|
-
self.onmessage = (e) => {
|
|
2570
|
-
// updated on init
|
|
2571
|
-
if (e.data.positions) {
|
|
2572
|
-
positions = e.data.positions;
|
|
2573
|
-
}
|
|
2574
|
-
// update on rig node changed
|
|
2575
|
-
else if (e.data.partMatrices) {
|
|
2576
|
-
partMatrices = e.data.partMatrices;
|
|
2577
|
-
}
|
|
2578
|
-
// update on rig node indices changed
|
|
2579
|
-
else if (e.data.partIndices !== undefined) {
|
|
2580
|
-
partIndices = e.data.partIndices;
|
|
2581
|
-
}
|
|
2582
|
-
// update on view changed
|
|
2583
|
-
else {
|
|
2584
|
-
const cameraId = e.data.cameraId;
|
|
2585
|
-
const sortRequestId = e.data.sortRequestId;
|
|
2586
|
-
const globalWorldMatrix = e.data.worldMatrix;
|
|
2587
|
-
const cameraForward = e.data.cameraForward;
|
|
2588
|
-
const cameraPosition = e.data.cameraPosition;
|
|
2589
|
-
depthMix = e.data.depthMix;
|
|
2590
|
-
if (!positions || !cameraForward) {
|
|
2591
|
-
// Sort request arrived before positions were initialized — return the buffer unchanged so the main thread can unlock _canPostToWorker.
|
|
2592
|
-
self.postMessage({ depthMix, cameraId, sortRequestId }, [depthMix.buffer]);
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
|
|
2596
|
-
indices = new Uint32Array(depthMix.buffer);
|
|
2597
|
-
floatMix = new Float32Array(depthMix.buffer);
|
|
2598
|
-
// Sort
|
|
2599
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2600
|
-
indices[2 * j] = j;
|
|
2601
|
-
}
|
|
2602
|
-
// depth = dot(cameraForward, worldPos - cameraPos)
|
|
2603
|
-
const camDot = cameraForward[0] * cameraPosition[0] + cameraForward[1] * cameraPosition[1] + cameraForward[2] * cameraPosition[2];
|
|
2604
|
-
const computeDepthCoeffs = (m) => {
|
|
2605
|
-
return [
|
|
2606
|
-
cameraForward[0] * m[0] + cameraForward[1] * m[1] + cameraForward[2] * m[2],
|
|
2607
|
-
cameraForward[0] * m[4] + cameraForward[1] * m[5] + cameraForward[2] * m[6],
|
|
2608
|
-
cameraForward[0] * m[8] + cameraForward[1] * m[9] + cameraForward[2] * m[10],
|
|
2609
|
-
cameraForward[0] * m[12] + cameraForward[1] * m[13] + cameraForward[2] * m[14] - camDot,
|
|
2610
|
-
];
|
|
2611
|
-
};
|
|
2612
|
-
try {
|
|
2613
|
-
if (partMatrices && partIndices) {
|
|
2614
|
-
// Precompute depth coefficients for each rig node
|
|
2615
|
-
const depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));
|
|
2616
|
-
// NB: For performance reasons, we assume that part indices are valid
|
|
2617
|
-
const length = partIndices.length;
|
|
2618
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2619
|
-
// NB: We need this 'min' because vertex array is padded, not partIndices
|
|
2620
|
-
const partIndex = partIndices[Math.min(j, length - 1)];
|
|
2621
|
-
const coeff = depthCoeffs[partIndex];
|
|
2622
|
-
floatMix[2 * j + 1] = coeff[0] * positions[4 * j + 0] + coeff[1] * positions[4 * j + 1] + coeff[2] * positions[4 * j + 2] + coeff[3];
|
|
2623
|
-
// instead of using minus to sort back to front, we use bitwise not operator to invert the order of indices
|
|
2624
|
-
// might not be faster but a minus sign implies a reference value that may not be enough and will decrease floatting precision
|
|
2625
|
-
indices[2 * j + 1] = ~indices[2 * j + 1];
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
else {
|
|
2629
|
-
// Compute depth coefficients from global world matrix
|
|
2630
|
-
const [a, b, c, d] = computeDepthCoeffs(globalWorldMatrix);
|
|
2631
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2632
|
-
floatMix[2 * j + 1] = a * positions[4 * j + 0] + b * positions[4 * j + 1] + c * positions[4 * j + 2] + d;
|
|
2633
|
-
indices[2 * j + 1] = ~indices[2 * j + 1];
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
depthMix.sort();
|
|
2637
|
-
}
|
|
2638
|
-
catch (sortError) {
|
|
2639
|
-
// Transient data inconsistency (e.g. partIndices/partMatrices mismatch during addPart/removePart rebuild).
|
|
2640
|
-
// Return the buffer unsorted so the main thread can unlock _canPostToWorker and retry next frame.
|
|
2641
|
-
// Logger is unavailable inside the worker — console is the only option.
|
|
2642
|
-
// eslint-disable-next-line no-console
|
|
2643
|
-
console.error("Gaussian splat sort worker encountered an error (will retry next frame):", sortError);
|
|
2644
|
-
}
|
|
2645
|
-
self.postMessage({ depthMix, cameraId, sortRequestId }, [depthMix.buffer]);
|
|
2646
|
-
}
|
|
2647
|
-
};
|
|
2648
|
-
};
|
|
2649
2788
|
/**
|
|
2650
2789
|
* Allocates SH texture buffers pre-filled with 128 (the neutral encoding of ~0.0 in the
|
|
2651
2790
|
* shader's decompose() function). Padding bytes beyond the actual coefficients in the last
|