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