@onerjs/core 8.46.1 → 8.46.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.
- package/Collisions/gpuPicker.js +9 -1
- package/Collisions/gpuPicker.js.map +1 -1
- package/Engines/WebGPU/webgpuCacheRenderPipeline.d.ts +20 -0
- package/Engines/WebGPU/webgpuCacheRenderPipeline.js +58 -13
- package/Engines/WebGPU/webgpuCacheRenderPipeline.js.map +1 -1
- package/Engines/WebGPU/webgpuConstants.d.ts +5 -2
- package/Engines/WebGPU/webgpuConstants.js +3 -0
- package/Engines/WebGPU/webgpuConstants.js.map +1 -1
- package/Engines/engine.d.ts +45 -41
- package/Engines/webgpuEngine.d.ts +84 -0
- package/Engines/webgpuEngine.js +80 -1
- package/Engines/webgpuEngine.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.d.ts +7 -0
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.js +22 -0
- package/Materials/GaussianSplatting/gaussianSplattingGpuPickingMaterialPlugin.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +2 -28
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.d.ts +46 -0
- package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js +56 -0
- package/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh.js.map +1 -0
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.d.ts +104 -463
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js +553 -2018
- package/Meshes/GaussianSplatting/gaussianSplattingMesh.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.d.ts +554 -0
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +2017 -0
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -0
- package/Meshes/index.d.ts +2 -0
- package/Meshes/index.js +2 -0
- package/Meshes/index.js.map +1 -1
- package/Rendering/depthRenderer.js +2 -1
- package/Rendering/depthRenderer.js.map +1 -1
- package/package.json +1 -1
- package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.d.ts +0 -5
- package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.js +0 -35
- package/Shaders/ShadersInclude/openpbrBlockAmbientOcclusion.js.map +0 -1
- package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.d.ts +0 -5
- package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.js +0 -36
- package/ShadersWGSL/ShadersInclude/openpbrBlockAmbientOcclusion.js.map +0 -1
|
@@ -1,2155 +1,690 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Matrix, TmpVectors, Vector2, Vector3, Quaternion } from "../../Maths/math.vector.js";
|
|
5
|
-
import { Logger } from "../../Misc/logger.js";
|
|
6
|
-
import { GaussianSplattingMaterial, GetGaussianSplattingMaxPartCount } from "../../Materials/GaussianSplatting/gaussianSplattingMaterial.js";
|
|
1
|
+
import { Quaternion, Vector3 } from "../../Maths/math.vector.js";
|
|
2
|
+
import { GetGaussianSplattingMaxPartCount } from "../../Materials/GaussianSplatting/gaussianSplattingMaterial.js";
|
|
3
|
+
import { GaussianSplattingMeshBase } from "./gaussianSplattingMeshBase.js";
|
|
7
4
|
import { RawTexture } from "../../Materials/Textures/rawTexture.js";
|
|
8
5
|
|
|
9
6
|
import "../thinInstanceMesh.js";
|
|
10
|
-
import { ToHalfFloat } from "../../Misc/textureTools.js";
|
|
11
|
-
import { Scalar } from "../../Maths/math.scalar.js";
|
|
12
|
-
import { runCoroutineSync, runCoroutineAsync, createYieldingScheduler } from "../../Misc/coroutine.js";
|
|
13
|
-
import { EngineStore } from "../../Engines/engineStore.js";
|
|
14
|
-
import { ImportMeshAsync } from "../../Loading/sceneLoader.js";
|
|
15
7
|
import { GaussianSplattingPartProxyMesh } from "./gaussianSplattingPartProxyMesh.js";
|
|
16
|
-
const IsNative = typeof _native !== "undefined";
|
|
17
|
-
const Native = IsNative ? _native : null;
|
|
18
|
-
// @internal
|
|
19
|
-
const UnpackUnorm = (value, bits) => {
|
|
20
|
-
const t = (1 << bits) - 1;
|
|
21
|
-
return (value & t) / t;
|
|
22
|
-
};
|
|
23
|
-
// @internal
|
|
24
|
-
const Unpack111011 = (value, result) => {
|
|
25
|
-
result.x = UnpackUnorm(value >>> 21, 11);
|
|
26
|
-
result.y = UnpackUnorm(value >>> 11, 10);
|
|
27
|
-
result.z = UnpackUnorm(value, 11);
|
|
28
|
-
};
|
|
29
|
-
// @internal
|
|
30
|
-
const Unpack8888 = (value, result) => {
|
|
31
|
-
result[0] = UnpackUnorm(value >>> 24, 8) * 255;
|
|
32
|
-
result[1] = UnpackUnorm(value >>> 16, 8) * 255;
|
|
33
|
-
result[2] = UnpackUnorm(value >>> 8, 8) * 255;
|
|
34
|
-
result[3] = UnpackUnorm(value, 8) * 255;
|
|
35
|
-
};
|
|
36
|
-
// @internal
|
|
37
|
-
// unpack quaternion with 2,10,10,10 format (largest element, 3x10bit element)
|
|
38
|
-
const UnpackRot = (value, result) => {
|
|
39
|
-
const norm = 1.0 / (Math.sqrt(2) * 0.5);
|
|
40
|
-
const a = (UnpackUnorm(value >>> 20, 10) - 0.5) * norm;
|
|
41
|
-
const b = (UnpackUnorm(value >>> 10, 10) - 0.5) * norm;
|
|
42
|
-
const c = (UnpackUnorm(value, 10) - 0.5) * norm;
|
|
43
|
-
const m = Math.sqrt(1.0 - (a * a + b * b + c * c));
|
|
44
|
-
switch (value >>> 30) {
|
|
45
|
-
case 0:
|
|
46
|
-
result.set(m, a, b, c);
|
|
47
|
-
break;
|
|
48
|
-
case 1:
|
|
49
|
-
result.set(a, m, b, c);
|
|
50
|
-
break;
|
|
51
|
-
case 2:
|
|
52
|
-
result.set(a, b, m, c);
|
|
53
|
-
break;
|
|
54
|
-
case 3:
|
|
55
|
-
result.set(a, b, c, m);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Representation of the types
|
|
61
|
-
*/
|
|
62
|
-
var PLYType;
|
|
63
|
-
(function (PLYType) {
|
|
64
|
-
PLYType[PLYType["FLOAT"] = 0] = "FLOAT";
|
|
65
|
-
PLYType[PLYType["INT"] = 1] = "INT";
|
|
66
|
-
PLYType[PLYType["UINT"] = 2] = "UINT";
|
|
67
|
-
PLYType[PLYType["DOUBLE"] = 3] = "DOUBLE";
|
|
68
|
-
PLYType[PLYType["UCHAR"] = 4] = "UCHAR";
|
|
69
|
-
PLYType[PLYType["UNDEFINED"] = 5] = "UNDEFINED";
|
|
70
|
-
})(PLYType || (PLYType = {}));
|
|
71
|
-
/**
|
|
72
|
-
* Usage types of the PLY values
|
|
73
|
-
*/
|
|
74
|
-
var PLYValue;
|
|
75
|
-
(function (PLYValue) {
|
|
76
|
-
PLYValue[PLYValue["MIN_X"] = 0] = "MIN_X";
|
|
77
|
-
PLYValue[PLYValue["MIN_Y"] = 1] = "MIN_Y";
|
|
78
|
-
PLYValue[PLYValue["MIN_Z"] = 2] = "MIN_Z";
|
|
79
|
-
PLYValue[PLYValue["MAX_X"] = 3] = "MAX_X";
|
|
80
|
-
PLYValue[PLYValue["MAX_Y"] = 4] = "MAX_Y";
|
|
81
|
-
PLYValue[PLYValue["MAX_Z"] = 5] = "MAX_Z";
|
|
82
|
-
PLYValue[PLYValue["MIN_SCALE_X"] = 6] = "MIN_SCALE_X";
|
|
83
|
-
PLYValue[PLYValue["MIN_SCALE_Y"] = 7] = "MIN_SCALE_Y";
|
|
84
|
-
PLYValue[PLYValue["MIN_SCALE_Z"] = 8] = "MIN_SCALE_Z";
|
|
85
|
-
PLYValue[PLYValue["MAX_SCALE_X"] = 9] = "MAX_SCALE_X";
|
|
86
|
-
PLYValue[PLYValue["MAX_SCALE_Y"] = 10] = "MAX_SCALE_Y";
|
|
87
|
-
PLYValue[PLYValue["MAX_SCALE_Z"] = 11] = "MAX_SCALE_Z";
|
|
88
|
-
PLYValue[PLYValue["PACKED_POSITION"] = 12] = "PACKED_POSITION";
|
|
89
|
-
PLYValue[PLYValue["PACKED_ROTATION"] = 13] = "PACKED_ROTATION";
|
|
90
|
-
PLYValue[PLYValue["PACKED_SCALE"] = 14] = "PACKED_SCALE";
|
|
91
|
-
PLYValue[PLYValue["PACKED_COLOR"] = 15] = "PACKED_COLOR";
|
|
92
|
-
PLYValue[PLYValue["X"] = 16] = "X";
|
|
93
|
-
PLYValue[PLYValue["Y"] = 17] = "Y";
|
|
94
|
-
PLYValue[PLYValue["Z"] = 18] = "Z";
|
|
95
|
-
PLYValue[PLYValue["SCALE_0"] = 19] = "SCALE_0";
|
|
96
|
-
PLYValue[PLYValue["SCALE_1"] = 20] = "SCALE_1";
|
|
97
|
-
PLYValue[PLYValue["SCALE_2"] = 21] = "SCALE_2";
|
|
98
|
-
PLYValue[PLYValue["DIFFUSE_RED"] = 22] = "DIFFUSE_RED";
|
|
99
|
-
PLYValue[PLYValue["DIFFUSE_GREEN"] = 23] = "DIFFUSE_GREEN";
|
|
100
|
-
PLYValue[PLYValue["DIFFUSE_BLUE"] = 24] = "DIFFUSE_BLUE";
|
|
101
|
-
PLYValue[PLYValue["OPACITY"] = 25] = "OPACITY";
|
|
102
|
-
PLYValue[PLYValue["F_DC_0"] = 26] = "F_DC_0";
|
|
103
|
-
PLYValue[PLYValue["F_DC_1"] = 27] = "F_DC_1";
|
|
104
|
-
PLYValue[PLYValue["F_DC_2"] = 28] = "F_DC_2";
|
|
105
|
-
PLYValue[PLYValue["F_DC_3"] = 29] = "F_DC_3";
|
|
106
|
-
PLYValue[PLYValue["ROT_0"] = 30] = "ROT_0";
|
|
107
|
-
PLYValue[PLYValue["ROT_1"] = 31] = "ROT_1";
|
|
108
|
-
PLYValue[PLYValue["ROT_2"] = 32] = "ROT_2";
|
|
109
|
-
PLYValue[PLYValue["ROT_3"] = 33] = "ROT_3";
|
|
110
|
-
PLYValue[PLYValue["MIN_COLOR_R"] = 34] = "MIN_COLOR_R";
|
|
111
|
-
PLYValue[PLYValue["MIN_COLOR_G"] = 35] = "MIN_COLOR_G";
|
|
112
|
-
PLYValue[PLYValue["MIN_COLOR_B"] = 36] = "MIN_COLOR_B";
|
|
113
|
-
PLYValue[PLYValue["MAX_COLOR_R"] = 37] = "MAX_COLOR_R";
|
|
114
|
-
PLYValue[PLYValue["MAX_COLOR_G"] = 38] = "MAX_COLOR_G";
|
|
115
|
-
PLYValue[PLYValue["MAX_COLOR_B"] = 39] = "MAX_COLOR_B";
|
|
116
|
-
PLYValue[PLYValue["SH_0"] = 40] = "SH_0";
|
|
117
|
-
PLYValue[PLYValue["SH_1"] = 41] = "SH_1";
|
|
118
|
-
PLYValue[PLYValue["SH_2"] = 42] = "SH_2";
|
|
119
|
-
PLYValue[PLYValue["SH_3"] = 43] = "SH_3";
|
|
120
|
-
PLYValue[PLYValue["SH_4"] = 44] = "SH_4";
|
|
121
|
-
PLYValue[PLYValue["SH_5"] = 45] = "SH_5";
|
|
122
|
-
PLYValue[PLYValue["SH_6"] = 46] = "SH_6";
|
|
123
|
-
PLYValue[PLYValue["SH_7"] = 47] = "SH_7";
|
|
124
|
-
PLYValue[PLYValue["SH_8"] = 48] = "SH_8";
|
|
125
|
-
PLYValue[PLYValue["SH_9"] = 49] = "SH_9";
|
|
126
|
-
PLYValue[PLYValue["SH_10"] = 50] = "SH_10";
|
|
127
|
-
PLYValue[PLYValue["SH_11"] = 51] = "SH_11";
|
|
128
|
-
PLYValue[PLYValue["SH_12"] = 52] = "SH_12";
|
|
129
|
-
PLYValue[PLYValue["SH_13"] = 53] = "SH_13";
|
|
130
|
-
PLYValue[PLYValue["SH_14"] = 54] = "SH_14";
|
|
131
|
-
PLYValue[PLYValue["SH_15"] = 55] = "SH_15";
|
|
132
|
-
PLYValue[PLYValue["SH_16"] = 56] = "SH_16";
|
|
133
|
-
PLYValue[PLYValue["SH_17"] = 57] = "SH_17";
|
|
134
|
-
PLYValue[PLYValue["SH_18"] = 58] = "SH_18";
|
|
135
|
-
PLYValue[PLYValue["SH_19"] = 59] = "SH_19";
|
|
136
|
-
PLYValue[PLYValue["SH_20"] = 60] = "SH_20";
|
|
137
|
-
PLYValue[PLYValue["SH_21"] = 61] = "SH_21";
|
|
138
|
-
PLYValue[PLYValue["SH_22"] = 62] = "SH_22";
|
|
139
|
-
PLYValue[PLYValue["SH_23"] = 63] = "SH_23";
|
|
140
|
-
PLYValue[PLYValue["SH_24"] = 64] = "SH_24";
|
|
141
|
-
PLYValue[PLYValue["SH_25"] = 65] = "SH_25";
|
|
142
|
-
PLYValue[PLYValue["SH_26"] = 66] = "SH_26";
|
|
143
|
-
PLYValue[PLYValue["SH_27"] = 67] = "SH_27";
|
|
144
|
-
PLYValue[PLYValue["SH_28"] = 68] = "SH_28";
|
|
145
|
-
PLYValue[PLYValue["SH_29"] = 69] = "SH_29";
|
|
146
|
-
PLYValue[PLYValue["SH_30"] = 70] = "SH_30";
|
|
147
|
-
PLYValue[PLYValue["SH_31"] = 71] = "SH_31";
|
|
148
|
-
PLYValue[PLYValue["SH_32"] = 72] = "SH_32";
|
|
149
|
-
PLYValue[PLYValue["SH_33"] = 73] = "SH_33";
|
|
150
|
-
PLYValue[PLYValue["SH_34"] = 74] = "SH_34";
|
|
151
|
-
PLYValue[PLYValue["SH_35"] = 75] = "SH_35";
|
|
152
|
-
PLYValue[PLYValue["SH_36"] = 76] = "SH_36";
|
|
153
|
-
PLYValue[PLYValue["SH_37"] = 77] = "SH_37";
|
|
154
|
-
PLYValue[PLYValue["SH_38"] = 78] = "SH_38";
|
|
155
|
-
PLYValue[PLYValue["SH_39"] = 79] = "SH_39";
|
|
156
|
-
PLYValue[PLYValue["SH_40"] = 80] = "SH_40";
|
|
157
|
-
PLYValue[PLYValue["SH_41"] = 81] = "SH_41";
|
|
158
|
-
PLYValue[PLYValue["SH_42"] = 82] = "SH_42";
|
|
159
|
-
PLYValue[PLYValue["SH_43"] = 83] = "SH_43";
|
|
160
|
-
PLYValue[PLYValue["SH_44"] = 84] = "SH_44";
|
|
161
|
-
PLYValue[PLYValue["UNDEFINED"] = 85] = "UNDEFINED";
|
|
162
|
-
})(PLYValue || (PLYValue = {}));
|
|
163
8
|
/**
|
|
164
|
-
* Class used to render a
|
|
9
|
+
* Class used to render a Gaussian Splatting mesh. Supports both single-cloud and compound
|
|
10
|
+
* (multi-part) rendering. In compound mode, multiple Gaussian Splatting source meshes are
|
|
11
|
+
* merged into one draw call while retaining per-part world-matrix control via
|
|
12
|
+
* addPart/addParts and removePart.
|
|
165
13
|
*/
|
|
166
|
-
export class GaussianSplattingMesh extends
|
|
14
|
+
export class GaussianSplattingMesh extends GaussianSplattingMeshBase {
|
|
167
15
|
/**
|
|
168
|
-
*
|
|
16
|
+
* Creates a new GaussianSplattingMesh
|
|
17
|
+
* @param name the name of the mesh
|
|
18
|
+
* @param url optional URL to load a Gaussian Splatting file from
|
|
19
|
+
* @param scene the hosting scene
|
|
20
|
+
* @param keepInRam whether to keep the raw splat data in RAM after uploading to GPU
|
|
169
21
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
22
|
+
constructor(name, url = null, scene = null, keepInRam = false) {
|
|
23
|
+
super(name, url, scene, keepInRam);
|
|
24
|
+
/**
|
|
25
|
+
* Proxy meshes indexed by part index. Maintained in sync with _partMatrices.
|
|
26
|
+
*/
|
|
27
|
+
this._partProxies = [];
|
|
28
|
+
/**
|
|
29
|
+
* World matrices for each part, indexed by part index.
|
|
30
|
+
*/
|
|
31
|
+
this._partMatrices = [];
|
|
32
|
+
/** When true, suppresses the sort trigger inside setWorldMatrixForPart during batch rebuilds. */
|
|
33
|
+
this._rebuilding = false;
|
|
34
|
+
/**
|
|
35
|
+
* Visibility values for each part (0.0 to 1.0), indexed by part index.
|
|
36
|
+
*/
|
|
37
|
+
this._partVisibility = [];
|
|
38
|
+
this._partIndicesTexture = null;
|
|
39
|
+
this._partIndices = null;
|
|
40
|
+
// Ensure _splatsData is retained once compound mode is entered — addPart/addParts need
|
|
41
|
+
// the source data for full-texture rebuilds. Set after super() so it is visible to
|
|
42
|
+
// _updateData when the async load completes.
|
|
43
|
+
this._alwaysRetainSplatsData = true;
|
|
184
44
|
}
|
|
185
45
|
/**
|
|
186
|
-
*
|
|
187
|
-
* @
|
|
46
|
+
* Returns the class name
|
|
47
|
+
* @returns "GaussianSplattingMesh"
|
|
188
48
|
*/
|
|
189
|
-
|
|
190
|
-
return
|
|
49
|
+
getClassName() {
|
|
50
|
+
return "GaussianSplattingMesh";
|
|
191
51
|
}
|
|
192
52
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
53
|
+
* Disposes proxy meshes and clears part data in addition to the base class GPU resources.
|
|
54
|
+
* @param doNotRecurse Set to true to not recurse into each children
|
|
195
55
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
set shDegree(value) {
|
|
200
|
-
const maxDegree = this._shTextures?.length ?? 0;
|
|
201
|
-
const clamped = Math.max(0, Math.min(Math.round(value), maxDegree));
|
|
202
|
-
if (this._shDegree === clamped) {
|
|
203
|
-
return;
|
|
56
|
+
dispose(doNotRecurse) {
|
|
57
|
+
for (const proxy of this._partProxies) {
|
|
58
|
+
proxy.dispose();
|
|
204
59
|
}
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Number of splats in the mesh
|
|
216
|
-
*/
|
|
217
|
-
get splatCount() {
|
|
218
|
-
return this._splatIndex?.length;
|
|
60
|
+
if (this._partIndicesTexture) {
|
|
61
|
+
this._partIndicesTexture.dispose();
|
|
62
|
+
}
|
|
63
|
+
this._partProxies = [];
|
|
64
|
+
this._partMatrices = [];
|
|
65
|
+
this._partVisibility = [];
|
|
66
|
+
this._partIndicesTexture = null;
|
|
67
|
+
super.dispose(doNotRecurse);
|
|
219
68
|
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Worker and material hooks
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
220
72
|
/**
|
|
221
|
-
*
|
|
73
|
+
* Posts the initial per-part data to the sort worker after it has been created.
|
|
74
|
+
* Sends the current part matrices and group index array so the worker can correctly
|
|
75
|
+
* weight depth values per part.
|
|
76
|
+
* @param worker the newly created sort worker
|
|
222
77
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
78
|
+
_onWorkerCreated(worker) {
|
|
79
|
+
worker.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
|
|
80
|
+
worker.postMessage({ partIndices: this._partIndices ? new Uint8Array(this._partIndices) : null });
|
|
225
81
|
}
|
|
226
82
|
/**
|
|
227
|
-
*
|
|
83
|
+
* Stores the raw part index array, padded to texture length, so the worker and GPU texture
|
|
84
|
+
* creation step have access to it.
|
|
85
|
+
* @param partIndices - the raw part indices array received during a data load
|
|
86
|
+
* @param textureLength - the padded texture length to allocate into
|
|
228
87
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
88
|
+
_onIndexDataReceived(partIndices, textureLength) {
|
|
89
|
+
this._partIndices = new Uint8Array(textureLength);
|
|
90
|
+
this._partIndices.set(partIndices);
|
|
231
91
|
}
|
|
232
92
|
/**
|
|
233
|
-
*
|
|
93
|
+
* Returns `true` when at least one part has been added to this compound mesh.
|
|
94
|
+
* Returns `false` before any parts are added, so the mesh renders in normal
|
|
95
|
+
* (non-compound) mode until the first addPart/addParts call. This matches the
|
|
96
|
+
* old base-class behavior of `this._partMatrices.length > 0` and avoids
|
|
97
|
+
* binding unset partWorld uniforms (which would cause division-by-zero in the
|
|
98
|
+
* Gaussian projection Jacobian and produce huge distorted splats).
|
|
99
|
+
* @internal
|
|
234
100
|
*/
|
|
235
101
|
get isCompound() {
|
|
236
102
|
return this._partMatrices.length > 0;
|
|
237
103
|
}
|
|
238
104
|
/**
|
|
239
|
-
*
|
|
105
|
+
* During a removePart rebuild, keep the existing sort worker alive rather than
|
|
106
|
+
* tearing it down and spinning up a new one. This avoids startup latency and the
|
|
107
|
+
* transient state window where a stale sort could fire against an incomplete
|
|
108
|
+
* partMatrices array.
|
|
109
|
+
* Outside of a rebuild the base-class behaviour is used unchanged.
|
|
240
110
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
*/
|
|
247
|
-
get partIndicesTexture() {
|
|
248
|
-
return this._partIndicesTexture;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Gets the part visibility array, if the mesh is a compound
|
|
252
|
-
*/
|
|
253
|
-
get partVisibility() {
|
|
254
|
-
return this._partVisibility;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Gets the covariancesA texture
|
|
258
|
-
*/
|
|
259
|
-
get covariancesATexture() {
|
|
260
|
-
return this._covariancesATexture;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Gets the covariancesB texture
|
|
264
|
-
*/
|
|
265
|
-
get covariancesBTexture() {
|
|
266
|
-
return this._covariancesBTexture;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Gets the centers texture
|
|
270
|
-
*/
|
|
271
|
-
get centersTexture() {
|
|
272
|
-
return this._centersTexture;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Gets the colors texture
|
|
276
|
-
*/
|
|
277
|
-
get colorsTexture() {
|
|
278
|
-
return this._colorsTexture;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Gets the SH textures
|
|
282
|
-
*/
|
|
283
|
-
get shTextures() {
|
|
284
|
-
return this._shTextures;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Gets the kernel size
|
|
288
|
-
* Documentation and mathematical explanations here:
|
|
289
|
-
* https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
|
|
290
|
-
* https://github.com/autonomousvision/mip-splatting/issues/18#issuecomment-1929388931
|
|
291
|
-
*/
|
|
292
|
-
get kernelSize() {
|
|
293
|
-
return this._material instanceof GaussianSplattingMaterial ? this._material.kernelSize : 0;
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Get the compensation state
|
|
297
|
-
*/
|
|
298
|
-
get compensation() {
|
|
299
|
-
return this._material instanceof GaussianSplattingMaterial ? this._material.compensation : false;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* set rendering material
|
|
303
|
-
*/
|
|
304
|
-
set material(value) {
|
|
305
|
-
this._material = value;
|
|
306
|
-
this._material.backFaceCulling = false;
|
|
307
|
-
this._material.cullBackFaces = false;
|
|
308
|
-
value.resetDrawCache();
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* get rendering material
|
|
312
|
-
*/
|
|
313
|
-
get material() {
|
|
314
|
-
return this._material;
|
|
315
|
-
}
|
|
316
|
-
static _MakeSplatGeometryForMesh(mesh) {
|
|
317
|
-
const vertexData = new VertexData();
|
|
318
|
-
const originPositions = [-2, -2, 0, 2, -2, 0, 2, 2, 0, -2, 2, 0];
|
|
319
|
-
const originIndices = [0, 1, 2, 0, 2, 3];
|
|
320
|
-
const positions = [];
|
|
321
|
-
const indices = [];
|
|
322
|
-
for (let i = 0; i < GaussianSplattingMesh._BatchSize; i++) {
|
|
323
|
-
for (let j = 0; j < 12; j++) {
|
|
324
|
-
if (j == 2 || j == 5 || j == 8 || j == 11) {
|
|
325
|
-
positions.push(i); // local splat index
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
positions.push(originPositions[j]);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
indices.push(originIndices.map((v) => v + i * 4));
|
|
332
|
-
}
|
|
333
|
-
vertexData.positions = positions;
|
|
334
|
-
vertexData.indices = indices.flat();
|
|
335
|
-
vertexData.applyToMesh(mesh);
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Creates a new gaussian splatting mesh
|
|
339
|
-
* @param name defines the name of the mesh
|
|
340
|
-
* @param url defines the url to load from (optional)
|
|
341
|
-
* @param scene defines the hosting scene (optional)
|
|
342
|
-
* @param keepInRam keep datas in ram for editing purpose
|
|
343
|
-
*/
|
|
344
|
-
constructor(name, url = null, scene = null, keepInRam = false) {
|
|
345
|
-
super(name, scene);
|
|
346
|
-
this._vertexCount = 0;
|
|
347
|
-
this._worker = null;
|
|
348
|
-
this._modelViewProjectionMatrix = Matrix.Identity();
|
|
349
|
-
this._canPostToWorker = true;
|
|
350
|
-
this._readyToDisplay = false;
|
|
351
|
-
this._covariancesATexture = null;
|
|
352
|
-
this._covariancesBTexture = null;
|
|
353
|
-
this._centersTexture = null;
|
|
354
|
-
this._colorsTexture = null;
|
|
355
|
-
this._splatPositions = null;
|
|
356
|
-
this._splatIndex = null;
|
|
357
|
-
this._shTextures = null;
|
|
358
|
-
this._splatsData = null;
|
|
359
|
-
this._shData = null;
|
|
360
|
-
this._partIndicesTexture = null;
|
|
361
|
-
this._partIndices = null;
|
|
362
|
-
this._partMatrices = [];
|
|
363
|
-
this._partVisibility = [];
|
|
364
|
-
this._partProxies = new Map();
|
|
365
|
-
this._textureSize = new Vector2(0, 0);
|
|
366
|
-
this._keepInRam = false;
|
|
367
|
-
this._delayedTextureUpdate = null;
|
|
368
|
-
this._useRGBACovariants = false;
|
|
369
|
-
this._material = null;
|
|
370
|
-
this._tmpCovariances = [0, 0, 0, 0, 0, 0];
|
|
371
|
-
this._sortIsDirty = false;
|
|
372
|
-
this._shDegree = 0;
|
|
373
|
-
this._cameraViewInfos = new Map();
|
|
374
|
-
/**
|
|
375
|
-
* Cosine value of the angle threshold to update view dependent splat sorting. Default is 0.0001.
|
|
376
|
-
*/
|
|
377
|
-
this.viewUpdateThreshold = GaussianSplattingMesh._DefaultViewUpdateThreshold;
|
|
378
|
-
this._disableDepthSort = false;
|
|
379
|
-
this._loadingPromise = null;
|
|
380
|
-
this.subMeshes = [];
|
|
381
|
-
new SubMesh(0, 0, 4 * GaussianSplattingMesh._BatchSize, 0, 6 * GaussianSplattingMesh._BatchSize, this);
|
|
382
|
-
this.setEnabled(false);
|
|
383
|
-
// webGL2 and webGPU support for RG texture with float16 is fine. not webGL1
|
|
384
|
-
this._useRGBACovariants = !this.getEngine().isWebGPU && this.getEngine().version === 1.0;
|
|
385
|
-
this._keepInRam = keepInRam;
|
|
386
|
-
if (url) {
|
|
387
|
-
this._loadingPromise = this.loadFileAsync(url);
|
|
111
|
+
_instantiateWorker() {
|
|
112
|
+
if (this._rebuilding && this._worker) {
|
|
113
|
+
// Worker already exists and is kept alive; just resize the splat-index buffer.
|
|
114
|
+
this._updateSplatIndexBuffer(this._vertexCount);
|
|
115
|
+
return;
|
|
388
116
|
}
|
|
389
|
-
|
|
390
|
-
gaussianSplattingMaterial.setSourceMesh(this);
|
|
391
|
-
this._material = gaussianSplattingMaterial;
|
|
392
|
-
// delete meshes created for cameras on camera removal
|
|
393
|
-
this._scene.onCameraRemovedObservable.add((camera) => {
|
|
394
|
-
const cameraId = camera.uniqueId;
|
|
395
|
-
// delete mesh for this camera
|
|
396
|
-
if (this._cameraViewInfos.has(cameraId)) {
|
|
397
|
-
const cameraViewInfos = this._cameraViewInfos.get(cameraId);
|
|
398
|
-
cameraViewInfos?.mesh.dispose();
|
|
399
|
-
this._cameraViewInfos.delete(cameraId);
|
|
400
|
-
}
|
|
401
|
-
});
|
|
117
|
+
super._instantiateWorker();
|
|
402
118
|
}
|
|
403
119
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
120
|
+
* Ensures the part-index GPU texture exists at the start of an incremental update.
|
|
121
|
+
* Called before the sub-texture upload so the correct texture is available for the first batch.
|
|
122
|
+
* @param textureSize - current texture dimensions
|
|
406
123
|
*/
|
|
407
|
-
|
|
408
|
-
|
|
124
|
+
_onIncrementalUpdateStart(textureSize) {
|
|
125
|
+
this._ensurePartIndicesTexture(textureSize, this._partIndices ?? undefined);
|
|
409
126
|
}
|
|
410
127
|
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
128
|
+
* Posts positions (via super) and then additionally posts the current part-index array
|
|
129
|
+
* to the sort worker so it can associate each splat with its part.
|
|
413
130
|
*/
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
* Returns the total number of vertices (splats) within the mesh
|
|
419
|
-
* @returns the total number of vertices
|
|
420
|
-
*/
|
|
421
|
-
getTotalVertices() {
|
|
422
|
-
return this._vertexCount;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Is this node ready to be used/rendered
|
|
426
|
-
* @param completeCheck defines if a complete check (including materials and lights) has to be done (false by default)
|
|
427
|
-
* @returns true when ready
|
|
428
|
-
*/
|
|
429
|
-
isReady(completeCheck = false) {
|
|
430
|
-
if (!super.isReady(completeCheck, true)) {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
if (!this._readyToDisplay) {
|
|
434
|
-
// mesh is ready when worker has done at least 1 sorting
|
|
435
|
-
this._postToWorker(true);
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
return true;
|
|
439
|
-
}
|
|
440
|
-
_getCameraDirection(camera) {
|
|
441
|
-
const cameraViewMatrix = camera.getViewMatrix();
|
|
442
|
-
const cameraProjectionMatrix = camera.getProjectionMatrix();
|
|
443
|
-
const cameraViewProjectionMatrix = TmpVectors.Matrix[0];
|
|
444
|
-
cameraViewMatrix.multiplyToRef(cameraProjectionMatrix, cameraViewProjectionMatrix);
|
|
445
|
-
const modelMatrix = this.getWorldMatrix();
|
|
446
|
-
const modelViewMatrix = TmpVectors.Matrix[1];
|
|
447
|
-
modelMatrix.multiplyToRef(cameraViewMatrix, modelViewMatrix);
|
|
448
|
-
modelMatrix.multiplyToRef(cameraViewProjectionMatrix, this._modelViewProjectionMatrix);
|
|
449
|
-
// return vector used to compute distance to camera
|
|
450
|
-
const localDirection = TmpVectors.Vector3[1];
|
|
451
|
-
localDirection.set(modelViewMatrix.m[2], modelViewMatrix.m[6], modelViewMatrix.m[10]);
|
|
452
|
-
localDirection.normalize();
|
|
453
|
-
return localDirection;
|
|
454
|
-
}
|
|
455
|
-
/** @internal */
|
|
456
|
-
_postToWorker(forced = false) {
|
|
457
|
-
const scene = this._scene;
|
|
458
|
-
const frameId = scene.getFrameId();
|
|
459
|
-
// force update or at least frame update for camera is outdated
|
|
460
|
-
let outdated = false;
|
|
461
|
-
this._cameraViewInfos.forEach((cameraViewInfos) => {
|
|
462
|
-
if (cameraViewInfos.frameIdLastUpdate !== frameId) {
|
|
463
|
-
outdated = true;
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
// array of cameras used for rendering
|
|
467
|
-
const cameras = this._scene.activeCameras?.length ? this._scene.activeCameras : [this._scene.activeCamera];
|
|
468
|
-
// list view infos for active cameras
|
|
469
|
-
const activeViewInfos = [];
|
|
470
|
-
cameras.forEach((camera) => {
|
|
471
|
-
if (!camera) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const cameraId = camera.uniqueId;
|
|
475
|
-
const cameraViewInfos = this._cameraViewInfos.get(cameraId);
|
|
476
|
-
if (cameraViewInfos) {
|
|
477
|
-
activeViewInfos.push(cameraViewInfos);
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
// mesh doesn't exist yet for this camera
|
|
481
|
-
const cameraMesh = new Mesh(this.name + "_cameraMesh_" + cameraId, this._scene);
|
|
482
|
-
// not visible with inspector or the scene graph
|
|
483
|
-
cameraMesh.reservedDataStore = { hidden: true };
|
|
484
|
-
cameraMesh.setEnabled(false);
|
|
485
|
-
cameraMesh.material = this.material;
|
|
486
|
-
if (cameraMesh.material && cameraMesh.material instanceof GaussianSplattingMaterial) {
|
|
487
|
-
const gsMaterial = cameraMesh.material;
|
|
488
|
-
// GaussianSplattingMaterial source mesh may not have been set yet.
|
|
489
|
-
// This happens for cloned resources from asset containers for instance,
|
|
490
|
-
// where material is cloned before mesh.
|
|
491
|
-
if (!gsMaterial.getSourceMesh()) {
|
|
492
|
-
gsMaterial.setSourceMesh(this);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
GaussianSplattingMesh._MakeSplatGeometryForMesh(cameraMesh);
|
|
496
|
-
const newViewInfos = {
|
|
497
|
-
camera: camera,
|
|
498
|
-
cameraDirection: new Vector3(0, 0, 0),
|
|
499
|
-
mesh: cameraMesh,
|
|
500
|
-
frameIdLastUpdate: frameId,
|
|
501
|
-
splatIndexBufferSet: false,
|
|
502
|
-
};
|
|
503
|
-
activeViewInfos.push(newViewInfos);
|
|
504
|
-
this._cameraViewInfos.set(cameraId, newViewInfos);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
// sort view infos by last updated frame id: first item is the least recently updated
|
|
508
|
-
activeViewInfos.sort((a, b) => a.frameIdLastUpdate - b.frameIdLastUpdate);
|
|
509
|
-
const hasSortFunction = this._worker || Native?.sortSplats || this._disableDepthSort;
|
|
510
|
-
if ((forced || outdated) && hasSortFunction && (this._scene.activeCameras?.length || this._scene.activeCamera) && this._canPostToWorker) {
|
|
511
|
-
// view infos sorted by least recent updated frame id
|
|
512
|
-
activeViewInfos.forEach((cameraViewInfos) => {
|
|
513
|
-
const camera = cameraViewInfos.camera;
|
|
514
|
-
const cameraDirection = this._getCameraDirection(camera);
|
|
515
|
-
const previousCameraDirection = cameraViewInfos.cameraDirection;
|
|
516
|
-
const dot = Vector3.Dot(cameraDirection, previousCameraDirection);
|
|
517
|
-
if ((forced || Math.abs(dot - 1) >= this.viewUpdateThreshold) && this._canPostToWorker) {
|
|
518
|
-
cameraViewInfos.cameraDirection.copyFrom(cameraDirection);
|
|
519
|
-
cameraViewInfos.frameIdLastUpdate = frameId;
|
|
520
|
-
this._canPostToWorker = false;
|
|
521
|
-
if (this._worker) {
|
|
522
|
-
const cameraViewMatrix = camera.getViewMatrix();
|
|
523
|
-
this._worker.postMessage({
|
|
524
|
-
worldMatrix: this.getWorldMatrix().m,
|
|
525
|
-
cameraForward: [cameraViewMatrix.m[2], cameraViewMatrix.m[6], cameraViewMatrix.m[10]],
|
|
526
|
-
cameraPosition: [camera.globalPosition.x, camera.globalPosition.y, camera.globalPosition.z],
|
|
527
|
-
depthMix: this._depthMix,
|
|
528
|
-
cameraId: camera.uniqueId,
|
|
529
|
-
}, [this._depthMix.buffer]);
|
|
530
|
-
}
|
|
531
|
-
else if (Native?.sortSplats) {
|
|
532
|
-
Native.sortSplats(this._modelViewProjectionMatrix, this._splatPositions, this._splatIndex, this._scene.useRightHandedSystem);
|
|
533
|
-
if (cameraViewInfos.splatIndexBufferSet) {
|
|
534
|
-
cameraViewInfos.mesh.thinInstanceBufferUpdated("splatIndex");
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
|
|
538
|
-
cameraViewInfos.splatIndexBufferSet = true;
|
|
539
|
-
}
|
|
540
|
-
this._canPostToWorker = true;
|
|
541
|
-
this._readyToDisplay = true;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
else if (this._disableDepthSort) {
|
|
547
|
-
activeViewInfos.forEach((cameraViewInfos) => {
|
|
548
|
-
if (!cameraViewInfos.splatIndexBufferSet) {
|
|
549
|
-
cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
|
|
550
|
-
cameraViewInfos.splatIndexBufferSet = true;
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
this._canPostToWorker = true;
|
|
554
|
-
this._readyToDisplay = true;
|
|
131
|
+
_notifyWorkerNewData() {
|
|
132
|
+
super._notifyWorkerNewData();
|
|
133
|
+
if (this._worker) {
|
|
134
|
+
this._worker.postMessage({ partIndices: this._partIndices ?? null });
|
|
555
135
|
}
|
|
556
136
|
}
|
|
557
137
|
/**
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
* @param
|
|
561
|
-
* @
|
|
562
|
-
* @returns the current mesh
|
|
138
|
+
* Binds all compound-specific shader uniforms: the group index texture, per-part world
|
|
139
|
+
* matrices, and per-part visibility values.
|
|
140
|
+
* @param effect the shader effect that is being bound
|
|
141
|
+
* @internal
|
|
563
142
|
*/
|
|
564
|
-
|
|
565
|
-
this.
|
|
566
|
-
|
|
567
|
-
if (!this._geometry && this._cameraViewInfos.size) {
|
|
568
|
-
this._geometry = this._cameraViewInfos.values().next().value.mesh.geometry;
|
|
569
|
-
}
|
|
570
|
-
const cameraId = this._scene.activeCamera.uniqueId;
|
|
571
|
-
const cameraViewInfos = this._cameraViewInfos.get(cameraId);
|
|
572
|
-
if (!cameraViewInfos || !cameraViewInfos.splatIndexBufferSet) {
|
|
573
|
-
return this;
|
|
574
|
-
}
|
|
575
|
-
if (this.onBeforeRenderObservable) {
|
|
576
|
-
this.onBeforeRenderObservable.notifyObservers(this);
|
|
577
|
-
}
|
|
578
|
-
const mesh = cameraViewInfos.mesh;
|
|
579
|
-
mesh.getWorldMatrix().copyFrom(this.getWorldMatrix());
|
|
580
|
-
// Propagate render pass material overrides (e.g., GPU picking) to the inner camera mesh.
|
|
581
|
-
// When this mesh is rendered into a RenderTargetTexture with a material override (via setMaterialForRendering),
|
|
582
|
-
// the override is set on this proxy mesh but needs to be applied to the actual camera mesh that does the rendering.
|
|
583
|
-
const engine = this._scene.getEngine();
|
|
584
|
-
const renderPassId = engine.currentRenderPassId;
|
|
585
|
-
const renderPassMaterial = this.getMaterialForRenderPass(renderPassId);
|
|
586
|
-
if (renderPassMaterial) {
|
|
587
|
-
mesh.setMaterialForRenderPass(renderPassId, renderPassMaterial);
|
|
588
|
-
}
|
|
589
|
-
const ret = mesh.render(subMesh, enableAlphaMode, effectiveMeshReplacement);
|
|
590
|
-
// Clean up the temporary override to avoid affecting other render passes
|
|
591
|
-
if (renderPassMaterial) {
|
|
592
|
-
mesh.setMaterialForRenderPass(renderPassId, undefined);
|
|
593
|
-
}
|
|
594
|
-
if (this.onAfterRenderObservable) {
|
|
595
|
-
this.onAfterRenderObservable.notifyObservers(this);
|
|
143
|
+
bindExtraEffectUniforms(effect) {
|
|
144
|
+
if (!this._partIndicesTexture) {
|
|
145
|
+
return;
|
|
596
146
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
case "float":
|
|
602
|
-
return 0 /* PLYType.FLOAT */;
|
|
603
|
-
case "int":
|
|
604
|
-
return 1 /* PLYType.INT */;
|
|
605
|
-
case "uint":
|
|
606
|
-
return 2 /* PLYType.UINT */;
|
|
607
|
-
case "double":
|
|
608
|
-
return 3 /* PLYType.DOUBLE */;
|
|
609
|
-
case "uchar":
|
|
610
|
-
return 4 /* PLYType.UCHAR */;
|
|
147
|
+
effect.setTexture("partIndicesTexture", this._partIndicesTexture);
|
|
148
|
+
const partWorldData = new Float32Array(this.partCount * 16);
|
|
149
|
+
for (let i = 0; i < this.partCount; i++) {
|
|
150
|
+
this._partMatrices[i].toArray(partWorldData, i * 16);
|
|
611
151
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
case "min_x":
|
|
617
|
-
return 0 /* PLYValue.MIN_X */;
|
|
618
|
-
case "min_y":
|
|
619
|
-
return 1 /* PLYValue.MIN_Y */;
|
|
620
|
-
case "min_z":
|
|
621
|
-
return 2 /* PLYValue.MIN_Z */;
|
|
622
|
-
case "max_x":
|
|
623
|
-
return 3 /* PLYValue.MAX_X */;
|
|
624
|
-
case "max_y":
|
|
625
|
-
return 4 /* PLYValue.MAX_Y */;
|
|
626
|
-
case "max_z":
|
|
627
|
-
return 5 /* PLYValue.MAX_Z */;
|
|
628
|
-
case "min_scale_x":
|
|
629
|
-
return 6 /* PLYValue.MIN_SCALE_X */;
|
|
630
|
-
case "min_scale_y":
|
|
631
|
-
return 7 /* PLYValue.MIN_SCALE_Y */;
|
|
632
|
-
case "min_scale_z":
|
|
633
|
-
return 8 /* PLYValue.MIN_SCALE_Z */;
|
|
634
|
-
case "max_scale_x":
|
|
635
|
-
return 9 /* PLYValue.MAX_SCALE_X */;
|
|
636
|
-
case "max_scale_y":
|
|
637
|
-
return 10 /* PLYValue.MAX_SCALE_Y */;
|
|
638
|
-
case "max_scale_z":
|
|
639
|
-
return 11 /* PLYValue.MAX_SCALE_Z */;
|
|
640
|
-
case "packed_position":
|
|
641
|
-
return 12 /* PLYValue.PACKED_POSITION */;
|
|
642
|
-
case "packed_rotation":
|
|
643
|
-
return 13 /* PLYValue.PACKED_ROTATION */;
|
|
644
|
-
case "packed_scale":
|
|
645
|
-
return 14 /* PLYValue.PACKED_SCALE */;
|
|
646
|
-
case "packed_color":
|
|
647
|
-
return 15 /* PLYValue.PACKED_COLOR */;
|
|
648
|
-
case "x":
|
|
649
|
-
return 16 /* PLYValue.X */;
|
|
650
|
-
case "y":
|
|
651
|
-
return 17 /* PLYValue.Y */;
|
|
652
|
-
case "z":
|
|
653
|
-
return 18 /* PLYValue.Z */;
|
|
654
|
-
case "scale_0":
|
|
655
|
-
return 19 /* PLYValue.SCALE_0 */;
|
|
656
|
-
case "scale_1":
|
|
657
|
-
return 20 /* PLYValue.SCALE_1 */;
|
|
658
|
-
case "scale_2":
|
|
659
|
-
return 21 /* PLYValue.SCALE_2 */;
|
|
660
|
-
case "diffuse_red":
|
|
661
|
-
case "red":
|
|
662
|
-
return 22 /* PLYValue.DIFFUSE_RED */;
|
|
663
|
-
case "diffuse_green":
|
|
664
|
-
case "green":
|
|
665
|
-
return 23 /* PLYValue.DIFFUSE_GREEN */;
|
|
666
|
-
case "diffuse_blue":
|
|
667
|
-
case "blue":
|
|
668
|
-
return 24 /* PLYValue.DIFFUSE_BLUE */;
|
|
669
|
-
case "f_dc_0":
|
|
670
|
-
return 26 /* PLYValue.F_DC_0 */;
|
|
671
|
-
case "f_dc_1":
|
|
672
|
-
return 27 /* PLYValue.F_DC_1 */;
|
|
673
|
-
case "f_dc_2":
|
|
674
|
-
return 28 /* PLYValue.F_DC_2 */;
|
|
675
|
-
case "f_dc_3":
|
|
676
|
-
return 29 /* PLYValue.F_DC_3 */;
|
|
677
|
-
case "opacity":
|
|
678
|
-
return 25 /* PLYValue.OPACITY */;
|
|
679
|
-
case "rot_0":
|
|
680
|
-
return 30 /* PLYValue.ROT_0 */;
|
|
681
|
-
case "rot_1":
|
|
682
|
-
return 31 /* PLYValue.ROT_1 */;
|
|
683
|
-
case "rot_2":
|
|
684
|
-
return 32 /* PLYValue.ROT_2 */;
|
|
685
|
-
case "rot_3":
|
|
686
|
-
return 33 /* PLYValue.ROT_3 */;
|
|
687
|
-
case "min_r":
|
|
688
|
-
return 34 /* PLYValue.MIN_COLOR_R */;
|
|
689
|
-
case "min_g":
|
|
690
|
-
return 35 /* PLYValue.MIN_COLOR_G */;
|
|
691
|
-
case "min_b":
|
|
692
|
-
return 36 /* PLYValue.MIN_COLOR_B */;
|
|
693
|
-
case "max_r":
|
|
694
|
-
return 37 /* PLYValue.MAX_COLOR_R */;
|
|
695
|
-
case "max_g":
|
|
696
|
-
return 38 /* PLYValue.MAX_COLOR_G */;
|
|
697
|
-
case "max_b":
|
|
698
|
-
return 39 /* PLYValue.MAX_COLOR_B */;
|
|
699
|
-
case "f_rest_0":
|
|
700
|
-
return 40 /* PLYValue.SH_0 */;
|
|
701
|
-
case "f_rest_1":
|
|
702
|
-
return 41 /* PLYValue.SH_1 */;
|
|
703
|
-
case "f_rest_2":
|
|
704
|
-
return 42 /* PLYValue.SH_2 */;
|
|
705
|
-
case "f_rest_3":
|
|
706
|
-
return 43 /* PLYValue.SH_3 */;
|
|
707
|
-
case "f_rest_4":
|
|
708
|
-
return 44 /* PLYValue.SH_4 */;
|
|
709
|
-
case "f_rest_5":
|
|
710
|
-
return 45 /* PLYValue.SH_5 */;
|
|
711
|
-
case "f_rest_6":
|
|
712
|
-
return 46 /* PLYValue.SH_6 */;
|
|
713
|
-
case "f_rest_7":
|
|
714
|
-
return 47 /* PLYValue.SH_7 */;
|
|
715
|
-
case "f_rest_8":
|
|
716
|
-
return 48 /* PLYValue.SH_8 */;
|
|
717
|
-
case "f_rest_9":
|
|
718
|
-
return 49 /* PLYValue.SH_9 */;
|
|
719
|
-
case "f_rest_10":
|
|
720
|
-
return 50 /* PLYValue.SH_10 */;
|
|
721
|
-
case "f_rest_11":
|
|
722
|
-
return 51 /* PLYValue.SH_11 */;
|
|
723
|
-
case "f_rest_12":
|
|
724
|
-
return 52 /* PLYValue.SH_12 */;
|
|
725
|
-
case "f_rest_13":
|
|
726
|
-
return 53 /* PLYValue.SH_13 */;
|
|
727
|
-
case "f_rest_14":
|
|
728
|
-
return 54 /* PLYValue.SH_14 */;
|
|
729
|
-
case "f_rest_15":
|
|
730
|
-
return 55 /* PLYValue.SH_15 */;
|
|
731
|
-
case "f_rest_16":
|
|
732
|
-
return 56 /* PLYValue.SH_16 */;
|
|
733
|
-
case "f_rest_17":
|
|
734
|
-
return 57 /* PLYValue.SH_17 */;
|
|
735
|
-
case "f_rest_18":
|
|
736
|
-
return 58 /* PLYValue.SH_18 */;
|
|
737
|
-
case "f_rest_19":
|
|
738
|
-
return 59 /* PLYValue.SH_19 */;
|
|
739
|
-
case "f_rest_20":
|
|
740
|
-
return 60 /* PLYValue.SH_20 */;
|
|
741
|
-
case "f_rest_21":
|
|
742
|
-
return 61 /* PLYValue.SH_21 */;
|
|
743
|
-
case "f_rest_22":
|
|
744
|
-
return 62 /* PLYValue.SH_22 */;
|
|
745
|
-
case "f_rest_23":
|
|
746
|
-
return 63 /* PLYValue.SH_23 */;
|
|
747
|
-
case "f_rest_24":
|
|
748
|
-
return 64 /* PLYValue.SH_24 */;
|
|
749
|
-
case "f_rest_25":
|
|
750
|
-
return 65 /* PLYValue.SH_25 */;
|
|
751
|
-
case "f_rest_26":
|
|
752
|
-
return 66 /* PLYValue.SH_26 */;
|
|
753
|
-
case "f_rest_27":
|
|
754
|
-
return 67 /* PLYValue.SH_27 */;
|
|
755
|
-
case "f_rest_28":
|
|
756
|
-
return 68 /* PLYValue.SH_28 */;
|
|
757
|
-
case "f_rest_29":
|
|
758
|
-
return 69 /* PLYValue.SH_29 */;
|
|
759
|
-
case "f_rest_30":
|
|
760
|
-
return 70 /* PLYValue.SH_30 */;
|
|
761
|
-
case "f_rest_31":
|
|
762
|
-
return 71 /* PLYValue.SH_31 */;
|
|
763
|
-
case "f_rest_32":
|
|
764
|
-
return 72 /* PLYValue.SH_32 */;
|
|
765
|
-
case "f_rest_33":
|
|
766
|
-
return 73 /* PLYValue.SH_33 */;
|
|
767
|
-
case "f_rest_34":
|
|
768
|
-
return 74 /* PLYValue.SH_34 */;
|
|
769
|
-
case "f_rest_35":
|
|
770
|
-
return 75 /* PLYValue.SH_35 */;
|
|
771
|
-
case "f_rest_36":
|
|
772
|
-
return 76 /* PLYValue.SH_36 */;
|
|
773
|
-
case "f_rest_37":
|
|
774
|
-
return 77 /* PLYValue.SH_37 */;
|
|
775
|
-
case "f_rest_38":
|
|
776
|
-
return 78 /* PLYValue.SH_38 */;
|
|
777
|
-
case "f_rest_39":
|
|
778
|
-
return 79 /* PLYValue.SH_39 */;
|
|
779
|
-
case "f_rest_40":
|
|
780
|
-
return 80 /* PLYValue.SH_40 */;
|
|
781
|
-
case "f_rest_41":
|
|
782
|
-
return 81 /* PLYValue.SH_41 */;
|
|
783
|
-
case "f_rest_42":
|
|
784
|
-
return 82 /* PLYValue.SH_42 */;
|
|
785
|
-
case "f_rest_43":
|
|
786
|
-
return 83 /* PLYValue.SH_43 */;
|
|
787
|
-
case "f_rest_44":
|
|
788
|
-
return 84 /* PLYValue.SH_44 */;
|
|
152
|
+
effect.setMatrices("partWorld", partWorldData);
|
|
153
|
+
const partVisibilityData = [];
|
|
154
|
+
for (let i = 0; i < this.partCount; i++) {
|
|
155
|
+
partVisibilityData.push(this._partVisibility[i] ?? 1.0);
|
|
789
156
|
}
|
|
790
|
-
|
|
157
|
+
effect.setArray("partVisibility", partVisibilityData);
|
|
791
158
|
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Part matrix / visibility management
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
792
162
|
/**
|
|
793
|
-
*
|
|
794
|
-
* @param data the loaded buffer
|
|
795
|
-
* @returns a PLYHeader
|
|
163
|
+
* Gets the number of parts in the compound.
|
|
796
164
|
*/
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const header = new TextDecoder().decode(ubuf.slice(0, 1024 * 10));
|
|
800
|
-
const headerEnd = "end_header\n";
|
|
801
|
-
const headerEndIndex = header.indexOf(headerEnd);
|
|
802
|
-
if (headerEndIndex < 0 || !header) {
|
|
803
|
-
// standard splat
|
|
804
|
-
return null;
|
|
805
|
-
}
|
|
806
|
-
const vertexCount = parseInt(/element vertex (\d+)\n/.exec(header)[1]);
|
|
807
|
-
const chunkElement = /element chunk (\d+)\n/.exec(header);
|
|
808
|
-
let chunkCount = 0;
|
|
809
|
-
if (chunkElement) {
|
|
810
|
-
chunkCount = parseInt(chunkElement[1]);
|
|
811
|
-
}
|
|
812
|
-
let rowVertexOffset = 0;
|
|
813
|
-
let rowChunkOffset = 0;
|
|
814
|
-
const offsets = {
|
|
815
|
-
double: 8,
|
|
816
|
-
int: 4,
|
|
817
|
-
uint: 4,
|
|
818
|
-
float: 4,
|
|
819
|
-
short: 2,
|
|
820
|
-
ushort: 2,
|
|
821
|
-
uchar: 1,
|
|
822
|
-
list: 0,
|
|
823
|
-
};
|
|
824
|
-
let ElementMode;
|
|
825
|
-
(function (ElementMode) {
|
|
826
|
-
ElementMode[ElementMode["Vertex"] = 0] = "Vertex";
|
|
827
|
-
ElementMode[ElementMode["Chunk"] = 1] = "Chunk";
|
|
828
|
-
ElementMode[ElementMode["SH"] = 2] = "SH";
|
|
829
|
-
ElementMode[ElementMode["Unused"] = 3] = "Unused";
|
|
830
|
-
})(ElementMode || (ElementMode = {}));
|
|
831
|
-
let chunkMode = 1 /* ElementMode.Chunk */;
|
|
832
|
-
const vertexProperties = [];
|
|
833
|
-
const chunkProperties = [];
|
|
834
|
-
const filtered = header.slice(0, headerEndIndex).split("\n");
|
|
835
|
-
let shDegree = 0;
|
|
836
|
-
for (const prop of filtered) {
|
|
837
|
-
if (prop.startsWith("property ")) {
|
|
838
|
-
const [, typeName, name] = prop.split(" ");
|
|
839
|
-
const value = GaussianSplattingMesh._ValueNameToEnum(name);
|
|
840
|
-
if (value != 85 /* PLYValue.UNDEFINED */) {
|
|
841
|
-
// SH degree 1,2 or 3 for 9, 24 or 45 values
|
|
842
|
-
if (value >= 84 /* PLYValue.SH_44 */) {
|
|
843
|
-
shDegree = 3;
|
|
844
|
-
}
|
|
845
|
-
else if (value >= 64 /* PLYValue.SH_24 */) {
|
|
846
|
-
shDegree = Math.max(shDegree, 2);
|
|
847
|
-
}
|
|
848
|
-
else if (value >= 48 /* PLYValue.SH_8 */) {
|
|
849
|
-
shDegree = Math.max(shDegree, 1);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
const type = GaussianSplattingMesh._TypeNameToEnum(typeName);
|
|
853
|
-
if (chunkMode == 1 /* ElementMode.Chunk */) {
|
|
854
|
-
chunkProperties.push({ value, type, offset: rowChunkOffset });
|
|
855
|
-
rowChunkOffset += offsets[typeName];
|
|
856
|
-
}
|
|
857
|
-
else if (chunkMode == 0 /* ElementMode.Vertex */) {
|
|
858
|
-
vertexProperties.push({ value, type, offset: rowVertexOffset });
|
|
859
|
-
rowVertexOffset += offsets[typeName];
|
|
860
|
-
}
|
|
861
|
-
else if (chunkMode == 2 /* ElementMode.SH */) {
|
|
862
|
-
// SH doesn't count for vertex row size but its properties are used to retrieve SH
|
|
863
|
-
vertexProperties.push({ value, type, offset: rowVertexOffset });
|
|
864
|
-
}
|
|
865
|
-
if (!offsets[typeName]) {
|
|
866
|
-
Logger.Warn(`Unsupported property type: ${typeName}.`);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
else if (prop.startsWith("element ")) {
|
|
870
|
-
const [, type] = prop.split(" ");
|
|
871
|
-
if (type == "chunk") {
|
|
872
|
-
chunkMode = 1 /* ElementMode.Chunk */;
|
|
873
|
-
}
|
|
874
|
-
else if (type == "vertex") {
|
|
875
|
-
chunkMode = 0 /* ElementMode.Vertex */;
|
|
876
|
-
}
|
|
877
|
-
else if (type == "sh") {
|
|
878
|
-
chunkMode = 2 /* ElementMode.SH */;
|
|
879
|
-
}
|
|
880
|
-
else {
|
|
881
|
-
chunkMode = 3 /* ElementMode.Unused */;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
const dataView = new DataView(data, headerEndIndex + headerEnd.length);
|
|
886
|
-
const buffer = new ArrayBuffer(GaussianSplattingMesh._RowOutputLength * vertexCount);
|
|
887
|
-
let shBuffer = null;
|
|
888
|
-
let shCoefficientCount = 0;
|
|
889
|
-
if (shDegree) {
|
|
890
|
-
const shVectorCount = (shDegree + 1) * (shDegree + 1) - 1;
|
|
891
|
-
shCoefficientCount = shVectorCount * 3;
|
|
892
|
-
shBuffer = new ArrayBuffer(shCoefficientCount * vertexCount);
|
|
893
|
-
}
|
|
894
|
-
return {
|
|
895
|
-
vertexCount: vertexCount,
|
|
896
|
-
chunkCount: chunkCount,
|
|
897
|
-
rowVertexLength: rowVertexOffset,
|
|
898
|
-
rowChunkLength: rowChunkOffset,
|
|
899
|
-
vertexProperties: vertexProperties,
|
|
900
|
-
chunkProperties: chunkProperties,
|
|
901
|
-
dataView: dataView,
|
|
902
|
-
buffer: buffer,
|
|
903
|
-
shDegree: shDegree,
|
|
904
|
-
shCoefficientCount: shCoefficientCount,
|
|
905
|
-
shBuffer: shBuffer,
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
static _GetCompressedChunks(header, offset) {
|
|
909
|
-
if (!header.chunkCount) {
|
|
910
|
-
return null;
|
|
911
|
-
}
|
|
912
|
-
const dataView = header.dataView;
|
|
913
|
-
const compressedChunks = new Array(header.chunkCount);
|
|
914
|
-
for (let i = 0; i < header.chunkCount; i++) {
|
|
915
|
-
const currentChunk = {
|
|
916
|
-
min: new Vector3(),
|
|
917
|
-
max: new Vector3(),
|
|
918
|
-
minScale: new Vector3(),
|
|
919
|
-
maxScale: new Vector3(),
|
|
920
|
-
minColor: new Vector3(0, 0, 0),
|
|
921
|
-
maxColor: new Vector3(1, 1, 1),
|
|
922
|
-
};
|
|
923
|
-
compressedChunks[i] = currentChunk;
|
|
924
|
-
for (let propertyIndex = 0; propertyIndex < header.chunkProperties.length; propertyIndex++) {
|
|
925
|
-
const property = header.chunkProperties[propertyIndex];
|
|
926
|
-
let value;
|
|
927
|
-
switch (property.type) {
|
|
928
|
-
case 0 /* PLYType.FLOAT */:
|
|
929
|
-
value = dataView.getFloat32(property.offset + offset.value, true);
|
|
930
|
-
break;
|
|
931
|
-
default:
|
|
932
|
-
continue;
|
|
933
|
-
}
|
|
934
|
-
switch (property.value) {
|
|
935
|
-
case 0 /* PLYValue.MIN_X */:
|
|
936
|
-
currentChunk.min.x = value;
|
|
937
|
-
break;
|
|
938
|
-
case 1 /* PLYValue.MIN_Y */:
|
|
939
|
-
currentChunk.min.y = value;
|
|
940
|
-
break;
|
|
941
|
-
case 2 /* PLYValue.MIN_Z */:
|
|
942
|
-
currentChunk.min.z = value;
|
|
943
|
-
break;
|
|
944
|
-
case 3 /* PLYValue.MAX_X */:
|
|
945
|
-
currentChunk.max.x = value;
|
|
946
|
-
break;
|
|
947
|
-
case 4 /* PLYValue.MAX_Y */:
|
|
948
|
-
currentChunk.max.y = value;
|
|
949
|
-
break;
|
|
950
|
-
case 5 /* PLYValue.MAX_Z */:
|
|
951
|
-
currentChunk.max.z = value;
|
|
952
|
-
break;
|
|
953
|
-
case 6 /* PLYValue.MIN_SCALE_X */:
|
|
954
|
-
currentChunk.minScale.x = value;
|
|
955
|
-
break;
|
|
956
|
-
case 7 /* PLYValue.MIN_SCALE_Y */:
|
|
957
|
-
currentChunk.minScale.y = value;
|
|
958
|
-
break;
|
|
959
|
-
case 8 /* PLYValue.MIN_SCALE_Z */:
|
|
960
|
-
currentChunk.minScale.z = value;
|
|
961
|
-
break;
|
|
962
|
-
case 9 /* PLYValue.MAX_SCALE_X */:
|
|
963
|
-
currentChunk.maxScale.x = value;
|
|
964
|
-
break;
|
|
965
|
-
case 10 /* PLYValue.MAX_SCALE_Y */:
|
|
966
|
-
currentChunk.maxScale.y = value;
|
|
967
|
-
break;
|
|
968
|
-
case 11 /* PLYValue.MAX_SCALE_Z */:
|
|
969
|
-
currentChunk.maxScale.z = value;
|
|
970
|
-
break;
|
|
971
|
-
case 34 /* PLYValue.MIN_COLOR_R */:
|
|
972
|
-
currentChunk.minColor.x = value;
|
|
973
|
-
break;
|
|
974
|
-
case 35 /* PLYValue.MIN_COLOR_G */:
|
|
975
|
-
currentChunk.minColor.y = value;
|
|
976
|
-
break;
|
|
977
|
-
case 36 /* PLYValue.MIN_COLOR_B */:
|
|
978
|
-
currentChunk.minColor.z = value;
|
|
979
|
-
break;
|
|
980
|
-
case 37 /* PLYValue.MAX_COLOR_R */:
|
|
981
|
-
currentChunk.maxColor.x = value;
|
|
982
|
-
break;
|
|
983
|
-
case 38 /* PLYValue.MAX_COLOR_G */:
|
|
984
|
-
currentChunk.maxColor.y = value;
|
|
985
|
-
break;
|
|
986
|
-
case 39 /* PLYValue.MAX_COLOR_B */:
|
|
987
|
-
currentChunk.maxColor.z = value;
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
offset.value += header.rowChunkLength;
|
|
992
|
-
}
|
|
993
|
-
return compressedChunks;
|
|
994
|
-
}
|
|
995
|
-
static _GetSplat(header, index, compressedChunks, offset) {
|
|
996
|
-
const q = TmpVectors.Quaternion[0];
|
|
997
|
-
const temp3 = TmpVectors.Vector3[0];
|
|
998
|
-
const rowOutputLength = GaussianSplattingMesh._RowOutputLength;
|
|
999
|
-
const buffer = header.buffer;
|
|
1000
|
-
const dataView = header.dataView;
|
|
1001
|
-
const position = new Float32Array(buffer, index * rowOutputLength, 3);
|
|
1002
|
-
const scale = new Float32Array(buffer, index * rowOutputLength + 12, 3);
|
|
1003
|
-
const rgba = new Uint8ClampedArray(buffer, index * rowOutputLength + 24, 4);
|
|
1004
|
-
const rot = new Uint8ClampedArray(buffer, index * rowOutputLength + 28, 4);
|
|
1005
|
-
let sh = null;
|
|
1006
|
-
if (header.shBuffer) {
|
|
1007
|
-
sh = new Uint8ClampedArray(header.shBuffer, index * header.shCoefficientCount, header.shCoefficientCount);
|
|
1008
|
-
}
|
|
1009
|
-
const chunkIndex = index >> 8;
|
|
1010
|
-
let r0 = 255;
|
|
1011
|
-
let r1 = 0;
|
|
1012
|
-
let r2 = 0;
|
|
1013
|
-
let r3 = 0;
|
|
1014
|
-
const plySH = [];
|
|
1015
|
-
for (let propertyIndex = 0; propertyIndex < header.vertexProperties.length; propertyIndex++) {
|
|
1016
|
-
const property = header.vertexProperties[propertyIndex];
|
|
1017
|
-
let value;
|
|
1018
|
-
switch (property.type) {
|
|
1019
|
-
case 0 /* PLYType.FLOAT */:
|
|
1020
|
-
value = dataView.getFloat32(offset.value + property.offset, true);
|
|
1021
|
-
break;
|
|
1022
|
-
case 1 /* PLYType.INT */:
|
|
1023
|
-
value = dataView.getInt32(offset.value + property.offset, true);
|
|
1024
|
-
break;
|
|
1025
|
-
case 2 /* PLYType.UINT */:
|
|
1026
|
-
value = dataView.getUint32(offset.value + property.offset, true);
|
|
1027
|
-
break;
|
|
1028
|
-
case 3 /* PLYType.DOUBLE */:
|
|
1029
|
-
value = dataView.getFloat64(offset.value + property.offset, true);
|
|
1030
|
-
break;
|
|
1031
|
-
case 4 /* PLYType.UCHAR */:
|
|
1032
|
-
value = dataView.getUint8(offset.value + property.offset);
|
|
1033
|
-
break;
|
|
1034
|
-
default:
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
switch (property.value) {
|
|
1038
|
-
case 12 /* PLYValue.PACKED_POSITION */:
|
|
1039
|
-
{
|
|
1040
|
-
const compressedChunk = compressedChunks[chunkIndex];
|
|
1041
|
-
Unpack111011(value, temp3);
|
|
1042
|
-
position[0] = Scalar.Lerp(compressedChunk.min.x, compressedChunk.max.x, temp3.x);
|
|
1043
|
-
position[1] = Scalar.Lerp(compressedChunk.min.y, compressedChunk.max.y, temp3.y);
|
|
1044
|
-
position[2] = Scalar.Lerp(compressedChunk.min.z, compressedChunk.max.z, temp3.z);
|
|
1045
|
-
}
|
|
1046
|
-
break;
|
|
1047
|
-
case 13 /* PLYValue.PACKED_ROTATION */:
|
|
1048
|
-
{
|
|
1049
|
-
UnpackRot(value, q);
|
|
1050
|
-
r0 = q.x;
|
|
1051
|
-
r1 = q.y;
|
|
1052
|
-
r2 = q.z;
|
|
1053
|
-
r3 = q.w;
|
|
1054
|
-
}
|
|
1055
|
-
break;
|
|
1056
|
-
case 14 /* PLYValue.PACKED_SCALE */:
|
|
1057
|
-
{
|
|
1058
|
-
const compressedChunk = compressedChunks[chunkIndex];
|
|
1059
|
-
Unpack111011(value, temp3);
|
|
1060
|
-
scale[0] = Math.exp(Scalar.Lerp(compressedChunk.minScale.x, compressedChunk.maxScale.x, temp3.x));
|
|
1061
|
-
scale[1] = Math.exp(Scalar.Lerp(compressedChunk.minScale.y, compressedChunk.maxScale.y, temp3.y));
|
|
1062
|
-
scale[2] = Math.exp(Scalar.Lerp(compressedChunk.minScale.z, compressedChunk.maxScale.z, temp3.z));
|
|
1063
|
-
}
|
|
1064
|
-
break;
|
|
1065
|
-
case 15 /* PLYValue.PACKED_COLOR */:
|
|
1066
|
-
{
|
|
1067
|
-
const compressedChunk = compressedChunks[chunkIndex];
|
|
1068
|
-
Unpack8888(value, rgba);
|
|
1069
|
-
rgba[0] = Scalar.Lerp(compressedChunk.minColor.x, compressedChunk.maxColor.x, rgba[0] / 255) * 255;
|
|
1070
|
-
rgba[1] = Scalar.Lerp(compressedChunk.minColor.y, compressedChunk.maxColor.y, rgba[1] / 255) * 255;
|
|
1071
|
-
rgba[2] = Scalar.Lerp(compressedChunk.minColor.z, compressedChunk.maxColor.z, rgba[2] / 255) * 255;
|
|
1072
|
-
}
|
|
1073
|
-
break;
|
|
1074
|
-
case 16 /* PLYValue.X */:
|
|
1075
|
-
position[0] = value;
|
|
1076
|
-
break;
|
|
1077
|
-
case 17 /* PLYValue.Y */:
|
|
1078
|
-
position[1] = value;
|
|
1079
|
-
break;
|
|
1080
|
-
case 18 /* PLYValue.Z */:
|
|
1081
|
-
position[2] = value;
|
|
1082
|
-
break;
|
|
1083
|
-
case 19 /* PLYValue.SCALE_0 */:
|
|
1084
|
-
scale[0] = Math.exp(value);
|
|
1085
|
-
break;
|
|
1086
|
-
case 20 /* PLYValue.SCALE_1 */:
|
|
1087
|
-
scale[1] = Math.exp(value);
|
|
1088
|
-
break;
|
|
1089
|
-
case 21 /* PLYValue.SCALE_2 */:
|
|
1090
|
-
scale[2] = Math.exp(value);
|
|
1091
|
-
break;
|
|
1092
|
-
case 22 /* PLYValue.DIFFUSE_RED */:
|
|
1093
|
-
rgba[0] = value;
|
|
1094
|
-
break;
|
|
1095
|
-
case 23 /* PLYValue.DIFFUSE_GREEN */:
|
|
1096
|
-
rgba[1] = value;
|
|
1097
|
-
break;
|
|
1098
|
-
case 24 /* PLYValue.DIFFUSE_BLUE */:
|
|
1099
|
-
rgba[2] = value;
|
|
1100
|
-
break;
|
|
1101
|
-
case 26 /* PLYValue.F_DC_0 */:
|
|
1102
|
-
rgba[0] = (0.5 + GaussianSplattingMesh._SH_C0 * value) * 255;
|
|
1103
|
-
break;
|
|
1104
|
-
case 27 /* PLYValue.F_DC_1 */:
|
|
1105
|
-
rgba[1] = (0.5 + GaussianSplattingMesh._SH_C0 * value) * 255;
|
|
1106
|
-
break;
|
|
1107
|
-
case 28 /* PLYValue.F_DC_2 */:
|
|
1108
|
-
rgba[2] = (0.5 + GaussianSplattingMesh._SH_C0 * value) * 255;
|
|
1109
|
-
break;
|
|
1110
|
-
case 29 /* PLYValue.F_DC_3 */:
|
|
1111
|
-
rgba[3] = (0.5 + GaussianSplattingMesh._SH_C0 * value) * 255;
|
|
1112
|
-
break;
|
|
1113
|
-
case 25 /* PLYValue.OPACITY */:
|
|
1114
|
-
rgba[3] = (1 / (1 + Math.exp(-value))) * 255;
|
|
1115
|
-
break;
|
|
1116
|
-
case 30 /* PLYValue.ROT_0 */:
|
|
1117
|
-
r0 = value;
|
|
1118
|
-
break;
|
|
1119
|
-
case 31 /* PLYValue.ROT_1 */:
|
|
1120
|
-
r1 = value;
|
|
1121
|
-
break;
|
|
1122
|
-
case 32 /* PLYValue.ROT_2 */:
|
|
1123
|
-
r2 = value;
|
|
1124
|
-
break;
|
|
1125
|
-
case 33 /* PLYValue.ROT_3 */:
|
|
1126
|
-
r3 = value;
|
|
1127
|
-
break;
|
|
1128
|
-
}
|
|
1129
|
-
if (sh && property.value >= 40 /* PLYValue.SH_0 */ && property.value <= 84 /* PLYValue.SH_44 */) {
|
|
1130
|
-
const shIndex = property.value - 40 /* PLYValue.SH_0 */;
|
|
1131
|
-
if (property.type == 4 /* PLYType.UCHAR */ && header.chunkCount) {
|
|
1132
|
-
// compressed ply. dataView points to beginning of vertex
|
|
1133
|
-
// could be improved with a direct copy instead of a per SH index computation + copy
|
|
1134
|
-
const compressedValue = dataView.getUint8(header.rowChunkLength * header.chunkCount + header.vertexCount * header.rowVertexLength + index * header.shCoefficientCount + shIndex);
|
|
1135
|
-
// compressed .ply SH import : https://github.com/playcanvas/engine/blob/fda3f0368b45d7381f0b5a1722bd2056128eaebe/src/scene/gsplat/gsplat-compressed-data.js#L88C81-L88C98
|
|
1136
|
-
plySH[shIndex] = (compressedValue * (8 / 255) - 4) * 127.5 + 127.5;
|
|
1137
|
-
}
|
|
1138
|
-
else {
|
|
1139
|
-
const clampedValue = Scalar.Clamp(value * 127.5 + 127.5, 0, 255);
|
|
1140
|
-
plySH[shIndex] = clampedValue;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
if (sh) {
|
|
1145
|
-
const shDim = header.shDegree == 1 ? 3 : header.shDegree == 2 ? 8 : 15;
|
|
1146
|
-
for (let j = 0; j < shDim; j++) {
|
|
1147
|
-
sh[j * 3 + 0] = plySH[j];
|
|
1148
|
-
sh[j * 3 + 1] = plySH[j + shDim];
|
|
1149
|
-
sh[j * 3 + 2] = plySH[j + shDim * 2];
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
q.set(r1, r2, r3, r0);
|
|
1153
|
-
q.normalize();
|
|
1154
|
-
rot[0] = q.w * 127.5 + 127.5;
|
|
1155
|
-
rot[1] = q.x * 127.5 + 127.5;
|
|
1156
|
-
rot[2] = q.y * 127.5 + 127.5;
|
|
1157
|
-
rot[3] = q.z * 127.5 + 127.5;
|
|
1158
|
-
offset.value += header.rowVertexLength;
|
|
165
|
+
get partCount() {
|
|
166
|
+
return this._partMatrices.length;
|
|
1159
167
|
}
|
|
1160
168
|
/**
|
|
1161
|
-
*
|
|
1162
|
-
* if data array buffer is not ply, returns the original buffer
|
|
1163
|
-
* @param data the .ply data to load
|
|
1164
|
-
* @param useCoroutine use coroutine and yield
|
|
1165
|
-
* @returns the loaded splat buffer and optional array of sh coefficients
|
|
169
|
+
* Gets the part visibility array.
|
|
1166
170
|
*/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (!header) {
|
|
1170
|
-
return { buffer: data };
|
|
1171
|
-
}
|
|
1172
|
-
const offset = { value: 0 };
|
|
1173
|
-
const compressedChunks = GaussianSplattingMesh._GetCompressedChunks(header, offset);
|
|
1174
|
-
for (let i = 0; i < header.vertexCount; i++) {
|
|
1175
|
-
GaussianSplattingMesh._GetSplat(header, i, compressedChunks, offset);
|
|
1176
|
-
if (i % GaussianSplattingMesh._PlyConversionBatchSize === 0 && useCoroutine) {
|
|
1177
|
-
yield;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
let sh = null;
|
|
1181
|
-
// make SH texture buffers
|
|
1182
|
-
if (header.shDegree && header.shBuffer) {
|
|
1183
|
-
const textureCount = Math.ceil(header.shCoefficientCount / 16); // 4 components can be stored per texture, 4 sh per component
|
|
1184
|
-
let shIndexRead = 0;
|
|
1185
|
-
const ubuf = new Uint8Array(header.shBuffer);
|
|
1186
|
-
// sh is an array of uint8array that will be used to create sh textures
|
|
1187
|
-
sh = [];
|
|
1188
|
-
const splatCount = header.vertexCount;
|
|
1189
|
-
const engine = EngineStore.LastCreatedEngine;
|
|
1190
|
-
if (engine) {
|
|
1191
|
-
const width = engine.getCaps().maxTextureSize;
|
|
1192
|
-
const height = Math.ceil(splatCount / width);
|
|
1193
|
-
// create array for the number of textures needed.
|
|
1194
|
-
for (let textureIndex = 0; textureIndex < textureCount; textureIndex++) {
|
|
1195
|
-
const texture = new Uint8Array(height * width * 4 * 4); // 4 components per texture, 4 sh per component
|
|
1196
|
-
sh.push(texture);
|
|
1197
|
-
}
|
|
1198
|
-
for (let i = 0; i < splatCount; i++) {
|
|
1199
|
-
for (let shIndexWrite = 0; shIndexWrite < header.shCoefficientCount; shIndexWrite++) {
|
|
1200
|
-
const shValue = ubuf[shIndexRead++];
|
|
1201
|
-
const textureIndex = Math.floor(shIndexWrite / 16);
|
|
1202
|
-
const shArray = sh[textureIndex];
|
|
1203
|
-
const byteIndexInTexture = shIndexWrite % 16; // [0..15]
|
|
1204
|
-
const offsetPerSplat = i * 16; // 16 sh values per texture per splat.
|
|
1205
|
-
shArray[byteIndexInTexture + offsetPerSplat] = shValue;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
return { buffer: header.buffer, sh: sh };
|
|
171
|
+
get partVisibility() {
|
|
172
|
+
return this._partVisibility;
|
|
1211
173
|
}
|
|
1212
174
|
/**
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
* @param
|
|
1217
|
-
* @
|
|
175
|
+
* Sets the world matrix for a specific part of the compound.
|
|
176
|
+
* This will trigger a re-sort of the mesh.
|
|
177
|
+
* The `_partMatrices` array is automatically extended when `partIndex >= partCount`.
|
|
178
|
+
* @param partIndex index of the part
|
|
179
|
+
* @param worldMatrix the world matrix to set
|
|
1218
180
|
*/
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
181
|
+
setWorldMatrixForPart(partIndex, worldMatrix) {
|
|
182
|
+
if (this._partMatrices.length <= partIndex) {
|
|
183
|
+
this.computeWorldMatrix(true);
|
|
184
|
+
const defaultMatrix = this.getWorldMatrix();
|
|
185
|
+
while (this._partMatrices.length <= partIndex) {
|
|
186
|
+
this._partMatrices.push(defaultMatrix.clone());
|
|
187
|
+
this._partVisibility.push(1.0);
|
|
188
|
+
}
|
|
1223
189
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (
|
|
1229
|
-
|
|
190
|
+
this._partMatrices[partIndex].copyFrom(worldMatrix);
|
|
191
|
+
// During a batch rebuild suppress intermediate posts — the final correct set is posted
|
|
192
|
+
// once the full rebuild completes (at the end of removePart).
|
|
193
|
+
if (!this._rebuilding) {
|
|
194
|
+
if (this._worker) {
|
|
195
|
+
this._worker.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
|
|
1230
196
|
}
|
|
197
|
+
this._postToWorker(true);
|
|
1231
198
|
}
|
|
1232
|
-
return header.buffer;
|
|
1233
|
-
}
|
|
1234
|
-
/**
|
|
1235
|
-
* Converts a .ply data array buffer to splat
|
|
1236
|
-
* if data array buffer is not ply, returns the original buffer
|
|
1237
|
-
* @param data the .ply data to load
|
|
1238
|
-
* @returns the loaded splat buffer
|
|
1239
|
-
*/
|
|
1240
|
-
static async ConvertPLYToSplatAsync(data) {
|
|
1241
|
-
return await runCoroutineAsync(GaussianSplattingMesh.ConvertPLYToSplat(data, true), createYieldingScheduler());
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Converts a .ply with SH data array buffer to splat
|
|
1245
|
-
* if data array buffer is not ply, returns the original buffer
|
|
1246
|
-
* @param data the .ply data to load
|
|
1247
|
-
* @returns the loaded splat buffer with SH
|
|
1248
|
-
*/
|
|
1249
|
-
static async ConvertPLYWithSHToSplatAsync(data) {
|
|
1250
|
-
return await runCoroutineAsync(GaussianSplattingMesh.ConvertPLYWithSHToSplat(data, true), createYieldingScheduler());
|
|
1251
199
|
}
|
|
1252
200
|
/**
|
|
1253
|
-
*
|
|
1254
|
-
* @param
|
|
1255
|
-
* @returns
|
|
201
|
+
* Gets the world matrix for a specific part of the compound.
|
|
202
|
+
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
203
|
+
* @returns the world matrix for the part, or the current world matrix of the mesh if the part is not found
|
|
1256
204
|
*/
|
|
1257
|
-
|
|
1258
|
-
return
|
|
205
|
+
getWorldMatrixForPart(partIndex) {
|
|
206
|
+
return this._partMatrices[partIndex] ?? this.getWorldMatrix();
|
|
1259
207
|
}
|
|
1260
208
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
* @param
|
|
1263
|
-
* @
|
|
1264
|
-
* @returns a promise that resolves when the operation is complete
|
|
1265
|
-
* @deprecated Please use SceneLoader.ImportMeshAsync instead
|
|
209
|
+
* Gets the visibility for a specific part of the compound.
|
|
210
|
+
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
211
|
+
* @returns the visibility value (0.0 to 1.0) for the part
|
|
1266
212
|
*/
|
|
1267
|
-
|
|
1268
|
-
|
|
213
|
+
getPartVisibility(partIndex) {
|
|
214
|
+
return this._partVisibility[partIndex] ?? 1.0;
|
|
1269
215
|
}
|
|
1270
216
|
/**
|
|
1271
|
-
*
|
|
1272
|
-
* @param
|
|
217
|
+
* Sets the visibility for a specific part of the compound.
|
|
218
|
+
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
219
|
+
* @param value the visibility value (0.0 to 1.0) to set
|
|
1273
220
|
*/
|
|
1274
|
-
|
|
1275
|
-
this.
|
|
1276
|
-
this._covariancesBTexture?.dispose();
|
|
1277
|
-
this._centersTexture?.dispose();
|
|
1278
|
-
this._colorsTexture?.dispose();
|
|
1279
|
-
if (this._shTextures) {
|
|
1280
|
-
for (const shTexture of this._shTextures) {
|
|
1281
|
-
shTexture.dispose();
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
if (this._partIndicesTexture) {
|
|
1285
|
-
this._partIndicesTexture.dispose();
|
|
1286
|
-
}
|
|
1287
|
-
this._covariancesATexture = null;
|
|
1288
|
-
this._covariancesBTexture = null;
|
|
1289
|
-
this._centersTexture = null;
|
|
1290
|
-
this._colorsTexture = null;
|
|
1291
|
-
this._shTextures = null;
|
|
1292
|
-
this._partIndicesTexture = null;
|
|
1293
|
-
this._partMatrices = [];
|
|
1294
|
-
this._worker?.terminate();
|
|
1295
|
-
this._worker = null;
|
|
1296
|
-
// delete meshes created for each camera
|
|
1297
|
-
this._cameraViewInfos.forEach((cameraViewInfo) => {
|
|
1298
|
-
cameraViewInfo.mesh.dispose();
|
|
1299
|
-
});
|
|
1300
|
-
// dispose all proxy meshes
|
|
1301
|
-
this._partProxies.forEach((proxy) => {
|
|
1302
|
-
proxy.dispose();
|
|
1303
|
-
});
|
|
1304
|
-
this._partProxies.clear();
|
|
1305
|
-
super.dispose(doNotRecurse, true);
|
|
221
|
+
setPartVisibility(partIndex, value) {
|
|
222
|
+
this._partVisibility[partIndex] = Math.max(0.0, Math.min(1.0, value));
|
|
1306
223
|
}
|
|
1307
224
|
_copyTextures(source) {
|
|
1308
|
-
|
|
1309
|
-
this._covariancesBTexture = source.covariancesBTexture?.clone();
|
|
1310
|
-
this._centersTexture = source.centersTexture?.clone();
|
|
1311
|
-
this._colorsTexture = source.colorsTexture?.clone();
|
|
225
|
+
super._copyTextures(source);
|
|
1312
226
|
this._partIndicesTexture = source._partIndicesTexture?.clone();
|
|
1313
|
-
if (source._shTextures) {
|
|
1314
|
-
this._shTextures = [];
|
|
1315
|
-
for (const shTexture of source._shTextures) {
|
|
1316
|
-
this._shTextures?.push(shTexture.clone());
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
/**
|
|
1321
|
-
* Returns a new Mesh object generated from the current mesh properties.
|
|
1322
|
-
* @param name is a string, the name given to the new mesh
|
|
1323
|
-
* @returns a new Gaussian Splatting Mesh
|
|
1324
|
-
*/
|
|
1325
|
-
clone(name = "") {
|
|
1326
|
-
const newGS = new GaussianSplattingMesh(name, undefined, this.getScene());
|
|
1327
|
-
newGS._copySource(this);
|
|
1328
|
-
newGS.makeGeometryUnique();
|
|
1329
|
-
newGS._vertexCount = this._vertexCount;
|
|
1330
|
-
newGS._copyTextures(this);
|
|
1331
|
-
newGS._modelViewProjectionMatrix = Matrix.Identity();
|
|
1332
|
-
newGS._splatPositions = this._splatPositions;
|
|
1333
|
-
newGS._readyToDisplay = false;
|
|
1334
|
-
newGS._disableDepthSort = this._disableDepthSort;
|
|
1335
|
-
newGS._partMatrices = this._partMatrices.map((m) => m.clone());
|
|
1336
|
-
newGS._instanciateWorker();
|
|
1337
|
-
const binfo = this.getBoundingInfo();
|
|
1338
|
-
newGS.getBoundingInfo().reConstruct(binfo.minimum, binfo.maximum, this.getWorldMatrix());
|
|
1339
|
-
newGS.forcedInstanceCount = this.forcedInstanceCount;
|
|
1340
|
-
newGS.setEnabled(true);
|
|
1341
|
-
return newGS;
|
|
1342
|
-
}
|
|
1343
|
-
_makeEmptySplat(index, covA, covB, colorArray) {
|
|
1344
|
-
const covBSItemSize = this._useRGBACovariants ? 4 : 2;
|
|
1345
|
-
this._splatPositions[4 * index + 0] = 0;
|
|
1346
|
-
this._splatPositions[4 * index + 1] = 0;
|
|
1347
|
-
this._splatPositions[4 * index + 2] = 0;
|
|
1348
|
-
covA[index * 4 + 0] = ToHalfFloat(0);
|
|
1349
|
-
covA[index * 4 + 1] = ToHalfFloat(0);
|
|
1350
|
-
covA[index * 4 + 2] = ToHalfFloat(0);
|
|
1351
|
-
covA[index * 4 + 3] = ToHalfFloat(0);
|
|
1352
|
-
covB[index * covBSItemSize + 0] = ToHalfFloat(0);
|
|
1353
|
-
covB[index * covBSItemSize + 1] = ToHalfFloat(0);
|
|
1354
|
-
colorArray[index * 4 + 3] = 0;
|
|
1355
|
-
}
|
|
1356
|
-
_makeSplat(index, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, options) {
|
|
1357
|
-
const matrixRotation = TmpVectors.Matrix[0];
|
|
1358
|
-
const matrixScale = TmpVectors.Matrix[1];
|
|
1359
|
-
const quaternion = TmpVectors.Quaternion[0];
|
|
1360
|
-
const covBSItemSize = this._useRGBACovariants ? 4 : 2;
|
|
1361
|
-
const x = fBuffer[8 * index + 0];
|
|
1362
|
-
const y = fBuffer[8 * index + 1] * (options.flipY ? -1 : 1);
|
|
1363
|
-
const z = fBuffer[8 * index + 2];
|
|
1364
|
-
this._splatPositions[4 * index + 0] = x;
|
|
1365
|
-
this._splatPositions[4 * index + 1] = y;
|
|
1366
|
-
this._splatPositions[4 * index + 2] = z;
|
|
1367
|
-
minimum.minimizeInPlaceFromFloats(x, y, z);
|
|
1368
|
-
maximum.maximizeInPlaceFromFloats(x, y, z);
|
|
1369
|
-
quaternion.set((uBuffer[32 * index + 28 + 1] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 2] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 3] - 127.5) / 127.5, -(uBuffer[32 * index + 28 + 0] - 127.5) / 127.5);
|
|
1370
|
-
quaternion.normalize();
|
|
1371
|
-
quaternion.toRotationMatrix(matrixRotation);
|
|
1372
|
-
Matrix.ScalingToRef(fBuffer[8 * index + 3 + 0] * 2, fBuffer[8 * index + 3 + 1] * 2, fBuffer[8 * index + 3 + 2] * 2, matrixScale);
|
|
1373
|
-
const m = matrixRotation.multiplyToRef(matrixScale, TmpVectors.Matrix[0]).m;
|
|
1374
|
-
const covariances = this._tmpCovariances;
|
|
1375
|
-
covariances[0] = m[0] * m[0] + m[1] * m[1] + m[2] * m[2];
|
|
1376
|
-
covariances[1] = m[0] * m[4] + m[1] * m[5] + m[2] * m[6];
|
|
1377
|
-
covariances[2] = m[0] * m[8] + m[1] * m[9] + m[2] * m[10];
|
|
1378
|
-
covariances[3] = m[4] * m[4] + m[5] * m[5] + m[6] * m[6];
|
|
1379
|
-
covariances[4] = m[4] * m[8] + m[5] * m[9] + m[6] * m[10];
|
|
1380
|
-
covariances[5] = m[8] * m[8] + m[9] * m[9] + m[10] * m[10];
|
|
1381
|
-
// normalize covA, covB
|
|
1382
|
-
let factor = -10000;
|
|
1383
|
-
for (let covIndex = 0; covIndex < 6; covIndex++) {
|
|
1384
|
-
factor = Math.max(factor, Math.abs(covariances[covIndex]));
|
|
1385
|
-
}
|
|
1386
|
-
this._splatPositions[4 * index + 3] = factor;
|
|
1387
|
-
const transform = factor;
|
|
1388
|
-
covA[index * 4 + 0] = ToHalfFloat(covariances[0] / transform);
|
|
1389
|
-
covA[index * 4 + 1] = ToHalfFloat(covariances[1] / transform);
|
|
1390
|
-
covA[index * 4 + 2] = ToHalfFloat(covariances[2] / transform);
|
|
1391
|
-
covA[index * 4 + 3] = ToHalfFloat(covariances[3] / transform);
|
|
1392
|
-
covB[index * covBSItemSize + 0] = ToHalfFloat(covariances[4] / transform);
|
|
1393
|
-
covB[index * covBSItemSize + 1] = ToHalfFloat(covariances[5] / transform);
|
|
1394
|
-
// colors
|
|
1395
|
-
colorArray[index * 4 + 0] = uBuffer[32 * index + 24 + 0];
|
|
1396
|
-
colorArray[index * 4 + 1] = uBuffer[32 * index + 24 + 1];
|
|
1397
|
-
colorArray[index * 4 + 2] = uBuffer[32 * index + 24 + 2];
|
|
1398
|
-
colorArray[index * 4 + 3] = uBuffer[32 * index + 24 + 3];
|
|
1399
227
|
}
|
|
1400
|
-
|
|
1401
|
-
_updateTextures(covA, covB, colorArray, sh, partIndices) {
|
|
1402
|
-
const textureSize = this._getTextureSize(this._vertexCount);
|
|
1403
|
-
// Update the textures
|
|
1404
|
-
const createTextureFromData = (data, width, height, format) => {
|
|
1405
|
-
return new RawTexture(data, width, height, format, this._scene, false, false, 2, 1);
|
|
1406
|
-
};
|
|
228
|
+
_onUpdateTextures(textureSize) {
|
|
1407
229
|
const createTextureFromDataU8 = (data, width, height, format) => {
|
|
1408
230
|
return new RawTexture(data, width, height, format, this._scene, false, false, 2, 0);
|
|
1409
231
|
};
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const textureSizeChanged = this._textureSize.y != textureSize.y;
|
|
1418
|
-
if (!firstTime && !textureSizeChanged) {
|
|
1419
|
-
this._delayedTextureUpdate = { covA, covB, colors: colorArray, centers: this._splatPositions, sh, partIndices };
|
|
1420
|
-
const positions = Float32Array.from(this._splatPositions);
|
|
1421
|
-
const vertexCount = this._vertexCount;
|
|
1422
|
-
if (this._worker) {
|
|
1423
|
-
this._worker.postMessage({ positions, vertexCount }, [positions.buffer]);
|
|
1424
|
-
}
|
|
1425
|
-
// Handle SH textures in update path - create if they don't exist
|
|
1426
|
-
if (sh && !this._shTextures) {
|
|
1427
|
-
this._shTextures = [];
|
|
1428
|
-
for (const shData of sh) {
|
|
1429
|
-
const buffer = new Uint32Array(shData.buffer);
|
|
1430
|
-
const shTexture = createTextureFromDataU32(buffer, textureSize.x, textureSize.y, 11);
|
|
1431
|
-
shTexture.wrapU = 0;
|
|
1432
|
-
shTexture.wrapV = 0;
|
|
1433
|
-
this._shTextures.push(shTexture);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
// Handle compound data, if any
|
|
1437
|
-
if (partIndices && !this._partIndicesTexture) {
|
|
1438
|
-
const buffer = new Uint8Array(partIndices);
|
|
232
|
+
// Keep the part indices texture in sync with _partIndices whenever textures are rebuilt.
|
|
233
|
+
// The old "only create if absent" logic left the texture stale after a second addPart/addParts
|
|
234
|
+
// call that doesn't change the texture dimensions: all new splats kept reading partIndex=0
|
|
235
|
+
// (the first part), causing wrong positions, broken GPU picking, and shared movement.
|
|
236
|
+
if (this._partIndices) {
|
|
237
|
+
const buffer = new Uint8Array(this._partIndices);
|
|
238
|
+
if (!this._partIndicesTexture) {
|
|
1439
239
|
this._partIndicesTexture = createTextureFromDataU8(buffer, textureSize.x, textureSize.y, 6);
|
|
1440
240
|
this._partIndicesTexture.wrapU = 0;
|
|
1441
241
|
this._partIndicesTexture.wrapV = 0;
|
|
1442
242
|
}
|
|
1443
|
-
if (this._worker) {
|
|
1444
|
-
this._worker.postMessage({ partIndices: partIndices ?? null });
|
|
1445
|
-
}
|
|
1446
|
-
this._postToWorker(true);
|
|
1447
|
-
}
|
|
1448
|
-
else {
|
|
1449
|
-
this._textureSize = textureSize;
|
|
1450
|
-
this._covariancesATexture = createTextureFromDataF16(covA, textureSize.x, textureSize.y, 5);
|
|
1451
|
-
this._covariancesBTexture = createTextureFromDataF16(covB, textureSize.x, textureSize.y, this._useRGBACovariants ? 5 : 7);
|
|
1452
|
-
this._centersTexture = createTextureFromData(this._splatPositions, textureSize.x, textureSize.y, 5);
|
|
1453
|
-
this._colorsTexture = createTextureFromDataU8(colorArray, textureSize.x, textureSize.y, 5);
|
|
1454
|
-
if (sh) {
|
|
1455
|
-
this._shTextures = [];
|
|
1456
|
-
for (const shData of sh) {
|
|
1457
|
-
const buffer = new Uint32Array(shData.buffer);
|
|
1458
|
-
const shTexture = createTextureFromDataU32(buffer, textureSize.x, textureSize.y, 11);
|
|
1459
|
-
shTexture.wrapU = 0;
|
|
1460
|
-
shTexture.wrapV = 0;
|
|
1461
|
-
this._shTextures.push(shTexture);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
if (partIndices) {
|
|
1465
|
-
const buffer = new Uint8Array(partIndices);
|
|
1466
|
-
this._partIndicesTexture = createTextureFromDataU8(buffer, textureSize.x, textureSize.y, 6);
|
|
1467
|
-
this._partIndicesTexture.wrapU = 0;
|
|
1468
|
-
this._partIndicesTexture.wrapV = 0;
|
|
1469
|
-
}
|
|
1470
|
-
if (firstTime) {
|
|
1471
|
-
this._instanciateWorker();
|
|
1472
|
-
}
|
|
1473
243
|
else {
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
this.
|
|
1478
|
-
this.
|
|
244
|
+
const existingSize = this._partIndicesTexture.getSize();
|
|
245
|
+
if (existingSize.width !== textureSize.x || existingSize.height !== textureSize.y) {
|
|
246
|
+
// Dimensions changed — dispose and recreate at the new size.
|
|
247
|
+
this._partIndicesTexture.dispose();
|
|
248
|
+
this._partIndicesTexture = createTextureFromDataU8(buffer, textureSize.x, textureSize.y, 6);
|
|
249
|
+
this._partIndicesTexture.wrapU = 0;
|
|
250
|
+
this._partIndicesTexture.wrapV = 0;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// Same size — update data in-place (e.g. second addParts fitting in existing dims).
|
|
254
|
+
this._updateTextureFromData(this._partIndicesTexture, buffer, textureSize.x, 0, textureSize.y);
|
|
1479
255
|
}
|
|
1480
|
-
this._postToWorker(true);
|
|
1481
256
|
}
|
|
1482
257
|
}
|
|
1483
258
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
if (this._keepInRam) {
|
|
1493
|
-
this._splatsData = data;
|
|
1494
|
-
this._shData = sh ? sh.map((arr) => new Uint8Array(arr)) : null;
|
|
1495
|
-
}
|
|
1496
|
-
const vertexCount = uBuffer.length / GaussianSplattingMesh._RowOutputLength;
|
|
1497
|
-
if (vertexCount != this._vertexCount) {
|
|
1498
|
-
this._updateSplatIndexBuffer(vertexCount);
|
|
1499
|
-
}
|
|
1500
|
-
this._vertexCount = vertexCount;
|
|
1501
|
-
// degree == 1 for 1 texture (3 terms), 2 for 2 textures(8 terms) and 3 for 3 textures (15 terms)
|
|
1502
|
-
this._shDegree = sh ? sh.length : 0;
|
|
1503
|
-
const textureSize = this._getTextureSize(vertexCount);
|
|
1504
|
-
const textureLength = textureSize.x * textureSize.y;
|
|
1505
|
-
const lineCountUpdate = GaussianSplattingMesh.ProgressiveUpdateAmount ?? textureSize.y;
|
|
1506
|
-
const textureLengthPerUpdate = textureSize.x * lineCountUpdate;
|
|
1507
|
-
this._splatPositions = new Float32Array(4 * textureLength);
|
|
1508
|
-
const covA = new Uint16Array(textureLength * 4);
|
|
1509
|
-
const covB = new Uint16Array((this._useRGBACovariants ? 4 : 2) * textureLength);
|
|
1510
|
-
const colorArray = new Uint8Array(textureLength * 4);
|
|
1511
|
-
// Ensure that partMatrices.length is at least the maximum part index + 1
|
|
1512
|
-
if (partIndices) {
|
|
1513
|
-
// We always keep part indices in RAM because they are needed for sorting
|
|
1514
|
-
this._partIndices = new Uint8Array(textureLength);
|
|
1515
|
-
this._partIndices.set(partIndices);
|
|
1516
|
-
let maxPartIndex = -1;
|
|
1517
|
-
for (let i = 0; i < partIndices.length; i++) {
|
|
1518
|
-
maxPartIndex = Math.max(maxPartIndex, partIndices[i]);
|
|
1519
|
-
}
|
|
1520
|
-
this._ensureMinimumPartMatricesLength(maxPartIndex + 1);
|
|
1521
|
-
}
|
|
1522
|
-
const minimum = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
|
|
1523
|
-
const maximum = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
|
|
1524
|
-
if (GaussianSplattingMesh.ProgressiveUpdateAmount) {
|
|
1525
|
-
// create textures with not filled-yet array, then update directly portions of it
|
|
1526
|
-
this._updateTextures(covA, covB, colorArray, sh, this._partIndices ? this._partIndices : undefined);
|
|
1527
|
-
this.setEnabled(true);
|
|
1528
|
-
const partCount = Math.ceil(textureSize.y / lineCountUpdate);
|
|
1529
|
-
for (let partIndex = 0; partIndex < partCount; partIndex++) {
|
|
1530
|
-
const updateLine = partIndex * lineCountUpdate;
|
|
1531
|
-
const splatIndexBase = updateLine * textureSize.x;
|
|
1532
|
-
for (let i = 0; i < textureLengthPerUpdate; i++) {
|
|
1533
|
-
this._makeSplat(splatIndexBase + i, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, options);
|
|
1534
|
-
}
|
|
1535
|
-
this._updateSubTextures(this._splatPositions, covA, covB, colorArray, updateLine, Math.min(lineCountUpdate, textureSize.y - updateLine));
|
|
1536
|
-
// Update the binfo
|
|
1537
|
-
this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
|
|
1538
|
-
if (isAsync) {
|
|
1539
|
-
yield;
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
// sort will be dirty here as just finished filled positions will not be sorted
|
|
1543
|
-
const positions = Float32Array.from(this._splatPositions);
|
|
1544
|
-
const vertexCount = this._vertexCount;
|
|
259
|
+
_updateSubTextures(splatPositions, covA, covB, colorArray, lineStart, lineCount, sh, partIndices) {
|
|
260
|
+
super._updateSubTextures(splatPositions, covA, covB, colorArray, lineStart, lineCount, sh);
|
|
261
|
+
if (partIndices && this._partIndicesTexture) {
|
|
262
|
+
const textureSize = this._getTextureSize(this._vertexCount);
|
|
263
|
+
const texelStart = lineStart * textureSize.x;
|
|
264
|
+
const texelCount = lineCount * textureSize.x;
|
|
265
|
+
const partIndicesView = new Uint8Array(partIndices.buffer, texelStart, texelCount);
|
|
266
|
+
this._updateTextureFromData(this._partIndicesTexture, partIndicesView, textureSize.x, lineStart, lineCount);
|
|
1545
267
|
if (this._worker) {
|
|
1546
|
-
this._worker.postMessage({
|
|
1547
|
-
this._worker.postMessage({ partIndices });
|
|
1548
|
-
}
|
|
1549
|
-
this._sortIsDirty = true;
|
|
1550
|
-
}
|
|
1551
|
-
else {
|
|
1552
|
-
const paddedVertexCount = (vertexCount + 15) & ~0xf;
|
|
1553
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
1554
|
-
this._makeSplat(i, fBuffer, uBuffer, covA, covB, colorArray, minimum, maximum, options);
|
|
1555
|
-
if (isAsync && i % GaussianSplattingMesh._SplatBatchSize === 0) {
|
|
1556
|
-
yield;
|
|
1557
|
-
}
|
|
268
|
+
this._worker.postMessage({ partIndices: partIndices });
|
|
1558
269
|
}
|
|
1559
|
-
// pad the rest
|
|
1560
|
-
for (let i = vertexCount; i < paddedVertexCount; i++) {
|
|
1561
|
-
this._makeEmptySplat(i, covA, covB, colorArray);
|
|
1562
|
-
}
|
|
1563
|
-
// textures
|
|
1564
|
-
this._updateTextures(covA, covB, colorArray, sh, this._partIndices ? this._partIndices : undefined);
|
|
1565
|
-
// Update the binfo
|
|
1566
|
-
this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
|
|
1567
|
-
this.setEnabled(true);
|
|
1568
|
-
this._sortIsDirty = true;
|
|
1569
270
|
}
|
|
1570
|
-
this._postToWorker(true);
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Update asynchronously the buffer
|
|
1574
|
-
* @param data array buffer containing center, color, orientation and scale of splats
|
|
1575
|
-
* @param sh optional array of uint8 array for SH data
|
|
1576
|
-
* @param partIndices optional array of uint8 for rig node indices
|
|
1577
|
-
* @returns a promise
|
|
1578
|
-
*/
|
|
1579
|
-
async updateDataAsync(data, sh, partIndices) {
|
|
1580
|
-
return await runCoroutineAsync(this._updateData(data, true, sh, partIndices), createYieldingScheduler());
|
|
1581
271
|
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Private helpers
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
1582
275
|
/**
|
|
1583
|
-
*
|
|
1584
|
-
*
|
|
1585
|
-
* @param
|
|
1586
|
-
* @param
|
|
1587
|
-
* @param options optional informations on how to treat data (needs to be 3rd for backward compatibility)
|
|
1588
|
-
* @param partIndices optional array of uint8 for rig node indices
|
|
276
|
+
* Creates the part indices GPU texture the first time an incremental addPart introduces
|
|
277
|
+
* compound data. Has no effect if the texture already exists or no partIndices are provided.
|
|
278
|
+
* @param textureSize - Current texture dimensions
|
|
279
|
+
* @param partIndices - Part index data; if undefined the method is a no-op
|
|
1589
280
|
*/
|
|
1590
|
-
|
|
1591
|
-
|
|
281
|
+
_ensurePartIndicesTexture(textureSize, partIndices) {
|
|
282
|
+
if (!partIndices || this._partIndicesTexture) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const buffer = new Uint8Array(this._partIndices);
|
|
286
|
+
this._partIndicesTexture = new RawTexture(buffer, textureSize.x, textureSize.y, 6, this._scene, false, false, 2, 0);
|
|
287
|
+
this._partIndicesTexture.wrapU = 0;
|
|
288
|
+
this._partIndicesTexture.wrapV = 0;
|
|
289
|
+
if (this._worker) {
|
|
290
|
+
this._worker.postMessage({ partIndices: partIndices ?? null });
|
|
291
|
+
}
|
|
1592
292
|
}
|
|
1593
293
|
/**
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
294
|
+
* Core implementation for adding one or more external GaussianSplattingMesh objects as new
|
|
295
|
+
* parts. Writes directly into texture-sized CPU arrays and uploads in one pass — no merged
|
|
296
|
+
* CPU splat buffer is ever constructed.
|
|
297
|
+
*
|
|
298
|
+
* @param others - Source meshes to append (must each be non-compound and fully loaded)
|
|
299
|
+
* @param disposeOthers - Dispose source meshes after appending
|
|
300
|
+
* @returns Proxy meshes and their assigned part indices
|
|
1596
301
|
*/
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
}
|
|
1601
|
-
// in case size is different
|
|
1602
|
-
_updateSplatIndexBuffer(vertexCount) {
|
|
1603
|
-
const paddedVertexCount = (vertexCount + 15) & ~0xf;
|
|
1604
|
-
if (!this._splatIndex || vertexCount != this._splatIndex.length) {
|
|
1605
|
-
this._splatIndex = new Float32Array(paddedVertexCount);
|
|
1606
|
-
for (let i = 0; i < paddedVertexCount; i++) {
|
|
1607
|
-
this._splatIndex[i] = i;
|
|
1608
|
-
}
|
|
1609
|
-
// update meshes for knowns cameras
|
|
1610
|
-
this._cameraViewInfos.forEach((cameraViewInfos) => {
|
|
1611
|
-
cameraViewInfos.mesh.thinInstanceSetBuffer("splatIndex", this._splatIndex, 16, false);
|
|
1612
|
-
});
|
|
302
|
+
_addPartsInternal(others, disposeOthers) {
|
|
303
|
+
if (others.length === 0) {
|
|
304
|
+
return { proxyMeshes: [], assignedPartIndices: [] };
|
|
1613
305
|
}
|
|
1614
|
-
//
|
|
1615
|
-
|
|
1616
|
-
|
|
306
|
+
// Validate
|
|
307
|
+
for (const other of others) {
|
|
308
|
+
if (!other._splatsData) {
|
|
309
|
+
throw new Error(`To call addPart()/addParts(), each source mesh must be fully loaded`);
|
|
310
|
+
}
|
|
311
|
+
if (other.isCompound) {
|
|
312
|
+
throw new Error(`To call addPart()/addParts(), each source mesh must not be a compound`);
|
|
313
|
+
}
|
|
1617
314
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
};
|
|
1624
|
-
const textureSize = this._getTextureSize(this._vertexCount);
|
|
315
|
+
const splatCountA = this._vertexCount;
|
|
316
|
+
const totalOtherCount = others.reduce((s, o) => s + o._vertexCount, 0);
|
|
317
|
+
const totalCount = splatCountA + totalOtherCount;
|
|
318
|
+
const textureSize = this._getTextureSize(totalCount);
|
|
319
|
+
const textureLength = textureSize.x * textureSize.y;
|
|
1625
320
|
const covBSItemSize = this._useRGBACovariants ? 4 : 2;
|
|
1626
|
-
|
|
1627
|
-
const
|
|
1628
|
-
const
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
for (let i = 0; i <
|
|
1638
|
-
|
|
1639
|
-
const shView = new Uint32Array(sh[i].buffer, texelStart * componentCount * 4, texelCount * componentCount);
|
|
1640
|
-
updateTextureFromData(this._shTextures[i], shView, textureSize.x, lineStart, lineCount);
|
|
321
|
+
// Allocate destination arrays for the full new texture
|
|
322
|
+
const covA = new Uint16Array(textureLength * 4);
|
|
323
|
+
const covB = new Uint16Array(covBSItemSize * textureLength);
|
|
324
|
+
const colorArray = new Uint8Array(textureLength * 4);
|
|
325
|
+
// Determine merged SH degree
|
|
326
|
+
const hasSH = this._shData !== null && others.every((o) => o._shData !== null);
|
|
327
|
+
const shDegreeNew = hasSH ? Math.max(this._shDegree, ...others.map((o) => o._shDegree)) : 0;
|
|
328
|
+
let sh = undefined;
|
|
329
|
+
if (hasSH && shDegreeNew > 0) {
|
|
330
|
+
const bytesPerTexel = 16;
|
|
331
|
+
sh = [];
|
|
332
|
+
for (let i = 0; i < shDegreeNew; i++) {
|
|
333
|
+
sh.push(new Uint8Array(textureLength * bytesPerTexel));
|
|
1641
334
|
}
|
|
1642
335
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
return;
|
|
336
|
+
// --- Incremental path: can we reuse the already-committed GPU region? ---
|
|
337
|
+
const incremental = this._canReuseCachedData(splatCountA, totalCount);
|
|
338
|
+
const firstNewLine = incremental ? Math.floor(splatCountA / textureSize.x) : 0;
|
|
339
|
+
const minimum = incremental ? this._cachedBoundingMin.clone() : new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
|
|
340
|
+
const maximum = incremental ? this._cachedBoundingMax.clone() : new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
|
|
341
|
+
// Preserve existing processed positions in the new array
|
|
342
|
+
const oldPositions = this._splatPositions;
|
|
343
|
+
this._splatPositions = new Float32Array(4 * textureLength);
|
|
344
|
+
if (incremental && oldPositions) {
|
|
345
|
+
this._splatPositions.set(oldPositions.subarray(0, splatCountA * 4));
|
|
1654
346
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
347
|
+
// --- Build part indices ---
|
|
348
|
+
let nextPartIndex = this.partCount;
|
|
349
|
+
let partIndicesA = this._partIndices;
|
|
350
|
+
if (!partIndicesA) {
|
|
351
|
+
// First addPart on a plain mesh: assign its splats to part 0
|
|
352
|
+
partIndicesA = new Uint8Array(splatCountA);
|
|
353
|
+
nextPartIndex = splatCountA > 0 ? 1 : 0;
|
|
1659
354
|
}
|
|
1660
|
-
|
|
1661
|
-
this.
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
355
|
+
this._partIndices = new Uint8Array(textureLength);
|
|
356
|
+
this._partIndices.set(partIndicesA.subarray(0, splatCountA));
|
|
357
|
+
const assignedPartIndices = [];
|
|
358
|
+
let dstOffset = splatCountA;
|
|
359
|
+
const maxPartCount = GetGaussianSplattingMaxPartCount(this._scene.getEngine());
|
|
360
|
+
for (const other of others) {
|
|
361
|
+
if (nextPartIndex >= maxPartCount) {
|
|
362
|
+
throw new Error(`Cannot add part, as the maximum part count (${maxPartCount}) has been reached`);
|
|
363
|
+
}
|
|
364
|
+
const newPartIndex = nextPartIndex++;
|
|
365
|
+
assignedPartIndices.push(newPartIndex);
|
|
366
|
+
this._partIndices.fill(newPartIndex, dstOffset, dstOffset + other._vertexCount);
|
|
367
|
+
dstOffset += other._vertexCount;
|
|
368
|
+
}
|
|
369
|
+
// --- Process source data ---
|
|
370
|
+
if (!incremental) {
|
|
371
|
+
// Full rebuild path — only reached when the GPU texture must be reallocated
|
|
372
|
+
// (either the texture height needs to grow to fit the new total, or this is
|
|
373
|
+
// the very first addPart onto a mesh with no GPU textures yet). In the common
|
|
374
|
+
// case where the texture height is unchanged, `incremental` is true and this
|
|
375
|
+
// entire block is skipped. The `splatCountA > 0` guard avoids redundant work
|
|
376
|
+
// on the first-ever addPart when the compound mesh starts empty.
|
|
377
|
+
if (splatCountA > 0) {
|
|
378
|
+
if (this._partProxies.length > 0) {
|
|
379
|
+
// Already compound: rebuild every existing part from its stored source data.
|
|
380
|
+
//
|
|
381
|
+
// DESIGN NOTE: The intended use of GaussianSplattingMesh / GaussianSplattingCompoundMesh
|
|
382
|
+
// in compound mode is to start EMPTY and compose parts exclusively via addPart/addParts.
|
|
383
|
+
// In a future major version this will be the only supported path and the "own data"
|
|
384
|
+
// legacy branch below will be removed.
|
|
385
|
+
//
|
|
386
|
+
// Until then, two layouts are possible:
|
|
387
|
+
// A) LEGACY — compound loaded its own splat data (via URL or updateData) before
|
|
388
|
+
// any addPart call. _partProxies[0] is undefined; the mesh's own splat data
|
|
389
|
+
// is treated as an implicit "part 0" in this._splatsData. Proxied parts occupy
|
|
390
|
+
// indices 1+. This layout will be deprecated in the next major version.
|
|
391
|
+
// B) PREFERRED — compound started empty; first addPart assigned partIndex=0.
|
|
392
|
+
// _partProxies[0] is set; this._splatsData is null; all parts are proxied.
|
|
393
|
+
let rebuildOffset = 0;
|
|
394
|
+
// Rebuild the compound's legacy "own" data at part 0 (scenario A only).
|
|
395
|
+
// Skipped in the preferred empty-composer path (scenario B).
|
|
396
|
+
if (!this._partProxies[0] && this._splatsData) {
|
|
397
|
+
const proxyVertexCount = this._partProxies.reduce((sum, proxy) => sum + (proxy ? proxy.proxiedMesh._vertexCount : 0), 0);
|
|
398
|
+
const part0Count = splatCountA - proxyVertexCount;
|
|
399
|
+
if (part0Count > 0) {
|
|
400
|
+
const uBufA = new Uint8Array(this._splatsData);
|
|
401
|
+
const fBufA = new Float32Array(this._splatsData);
|
|
402
|
+
for (let i = 0; i < part0Count; i++) {
|
|
403
|
+
this._makeSplat(i, fBufA, uBufA, covA, covB, colorArray, minimum, maximum, false);
|
|
404
|
+
}
|
|
405
|
+
if (sh && this._shData) {
|
|
406
|
+
const bytesPerTexel = 16;
|
|
407
|
+
for (let texIdx = 0; texIdx < sh.length; texIdx++) {
|
|
408
|
+
if (texIdx < this._shData.length) {
|
|
409
|
+
sh[texIdx].set(this._shData[texIdx].subarray(0, part0Count * bytesPerTexel), 0);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
rebuildOffset += part0Count;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Rebuild all proxied parts. Loop from index 0 because in the preferred
|
|
417
|
+
// scenario B, part 0 is itself a proxied part with no implicit "own" data.
|
|
418
|
+
for (let partIndex = 0; partIndex < this._partProxies.length; partIndex++) {
|
|
419
|
+
const proxy = this._partProxies[partIndex];
|
|
420
|
+
if (!proxy || !proxy.proxiedMesh) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
this._appendSourceToArrays(proxy.proxiedMesh, rebuildOffset, covA, covB, colorArray, sh, minimum, maximum);
|
|
424
|
+
rebuildOffset += proxy.proxiedMesh._vertexCount;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
// No proxies yet: this is the very first addPart call on a mesh that loaded
|
|
429
|
+
// its own splat data (scenario A legacy path). Re-process that own data so
|
|
430
|
+
// it occupies the start of the new texture before the incoming part is appended.
|
|
431
|
+
// In the preferred scenario B (empty composer) splatCountA is 0 and this
|
|
432
|
+
// entire branch is skipped by the outer `if (splatCountA > 0)` guard.
|
|
433
|
+
if (this._splatsData) {
|
|
434
|
+
const uBufA = new Uint8Array(this._splatsData);
|
|
435
|
+
const fBufA = new Float32Array(this._splatsData);
|
|
436
|
+
for (let i = 0; i < splatCountA; i++) {
|
|
437
|
+
this._makeSplat(i, fBufA, uBufA, covA, covB, colorArray, minimum, maximum, false);
|
|
438
|
+
}
|
|
439
|
+
if (sh && this._shData) {
|
|
440
|
+
const bytesPerTexel = 16;
|
|
441
|
+
for (let texIdx = 0; texIdx < sh.length; texIdx++) {
|
|
442
|
+
if (texIdx < this._shData.length) {
|
|
443
|
+
sh[texIdx].set(this._shData[texIdx].subarray(0, splatCountA * bytesPerTexel), 0);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
1687
448
|
}
|
|
1688
449
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
const
|
|
1696
|
-
if (
|
|
1697
|
-
if (
|
|
1698
|
-
|
|
450
|
+
}
|
|
451
|
+
// Incremental path: rebuild the partial first row (indices firstNewTexel to splatCountA-1)
|
|
452
|
+
// so _updateSubTextures does not upload stale zeros over those already-committed texels.
|
|
453
|
+
// The base-class _updateData always re-processes from firstNewTexel for the same reason;
|
|
454
|
+
// the compound path must do the same.
|
|
455
|
+
if (incremental) {
|
|
456
|
+
const firstNewTexel = firstNewLine * textureSize.x;
|
|
457
|
+
if (firstNewTexel < splatCountA) {
|
|
458
|
+
if (this._partProxies.length === 0) {
|
|
459
|
+
// No proxies: the mesh loaded its own splat data and this is the first
|
|
460
|
+
// addPart call (scenario A legacy path). Re-process the partial boundary
|
|
461
|
+
// row so it is not clobbered by stale zeros during the sub-texture upload.
|
|
462
|
+
if (this._splatsData) {
|
|
463
|
+
const uBufA = new Uint8Array(this._splatsData);
|
|
464
|
+
const fBufA = new Float32Array(this._splatsData);
|
|
465
|
+
for (let i = firstNewTexel; i < splatCountA; i++) {
|
|
466
|
+
this._makeSplat(i, fBufA, uBufA, covA, covB, colorArray, minimum, maximum, false, i);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
1699
469
|
}
|
|
1700
470
|
else {
|
|
1701
|
-
|
|
1702
|
-
|
|
471
|
+
// Already compound: build a per-partIndex source lookup so each splat in the
|
|
472
|
+
// partial boundary row can be re-processed from its original source buffer.
|
|
473
|
+
//
|
|
474
|
+
// Handles both layouts (see full-rebuild comment above):
|
|
475
|
+
// A) LEGACY: _partProxies[0] absent → seed lookup[0] with this._splatsData
|
|
476
|
+
// B) PREFERRED: _partProxies[0] present → all entries filled from proxies
|
|
477
|
+
const proxyTotal = this._partProxies.reduce((s, p) => s + (p ? p.proxiedMesh._vertexCount : 0), 0);
|
|
478
|
+
const part0Count = splatCountA - proxyTotal; // > 0 only in legacy scenario A
|
|
479
|
+
const srcUBufs = new Array(this._partProxies.length).fill(null);
|
|
480
|
+
const srcFBufs = new Array(this._partProxies.length).fill(null);
|
|
481
|
+
const partStarts = new Array(this._partProxies.length).fill(0);
|
|
482
|
+
// Legacy scenario A: part 0 is the mesh's own loaded data.
|
|
483
|
+
if (!this._partProxies[0] && this._splatsData && part0Count > 0) {
|
|
484
|
+
srcUBufs[0] = new Uint8Array(this._splatsData);
|
|
485
|
+
srcFBufs[0] = new Float32Array(this._splatsData);
|
|
486
|
+
partStarts[0] = 0;
|
|
487
|
+
}
|
|
488
|
+
// All proxied parts — start from pi=0 to cover preferred scenario B.
|
|
489
|
+
let cumOffset = part0Count;
|
|
490
|
+
for (let pi = 0; pi < this._partProxies.length; pi++) {
|
|
491
|
+
const proxy = this._partProxies[pi];
|
|
492
|
+
if (!proxy?.proxiedMesh) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const srcData = proxy.proxiedMesh._splatsData ?? null;
|
|
496
|
+
srcUBufs[pi] = srcData ? new Uint8Array(srcData) : null;
|
|
497
|
+
srcFBufs[pi] = srcData ? new Float32Array(srcData) : null;
|
|
498
|
+
partStarts[pi] = cumOffset;
|
|
499
|
+
cumOffset += proxy.proxiedMesh._vertexCount;
|
|
500
|
+
}
|
|
501
|
+
for (let splatIdx = firstNewTexel; splatIdx < splatCountA; splatIdx++) {
|
|
502
|
+
const partIdx = this._partIndices ? this._partIndices[splatIdx] : 0;
|
|
503
|
+
const uBuf = partIdx < srcUBufs.length ? srcUBufs[partIdx] : null;
|
|
504
|
+
const fBuf = partIdx < srcFBufs.length ? srcFBufs[partIdx] : null;
|
|
505
|
+
if (uBuf && fBuf) {
|
|
506
|
+
this._makeSplat(splatIdx, fBuf, uBuf, covA, covB, colorArray, minimum, maximum, false, splatIdx - (partStarts[partIdx] ?? 0));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
1703
509
|
}
|
|
1704
510
|
}
|
|
1705
|
-
this._canPostToWorker = true;
|
|
1706
|
-
this._readyToDisplay = true;
|
|
1707
|
-
// sort is dirty when GS is visible for progressive update with a this message arriving but positions were partially filled
|
|
1708
|
-
// another update needs to be kicked. The kick can't happen just when the position buffer is ready because _canPostToWorker might be false.
|
|
1709
|
-
if (this._sortIsDirty) {
|
|
1710
|
-
this._postToWorker(true);
|
|
1711
|
-
this._sortIsDirty = false;
|
|
1712
|
-
}
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
_getTextureSize(length) {
|
|
1716
|
-
const engine = this._scene.getEngine();
|
|
1717
|
-
const width = engine.getCaps().maxTextureSize;
|
|
1718
|
-
let height = 1;
|
|
1719
|
-
if (engine.version === 1 && !engine.isWebGPU) {
|
|
1720
|
-
while (width * height < length) {
|
|
1721
|
-
height *= 2;
|
|
1722
|
-
}
|
|
1723
511
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
height = width;
|
|
1730
|
-
}
|
|
1731
|
-
return new Vector2(width, height);
|
|
1732
|
-
}
|
|
1733
|
-
/**
|
|
1734
|
-
* Gets the number of parts in the compound
|
|
1735
|
-
* @returns the number of parts in the compound, or 0 if the mesh is not a compound
|
|
1736
|
-
*/
|
|
1737
|
-
get partCount() {
|
|
1738
|
-
return this._partMatrices.length;
|
|
1739
|
-
}
|
|
1740
|
-
/**
|
|
1741
|
-
* Sets the world matrix for a specific part of the compound (if this mesh is a compound).
|
|
1742
|
-
* This will trigger a re-sort of the mesh.
|
|
1743
|
-
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
1744
|
-
* @param worldMatrix the world matrix to set
|
|
1745
|
-
*/
|
|
1746
|
-
setWorldMatrixForPart(partIndex, worldMatrix) {
|
|
1747
|
-
this._partMatrices[partIndex].copyFrom(worldMatrix);
|
|
1748
|
-
if (this._worker) {
|
|
1749
|
-
this._worker.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
|
|
512
|
+
// Append each new source
|
|
513
|
+
dstOffset = splatCountA;
|
|
514
|
+
for (const other of others) {
|
|
515
|
+
this._appendSourceToArrays(other, dstOffset, covA, covB, colorArray, sh, minimum, maximum);
|
|
516
|
+
dstOffset += other._vertexCount;
|
|
1750
517
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
1756
|
-
* @returns the world matrix for the part, or the current world matrix of the mesh if the mesh is not a compound
|
|
1757
|
-
*/
|
|
1758
|
-
getWorldMatrixForPart(partIndex) {
|
|
1759
|
-
return this._partMatrices[partIndex] ?? this.getWorldMatrix();
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Gets the visibility for a specific part of the compound (if this mesh is a compound).
|
|
1763
|
-
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
1764
|
-
* @returns the visibility value (0.0 to 1.0) for the part
|
|
1765
|
-
*/
|
|
1766
|
-
getPartVisibility(partIndex) {
|
|
1767
|
-
return this._partVisibility[partIndex] ?? 1.0;
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
|
-
* Sets the visibility for a specific part of the compound (if this mesh is a compound).
|
|
1771
|
-
* @param partIndex index of the part, that must be between 0 and partCount - 1
|
|
1772
|
-
* @param value the visibility value (0.0 to 1.0) to set
|
|
1773
|
-
*/
|
|
1774
|
-
setPartVisibility(partIndex, value) {
|
|
1775
|
-
this._partVisibility[partIndex] = Math.max(0.0, Math.min(1.0, value));
|
|
1776
|
-
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Ensure that the part world matrix array is at least the given length.
|
|
1779
|
-
* NB: This length is used as reference for the number of parts in the compound.
|
|
1780
|
-
* Newly inserted parts are initialized with the current world matrix of the mesh.
|
|
1781
|
-
* @param length - The minimum length to ensure
|
|
1782
|
-
*/
|
|
1783
|
-
_ensureMinimumPartMatricesLength(length) {
|
|
1784
|
-
if (this._partMatrices.length < length) {
|
|
1785
|
-
this._resizePartMatrices(length);
|
|
518
|
+
// Pad empty splats to texture boundary
|
|
519
|
+
const paddedEnd = (totalCount + 15) & ~0xf;
|
|
520
|
+
for (let i = totalCount; i < paddedEnd; i++) {
|
|
521
|
+
this._makeEmptySplat(i, covA, covB, colorArray);
|
|
1786
522
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
* Warning: This must be consistent with the indices used in the partIndices texture.
|
|
1791
|
-
* Newly inserted parts are initialized with the current world matrix of the mesh.
|
|
1792
|
-
* @param length - The length to resize to
|
|
1793
|
-
*/
|
|
1794
|
-
_resizePartMatrices(length) {
|
|
1795
|
-
if (this._partMatrices.length == length) {
|
|
1796
|
-
return;
|
|
523
|
+
// --- Update vertex count / index buffer ---
|
|
524
|
+
if (totalCount !== this._vertexCount) {
|
|
525
|
+
this._updateSplatIndexBuffer(totalCount);
|
|
1797
526
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
527
|
+
this._vertexCount = totalCount;
|
|
528
|
+
this._shDegree = shDegreeNew;
|
|
529
|
+
// --- Upload to GPU ---
|
|
530
|
+
if (incremental) {
|
|
531
|
+
// Update the part-indices texture (handles both create and update-in-place).
|
|
532
|
+
// _ensurePartIndicesTexture is a no-op when the texture already exists, so on the
|
|
533
|
+
// second+ addPart the partIndices would be stale without this call.
|
|
534
|
+
this._onUpdateTextures(textureSize);
|
|
535
|
+
this._updateSubTextures(this._splatPositions, covA, covB, colorArray, firstNewLine, textureSize.y - firstNewLine, sh);
|
|
1801
536
|
}
|
|
1802
537
|
else {
|
|
1803
|
-
this.
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
538
|
+
this._updateTextures(covA, covB, colorArray, sh);
|
|
539
|
+
}
|
|
540
|
+
this.getBoundingInfo().reConstruct(minimum, maximum, this.getWorldMatrix());
|
|
541
|
+
this.setEnabled(true);
|
|
542
|
+
this._cachedBoundingMin = minimum.clone();
|
|
543
|
+
this._cachedBoundingMax = maximum.clone();
|
|
544
|
+
this._notifyWorkerNewData();
|
|
545
|
+
// --- Create proxy meshes ---
|
|
546
|
+
const proxyMeshes = [];
|
|
547
|
+
for (let i = 0; i < others.length; i++) {
|
|
548
|
+
const other = others[i];
|
|
549
|
+
const newPartIndex = assignedPartIndices[i];
|
|
550
|
+
const partWorldMatrix = other.getWorldMatrix();
|
|
551
|
+
this.setWorldMatrixForPart(newPartIndex, partWorldMatrix);
|
|
552
|
+
const proxyMesh = new GaussianSplattingPartProxyMesh(other.name, this.getScene(), this, other, newPartIndex);
|
|
553
|
+
if (disposeOthers) {
|
|
554
|
+
other.dispose();
|
|
555
|
+
}
|
|
556
|
+
const quaternion = new Quaternion();
|
|
557
|
+
partWorldMatrix.decompose(proxyMesh.scaling, quaternion, proxyMesh.position);
|
|
558
|
+
proxyMesh.rotationQuaternion = quaternion;
|
|
559
|
+
proxyMesh.computeWorldMatrix(true);
|
|
560
|
+
this._partProxies[newPartIndex] = proxyMesh;
|
|
561
|
+
proxyMeshes.push(proxyMesh);
|
|
562
|
+
}
|
|
563
|
+
return { proxyMeshes, assignedPartIndices };
|
|
564
|
+
}
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
// Public compound API
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
1815
568
|
/**
|
|
1816
569
|
* Add another mesh to this mesh, as a new part. This makes the current mesh a compound, if not already.
|
|
1817
|
-
*
|
|
1818
|
-
* @param other - The other mesh to add.
|
|
570
|
+
* The source mesh's splat data is read directly — no merged CPU buffer is constructed.
|
|
571
|
+
* @param other - The other mesh to add. Must be fully loaded before calling this method.
|
|
1819
572
|
* @param disposeOther - Whether to dispose the other mesh after adding it to the current mesh.
|
|
1820
573
|
* @returns a placeholder mesh that can be used to manipulate the part transform
|
|
574
|
+
* @deprecated Use {@link GaussianSplattingCompoundMesh.addPart} instead.
|
|
1821
575
|
*/
|
|
1822
576
|
addPart(other, disposeOther = true) {
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
throw new Error(`Cannot add part, as the maximum part count (${maxPartCount}) has been reached`);
|
|
1826
|
-
}
|
|
1827
|
-
const splatCountA = this._vertexCount;
|
|
1828
|
-
const splatsDataA = splatCountA == 0 ? new ArrayBuffer(0) : this.splatsData;
|
|
1829
|
-
const shDataA = this.shData;
|
|
1830
|
-
const splatCountB = other._vertexCount;
|
|
1831
|
-
const splatsDataB = other.splatsData;
|
|
1832
|
-
const shDataB = other.shData;
|
|
1833
|
-
const mergedShDataLength = Math.max(shDataA?.length || 0, shDataB?.length || 0);
|
|
1834
|
-
const hasMergedShData = shDataA !== null && shDataB !== null;
|
|
1835
|
-
// Sanity checks
|
|
1836
|
-
if (!splatsDataA) {
|
|
1837
|
-
throw new Error(`To call addPart(), the current mesh must be loaded with keepInRam: true`);
|
|
1838
|
-
}
|
|
1839
|
-
const expectedSplatsDataSizeA = splatCountA * GaussianSplattingMesh._RowOutputLength;
|
|
1840
|
-
if (splatsDataA.byteLength !== expectedSplatsDataSizeA) {
|
|
1841
|
-
throw new Error(`splatsDataA size (${splatsDataA.byteLength}) does not match expected size (${expectedSplatsDataSizeA})`);
|
|
1842
|
-
}
|
|
1843
|
-
if (!splatsDataB) {
|
|
1844
|
-
throw new Error(`To call addPart(), the other mesh must be loaded with keepInRam: true`);
|
|
1845
|
-
}
|
|
1846
|
-
const expectedSplatsDataSizeB = splatCountB * GaussianSplattingMesh._RowOutputLength;
|
|
1847
|
-
if (splatsDataB.byteLength !== expectedSplatsDataSizeB) {
|
|
1848
|
-
throw new Error(`splatsDataB size (${splatsDataB.byteLength}) does not match expected size (${expectedSplatsDataSizeB})`);
|
|
1849
|
-
}
|
|
1850
|
-
if (other.partIndices) {
|
|
1851
|
-
throw new Error(`To call addPart(), the other mesh must not be a compound`);
|
|
1852
|
-
}
|
|
1853
|
-
// Concatenate splatsData (ArrayBuffer)
|
|
1854
|
-
const mergedSplatsData = new Uint8Array(splatsDataA.byteLength + splatsDataB.byteLength);
|
|
1855
|
-
mergedSplatsData.set(new Uint8Array(splatsDataA), 0);
|
|
1856
|
-
mergedSplatsData.set(new Uint8Array(splatsDataB), splatsDataA.byteLength);
|
|
1857
|
-
let mergedShData = undefined;
|
|
1858
|
-
if (hasMergedShData) {
|
|
1859
|
-
// Note: We need to calculate the texture size and pad accordingly
|
|
1860
|
-
// Each SH texture texel stores 16 bytes (4 RGBA uint32 components)
|
|
1861
|
-
const bytesPerTexel = 16;
|
|
1862
|
-
const totalSplatCount = splatCountA + splatCountB;
|
|
1863
|
-
mergedShData = [];
|
|
1864
|
-
for (let i = 0; i < mergedShDataLength; i++) {
|
|
1865
|
-
const mergedShDataItem = new Uint8Array(totalSplatCount * bytesPerTexel);
|
|
1866
|
-
if (i < (shDataA?.length ?? 0)) {
|
|
1867
|
-
mergedShDataItem.set(shDataA[i], 0);
|
|
1868
|
-
}
|
|
1869
|
-
if (i < (shDataB?.length ?? 0)) {
|
|
1870
|
-
const byteOffset = bytesPerTexel * splatCountA;
|
|
1871
|
-
mergedShDataItem.set(shDataB[i], byteOffset);
|
|
1872
|
-
}
|
|
1873
|
-
mergedShData.push(mergedShDataItem);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
// Concatenate partIndices (Uint8Array)
|
|
1877
|
-
let newPartIndex = this.partCount;
|
|
1878
|
-
let partIndicesA = this.partIndices;
|
|
1879
|
-
if (!partIndicesA) {
|
|
1880
|
-
partIndicesA = new Uint8Array(splatCountA);
|
|
1881
|
-
newPartIndex = splatCountA > 0 ? 1 : 0;
|
|
1882
|
-
//newPartIndex = 1;
|
|
1883
|
-
}
|
|
1884
|
-
if (partIndicesA.length < splatCountA) {
|
|
1885
|
-
throw new Error(`partIndices length (${partIndicesA.length}) should be at least vertexCount (${splatCountA}) in the current mesh`);
|
|
1886
|
-
}
|
|
1887
|
-
const partIndicesB = new Uint8Array(splatCountB).fill(newPartIndex);
|
|
1888
|
-
const mergedPartIndices = new Uint8Array(splatCountA + splatCountB);
|
|
1889
|
-
mergedPartIndices.set(partIndicesA.slice(0, splatCountA), 0);
|
|
1890
|
-
mergedPartIndices.set(partIndicesB, splatCountA);
|
|
1891
|
-
this.updateData(mergedSplatsData.buffer, mergedShData, { flipY: false }, mergedPartIndices);
|
|
1892
|
-
// Merge part matrices (TODO)
|
|
1893
|
-
const partWorldMatrix = other.getWorldMatrix();
|
|
1894
|
-
this.setWorldMatrixForPart(newPartIndex, partWorldMatrix);
|
|
1895
|
-
// Create a proxy mesh to manipulate the part transform
|
|
1896
|
-
const proxyMesh = new GaussianSplattingPartProxyMesh(other.name, this.getScene(), this, other, newPartIndex);
|
|
1897
|
-
if (disposeOther) {
|
|
1898
|
-
other.dispose();
|
|
1899
|
-
}
|
|
1900
|
-
// Set the initial world matrix
|
|
1901
|
-
const quaternion = new Quaternion();
|
|
1902
|
-
partWorldMatrix.decompose(proxyMesh.scaling, quaternion, proxyMesh.position);
|
|
1903
|
-
proxyMesh.rotationQuaternion = quaternion;
|
|
1904
|
-
proxyMesh.computeWorldMatrix(true);
|
|
1905
|
-
// Store the proxy in the map
|
|
1906
|
-
this._partProxies.set(newPartIndex, proxyMesh);
|
|
1907
|
-
return proxyMesh;
|
|
577
|
+
const { proxyMeshes } = this._addPartsInternal([other], disposeOther);
|
|
578
|
+
return proxyMeshes[0];
|
|
1908
579
|
}
|
|
1909
580
|
/**
|
|
1910
581
|
* Remove a part from this compound mesh.
|
|
582
|
+
* The remaining parts are rebuilt directly from their stored source mesh references —
|
|
583
|
+
* no merged CPU splat buffer is read back. The current mesh is reset to a plain (single-part)
|
|
584
|
+
* state and then each remaining source is re-added via addParts.
|
|
1911
585
|
* @param index - The index of the part to remove
|
|
586
|
+
* @deprecated Use {@link GaussianSplattingCompoundMesh.removePart} instead.
|
|
1912
587
|
*/
|
|
1913
588
|
removePart(index) {
|
|
1914
589
|
if (index < 0 || index >= this.partCount) {
|
|
1915
590
|
throw new Error(`Part index ${index} is out of range [0, ${this.partCount})`);
|
|
1916
591
|
}
|
|
1917
|
-
//
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
}
|
|
1924
|
-
const splatCount = this._vertexCount;
|
|
1925
|
-
const rowLength = GaussianSplattingMesh._RowOutputLength;
|
|
1926
|
-
// Count splats that will remain (not in the removed part)
|
|
1927
|
-
let newSplatCount = 0;
|
|
1928
|
-
for (let i = 0; i < splatCount; i++) {
|
|
1929
|
-
if (partIndices[i] !== index) {
|
|
1930
|
-
newSplatCount++;
|
|
592
|
+
// Collect surviving proxy objects (sorted by current part index so part 0 is added first)
|
|
593
|
+
const survivors = [];
|
|
594
|
+
for (let proxyIndex = 0; proxyIndex < this._partProxies.length; proxyIndex++) {
|
|
595
|
+
const proxy = this._partProxies[proxyIndex];
|
|
596
|
+
if (proxy && proxyIndex !== index) {
|
|
597
|
+
survivors.push({ proxyMesh: proxy, oldIndex: proxyIndex, worldMatrix: proxy.getWorldMatrix().clone(), visibility: this._partVisibility[proxyIndex] ?? 1.0 });
|
|
1931
598
|
}
|
|
1932
599
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
const bytesPerTexel = 16;
|
|
1939
|
-
newShData = [];
|
|
1940
|
-
for (let i = 0; i < shData.length; i++) {
|
|
1941
|
-
newShData.push(new Uint8Array(newSplatCount * bytesPerTexel));
|
|
600
|
+
survivors.sort((a, b) => a.oldIndex - b.oldIndex);
|
|
601
|
+
// Validate every survivor still has its source data. If even one is missing we cannot rebuild.
|
|
602
|
+
for (const { proxyMesh } of survivors) {
|
|
603
|
+
if (!proxyMesh.proxiedMesh._splatsData) {
|
|
604
|
+
throw new Error(`Cannot remove part: the source mesh for part "${proxyMesh.name}" no longer has its splat data available.`);
|
|
1942
605
|
}
|
|
1943
606
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
}
|
|
1951
|
-
// Copy splat data
|
|
1952
|
-
const srcOffset = readIndex * rowLength;
|
|
1953
|
-
const dstOffset = writeIndex * rowLength;
|
|
1954
|
-
newSplatsData.set(new Uint8Array(splatsData, srcOffset, rowLength), dstOffset);
|
|
1955
|
-
// Renumber part indices: indices > removed index get decremented
|
|
1956
|
-
newPartIndices[writeIndex] = currentPartIndex > index ? currentPartIndex - 1 : currentPartIndex;
|
|
1957
|
-
// Copy SH data if present
|
|
1958
|
-
if (shData && newShData) {
|
|
1959
|
-
const bytesPerTexel = 16;
|
|
1960
|
-
for (let shIndex = 0; shIndex < shData.length; shIndex++) {
|
|
1961
|
-
const srcShOffset = readIndex * bytesPerTexel;
|
|
1962
|
-
const dstShOffset = writeIndex * bytesPerTexel;
|
|
1963
|
-
newShData[shIndex].set(new Uint8Array(shData[shIndex].buffer, srcShOffset, bytesPerTexel), dstShOffset);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
writeIndex++;
|
|
1967
|
-
}
|
|
1968
|
-
// Remove the part matrix and visibility
|
|
1969
|
-
this._partMatrices.splice(index, 1);
|
|
1970
|
-
this._partVisibility.splice(index, 1);
|
|
1971
|
-
// Update worker with new part matrices
|
|
607
|
+
// --- Reset this mesh to an empty state ---
|
|
608
|
+
// Terminate the sort worker before zeroing _vertexCount. The worker's onmessage handler
|
|
609
|
+
// compares depthMix.length against (_vertexCount + 15) & ~0xf; with _vertexCount = 0 that
|
|
610
|
+
// becomes 16, which causes a forced re-sort loop on stale data and resets _canPostToWorker
|
|
611
|
+
// to true, defeating the gate below. The worker will be re-instantiated naturally after
|
|
612
|
+
// the rebuild via the first _postToWorker call.
|
|
1972
613
|
if (this._worker) {
|
|
1973
|
-
this._worker.
|
|
1974
|
-
|
|
1975
|
-
// Update the mesh with the new data
|
|
1976
|
-
this.updateData(newSplatsData.buffer, newShData, { flipY: false }, newPartIndices);
|
|
1977
|
-
// Dispose and remove the proxy for the removed part
|
|
1978
|
-
const proxyToRemove = this._partProxies.get(index);
|
|
1979
|
-
if (proxyToRemove) {
|
|
1980
|
-
proxyToRemove.dispose();
|
|
1981
|
-
this._partProxies.delete(index);
|
|
614
|
+
this._worker.terminate();
|
|
615
|
+
this._worker = null;
|
|
1982
616
|
}
|
|
1983
|
-
//
|
|
1984
|
-
|
|
1985
|
-
this.
|
|
1986
|
-
|
|
1987
|
-
|
|
617
|
+
// Dispose and null GPU textures so _updateTextures sees firstTime=true and creates
|
|
618
|
+
// fresh GPU textures.
|
|
619
|
+
this._covariancesATexture?.dispose();
|
|
620
|
+
this._covariancesBTexture?.dispose();
|
|
621
|
+
this._centersTexture?.dispose();
|
|
622
|
+
this._colorsTexture?.dispose();
|
|
623
|
+
this._covariancesATexture = null;
|
|
624
|
+
this._covariancesBTexture = null;
|
|
625
|
+
this._centersTexture = null;
|
|
626
|
+
this._colorsTexture = null;
|
|
627
|
+
if (this._shTextures) {
|
|
628
|
+
for (const t of this._shTextures) {
|
|
629
|
+
t.dispose();
|
|
1988
630
|
}
|
|
1989
|
-
|
|
1990
|
-
// Remove and re-add with updated indices
|
|
1991
|
-
for (const [oldIndex, proxy] of proxiesToUpdate) {
|
|
1992
|
-
this._partProxies.delete(oldIndex);
|
|
1993
|
-
// Update the proxy's internal partIndex
|
|
1994
|
-
proxy.updatePartIndex(oldIndex - 1);
|
|
1995
|
-
this._partProxies.set(oldIndex - 1, proxy);
|
|
631
|
+
this._shTextures = null;
|
|
1996
632
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
* @param transform defines the transform matrix to use
|
|
2001
|
-
* @returns the current mesh
|
|
2002
|
-
*/
|
|
2003
|
-
bakeTransformIntoVertices(transform) {
|
|
2004
|
-
const arrayBuffer = this.splatsData;
|
|
2005
|
-
if (!arrayBuffer) {
|
|
2006
|
-
Logger.Error("Cannot bake transform into vertices if splatsData is not kept in RAM");
|
|
2007
|
-
return this;
|
|
633
|
+
if (this._partIndicesTexture) {
|
|
634
|
+
this._partIndicesTexture.dispose();
|
|
635
|
+
this._partIndicesTexture = null;
|
|
2008
636
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
637
|
+
this._vertexCount = 0;
|
|
638
|
+
this._splatPositions = null;
|
|
639
|
+
this._partIndices = null;
|
|
640
|
+
this._partMatrices = [];
|
|
641
|
+
this._partVisibility = [];
|
|
642
|
+
this._cachedBoundingMin = null;
|
|
643
|
+
this._cachedBoundingMax = null;
|
|
644
|
+
// Remove the proxy for the removed part and dispose it
|
|
645
|
+
const proxyToRemove = this._partProxies[index];
|
|
646
|
+
if (proxyToRemove) {
|
|
647
|
+
proxyToRemove.dispose();
|
|
2018
648
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
for (index = 0; index < this._vertexCount; index++) {
|
|
2027
|
-
const floatIndex = index * 8; // 8 floats per splat (center.x, center.y, center.z, scale.x, scale.y, scale.z, ...)
|
|
2028
|
-
Vector3.TransformCoordinatesFromFloatsToRef(fBuffer[floatIndex], fBuffer[floatIndex + 1], fBuffer[floatIndex + 2], transform, temp);
|
|
2029
|
-
fBuffer[floatIndex] = temp.x;
|
|
2030
|
-
fBuffer[floatIndex + 1] = temp.y;
|
|
2031
|
-
fBuffer[floatIndex + 2] = temp.z;
|
|
2032
|
-
// Apply uniform scaling to splat scales
|
|
2033
|
-
fBuffer[floatIndex + 3] *= scaleX;
|
|
2034
|
-
fBuffer[floatIndex + 4] *= scaleX;
|
|
2035
|
-
fBuffer[floatIndex + 5] *= scaleX;
|
|
2036
|
-
// Unpack quaternion from uint8array (matching _GetSplat packing convention)
|
|
2037
|
-
quaternion.set((uBuffer[32 * index + 28 + 1] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 2] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 3] - 127.5) / 127.5, (uBuffer[32 * index + 28 + 0] - 127.5) / 127.5);
|
|
2038
|
-
quaternion.normalize();
|
|
2039
|
-
// If there is a negative scaling, we need to flip the quaternion to keep the correct handedness
|
|
2040
|
-
if (this.scaling.x < 0) {
|
|
2041
|
-
quaternion.x = -quaternion.x;
|
|
2042
|
-
quaternion.w = -quaternion.w;
|
|
2043
|
-
}
|
|
2044
|
-
if (this.scaling.y < 0) {
|
|
2045
|
-
quaternion.y = -quaternion.y;
|
|
2046
|
-
quaternion.w = -quaternion.w;
|
|
2047
|
-
}
|
|
2048
|
-
if (this.scaling.z < 0) {
|
|
2049
|
-
quaternion.z = -quaternion.z;
|
|
2050
|
-
quaternion.w = -quaternion.w;
|
|
2051
|
-
}
|
|
2052
|
-
// Transform the quaternion
|
|
2053
|
-
transformedQuaternion.multiplyToRef(quaternion, quaternion);
|
|
2054
|
-
quaternion.normalize();
|
|
2055
|
-
// Pack quaternion back to uint8array (matching _GetSplat packing convention)
|
|
2056
|
-
uBuffer[32 * index + 28 + 0] = Math.round(quaternion.w * 127.5 + 127.5);
|
|
2057
|
-
uBuffer[32 * index + 28 + 1] = Math.round(quaternion.x * 127.5 + 127.5);
|
|
2058
|
-
uBuffer[32 * index + 28 + 2] = Math.round(quaternion.y * 127.5 + 127.5);
|
|
2059
|
-
uBuffer[32 * index + 28 + 3] = Math.round(quaternion.z * 127.5 + 127.5);
|
|
649
|
+
this._partProxies = [];
|
|
650
|
+
// Rebuild from surviving sources. _addPartsInternal assigns part indices in order 0, 1, 2, …
|
|
651
|
+
// so the new index for each survivor is simply its position in the survivors array.
|
|
652
|
+
if (survivors.length === 0) {
|
|
653
|
+
// Nothing left — leave the mesh empty.
|
|
654
|
+
this.setEnabled(false);
|
|
655
|
+
return;
|
|
2060
656
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
657
|
+
// Gate the sort worker: suppress any sort request until the full rebuild is committed.
|
|
658
|
+
this._rebuilding = true;
|
|
659
|
+
this._canPostToWorker = false;
|
|
660
|
+
const sources = survivors.map((s) => s.proxyMesh.proxiedMesh);
|
|
661
|
+
const { proxyMeshes: newProxies } = this._addPartsInternal(sources, false);
|
|
662
|
+
// Restore world matrices and re-map proxies
|
|
663
|
+
for (let i = 0; i < survivors.length; i++) {
|
|
664
|
+
const oldProxy = survivors[i].proxyMesh;
|
|
665
|
+
const newProxy = newProxies[i];
|
|
666
|
+
const newPartIndex = newProxy.partIndex;
|
|
667
|
+
// Restore the world matrix and visibility the user had set on the old proxy
|
|
668
|
+
this.setWorldMatrixForPart(newPartIndex, survivors[i].worldMatrix);
|
|
669
|
+
this.setPartVisibility(newPartIndex, survivors[i].visibility);
|
|
670
|
+
const quaternion = new Quaternion();
|
|
671
|
+
survivors[i].worldMatrix.decompose(newProxy.scaling, quaternion, newProxy.position);
|
|
672
|
+
newProxy.rotationQuaternion = quaternion;
|
|
673
|
+
newProxy.computeWorldMatrix(true);
|
|
674
|
+
// Update the old proxy's index so any existing user references still work
|
|
675
|
+
oldProxy.updatePartIndex(newPartIndex);
|
|
676
|
+
this._partProxies[newPartIndex] = oldProxy;
|
|
677
|
+
// newProxy is redundant — it was created inside _addPartsInternal; dispose it
|
|
678
|
+
newProxy.dispose();
|
|
679
|
+
}
|
|
680
|
+
// Rebuild is complete: all partMatrices are now set correctly.
|
|
681
|
+
// Post the final complete set and fire one sort.
|
|
682
|
+
this._rebuilding = false;
|
|
683
|
+
// Break TypeScript's flow narrowing — _addPartsInternal may have reinstantiated _worker.
|
|
684
|
+
const workerAfterRebuild = this._worker;
|
|
685
|
+
workerAfterRebuild?.postMessage({ partMatrices: this._partMatrices.map((matrix) => new Float32Array(matrix.m)) });
|
|
686
|
+
this._canPostToWorker = true;
|
|
687
|
+
this._postToWorker(true);
|
|
2063
688
|
}
|
|
2064
689
|
}
|
|
2065
|
-
GaussianSplattingMesh._RowOutputLength = 3 * 4 + 3 * 4 + 4 + 4; // Vector3 position, Vector3 scale, 1 u8 quaternion, 1 color with alpha
|
|
2066
|
-
GaussianSplattingMesh._SH_C0 = 0.28209479177387814;
|
|
2067
|
-
// batch size between 2 yield calls. This value is a tradeoff between updates overhead and framerate hiccups
|
|
2068
|
-
// This step is faster the PLY conversion. So batch size can be bigger
|
|
2069
|
-
GaussianSplattingMesh._SplatBatchSize = 327680;
|
|
2070
|
-
// batch size between 2 yield calls during the PLY to splat conversion.
|
|
2071
|
-
GaussianSplattingMesh._PlyConversionBatchSize = 32768;
|
|
2072
|
-
GaussianSplattingMesh._BatchSize = 16; // 16 splats per instance
|
|
2073
|
-
GaussianSplattingMesh._DefaultViewUpdateThreshold = 1e-4;
|
|
2074
|
-
/**
|
|
2075
|
-
* Set the number of batch (a batch is 16384 splats) after which a display update is performed
|
|
2076
|
-
* A value of 0 (default) means display update will not happens before splat is ready.
|
|
2077
|
-
*/
|
|
2078
|
-
GaussianSplattingMesh.ProgressiveUpdateAmount = 0;
|
|
2079
|
-
GaussianSplattingMesh._CreateWorker = function (self) {
|
|
2080
|
-
let positions;
|
|
2081
|
-
let depthMix;
|
|
2082
|
-
let indices;
|
|
2083
|
-
let floatMix;
|
|
2084
|
-
let partIndices;
|
|
2085
|
-
let partMatrices;
|
|
2086
|
-
self.onmessage = (e) => {
|
|
2087
|
-
// updated on init
|
|
2088
|
-
if (e.data.positions) {
|
|
2089
|
-
positions = e.data.positions;
|
|
2090
|
-
}
|
|
2091
|
-
// update on rig node changed
|
|
2092
|
-
else if (e.data.partMatrices) {
|
|
2093
|
-
partMatrices = e.data.partMatrices;
|
|
2094
|
-
}
|
|
2095
|
-
// update on rig node indices changed
|
|
2096
|
-
else if (e.data.partIndices !== undefined) {
|
|
2097
|
-
partIndices = e.data.partIndices;
|
|
2098
|
-
}
|
|
2099
|
-
// update on view changed
|
|
2100
|
-
else {
|
|
2101
|
-
const cameraId = e.data.cameraId;
|
|
2102
|
-
const globalWorldMatrix = e.data.worldMatrix;
|
|
2103
|
-
const cameraForward = e.data.cameraForward;
|
|
2104
|
-
const cameraPosition = e.data.cameraPosition;
|
|
2105
|
-
const vertexCountPadded = (positions.length / 4 + 15) & ~0xf;
|
|
2106
|
-
if (!positions || !cameraForward) {
|
|
2107
|
-
// Sanity check, it shouldn't happen!
|
|
2108
|
-
throw new Error("positions or camera info is not defined!");
|
|
2109
|
-
}
|
|
2110
|
-
depthMix = e.data.depthMix;
|
|
2111
|
-
indices = new Uint32Array(depthMix.buffer);
|
|
2112
|
-
floatMix = new Float32Array(depthMix.buffer);
|
|
2113
|
-
// Sort
|
|
2114
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2115
|
-
indices[2 * j] = j;
|
|
2116
|
-
}
|
|
2117
|
-
// depth = dot(cameraForward, worldPos - cameraPos)
|
|
2118
|
-
const camDot = cameraForward[0] * cameraPosition[0] + cameraForward[1] * cameraPosition[1] + cameraForward[2] * cameraPosition[2];
|
|
2119
|
-
const computeDepthCoeffs = (m) => {
|
|
2120
|
-
return [
|
|
2121
|
-
cameraForward[0] * m[0] + cameraForward[1] * m[1] + cameraForward[2] * m[2],
|
|
2122
|
-
cameraForward[0] * m[4] + cameraForward[1] * m[5] + cameraForward[2] * m[6],
|
|
2123
|
-
cameraForward[0] * m[8] + cameraForward[1] * m[9] + cameraForward[2] * m[10],
|
|
2124
|
-
cameraForward[0] * m[12] + cameraForward[1] * m[13] + cameraForward[2] * m[14] - camDot,
|
|
2125
|
-
];
|
|
2126
|
-
};
|
|
2127
|
-
if (partMatrices && partIndices) {
|
|
2128
|
-
// Precompute depth coefficients for each rig node
|
|
2129
|
-
const depthCoeffs = partMatrices.map((m) => computeDepthCoeffs(m));
|
|
2130
|
-
// NB: For performance reasons, we assume that part indices are valid
|
|
2131
|
-
const length = partIndices.length;
|
|
2132
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2133
|
-
// NB: We need this 'min' because vertex array is padded, not partIndices
|
|
2134
|
-
const partIndex = partIndices[Math.min(j, length - 1)];
|
|
2135
|
-
const coeff = depthCoeffs[partIndex];
|
|
2136
|
-
floatMix[2 * j + 1] = coeff[0] * positions[4 * j + 0] + coeff[1] * positions[4 * j + 1] + coeff[2] * positions[4 * j + 2] + coeff[3];
|
|
2137
|
-
// instead of using minus to sort back to front, we use bitwise not operator to invert the order of indices
|
|
2138
|
-
// might not be faster but a minus sign implies a reference value that may not be enough and will decrease floatting precision
|
|
2139
|
-
indices[2 * j + 1] = ~indices[2 * j + 1];
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
else {
|
|
2143
|
-
// Compute depth coefficients from global world matrix
|
|
2144
|
-
const [a, b, c, d] = computeDepthCoeffs(globalWorldMatrix);
|
|
2145
|
-
for (let j = 0; j < vertexCountPadded; j++) {
|
|
2146
|
-
floatMix[2 * j + 1] = a * positions[4 * j + 0] + b * positions[4 * j + 1] + c * positions[4 * j + 2] + d;
|
|
2147
|
-
indices[2 * j + 1] = ~indices[2 * j + 1];
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
depthMix.sort();
|
|
2151
|
-
self.postMessage({ depthMix, cameraId }, [depthMix.buffer]);
|
|
2152
|
-
}
|
|
2153
|
-
};
|
|
2154
|
-
};
|
|
2155
690
|
//# sourceMappingURL=gaussianSplattingMesh.js.map
|