@myned-ai/gsplat-flame-avatar-renderer 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +4 -29
  2. package/dist/gsplat-flame-avatar-renderer.cjs.js +12875 -0
  3. package/dist/{gsplat-flame-avatar-renderer.umd.js.map → gsplat-flame-avatar-renderer.cjs.js.map} +1 -1
  4. package/dist/gsplat-flame-avatar-renderer.esm.js +1 -1
  5. package/package.json +5 -3
  6. package/src/api/index.js +7 -0
  7. package/src/buffers/SplatBuffer.js +1394 -0
  8. package/src/buffers/SplatBufferGenerator.js +41 -0
  9. package/src/buffers/SplatPartitioner.js +110 -0
  10. package/src/buffers/UncompressedSplatArray.js +106 -0
  11. package/src/buffers/index.js +11 -0
  12. package/src/core/SplatGeometry.js +48 -0
  13. package/src/core/SplatMesh.js +2620 -0
  14. package/src/core/SplatScene.js +43 -0
  15. package/src/core/SplatTree.js +200 -0
  16. package/src/core/Viewer.js +2895 -0
  17. package/src/core/index.js +13 -0
  18. package/src/enums/EngineConstants.js +58 -0
  19. package/src/enums/LogLevel.js +13 -0
  20. package/src/enums/RenderMode.js +11 -0
  21. package/src/enums/SceneFormat.js +21 -0
  22. package/src/enums/SceneRevealMode.js +11 -0
  23. package/src/enums/SplatRenderMode.js +10 -0
  24. package/src/enums/index.js +13 -0
  25. package/src/flame/FlameAnimator.js +271 -0
  26. package/src/flame/FlameConstants.js +21 -0
  27. package/src/flame/FlameTextureManager.js +293 -0
  28. package/src/flame/index.js +22 -0
  29. package/src/flame/utils.js +50 -0
  30. package/src/index.js +39 -0
  31. package/src/loaders/DirectLoadError.js +14 -0
  32. package/src/loaders/INRIAV1PlyParser.js +223 -0
  33. package/src/loaders/PlyLoader.js +261 -0
  34. package/src/loaders/PlyParser.js +19 -0
  35. package/src/loaders/PlyParserUtils.js +311 -0
  36. package/src/loaders/index.js +13 -0
  37. package/src/materials/SplatMaterial.js +1065 -0
  38. package/src/materials/SplatMaterial2D.js +358 -0
  39. package/src/materials/SplatMaterial3D.js +278 -0
  40. package/src/materials/index.js +11 -0
  41. package/src/raycaster/Hit.js +37 -0
  42. package/src/raycaster/Ray.js +123 -0
  43. package/src/raycaster/Raycaster.js +175 -0
  44. package/src/raycaster/index.js +10 -0
  45. package/src/renderer/AnimationManager.js +574 -0
  46. package/src/renderer/AppConstants.js +101 -0
  47. package/src/renderer/GaussianSplatRenderer.js +695 -0
  48. package/src/renderer/index.js +24 -0
  49. package/src/utils/LoaderUtils.js +65 -0
  50. package/src/utils/Util.js +375 -0
  51. package/src/utils/index.js +9 -0
  52. package/src/worker/SortWorker.js +284 -0
  53. package/src/worker/index.js +8 -0
  54. package/dist/gsplat-flame-avatar-renderer.esm.min.js +0 -2
  55. package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +0 -1
  56. package/dist/gsplat-flame-avatar-renderer.umd.js +0 -12876
  57. package/dist/gsplat-flame-avatar-renderer.umd.min.js +0 -2
  58. package/dist/gsplat-flame-avatar-renderer.umd.min.js.map +0 -1
  59. package/dist/gsplat-flame-avatar.esm.js +0 -12755
  60. package/dist/gsplat-flame-avatar.esm.js.map +0 -1
  61. package/dist/gsplat-flame-avatar.esm.min.js +0 -2
  62. package/dist/gsplat-flame-avatar.esm.min.js.map +0 -1
  63. package/dist/gsplat-flame-avatar.umd.js +0 -12876
  64. package/dist/gsplat-flame-avatar.umd.js.map +0 -1
  65. package/dist/gsplat-flame-avatar.umd.min.js +0 -2
  66. package/dist/gsplat-flame-avatar.umd.min.js.map +0 -1
@@ -0,0 +1,2620 @@
1
+ /**
2
+ * SplatMesh
3
+ *
4
+ * Based on @mkkellogg/gaussian-splats-3d (MIT License)
5
+ * https://github.com/mkkellogg/GaussianSplats3D
6
+ *
7
+ * HEAVILY MODIFIED for FLAME avatar support:
8
+ * - Added FLAME bone texture handling
9
+ * - Added expression blendshape support
10
+ * - Extended with skinning data management
11
+ * - Additional ~800 lines of FLAME-specific code
12
+ */
13
+
14
+ import { Box3, BufferGeometry, DataTexture, FloatType, HalfFloatType, Matrix4, Mesh, MeshBasicMaterial, Quaternion, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, RGBIntegerFormat, Scene, UnsignedByteType, UnsignedIntType, Vector2, Vector3, Vector4, WebGLRenderer } from 'three';
15
+ import { SplatRenderMode } from '../enums/SplatRenderMode.js';
16
+ import { LogLevel } from '../enums/LogLevel.js';
17
+ import { SceneRevealMode } from '../enums/SceneRevealMode.js';
18
+ import { Constants } from '../enums/EngineConstants.js';
19
+ import { SplatScene } from './SplatScene.js';
20
+ import { SplatGeometry } from './SplatGeometry.js';
21
+ import { SplatTree } from './SplatTree.js';
22
+ import { SplatMaterial3D } from '../materials/SplatMaterial3D.js';
23
+ import { SplatMaterial2D } from '../materials/SplatMaterial2D.js';
24
+ import { getSphericalHarmonicsComponentCountForDegree, uintEncodedFloat, rgbaArrayToInteger, clamp } from '../utils/Util.js';
25
+
26
+ // Dummy geometry and material for initial Mesh construction
27
+ const dummyGeometry = new BufferGeometry();
28
+ const dummyMaterial = new MeshBasicMaterial();
29
+
30
+ /**
31
+ * WebGL Extensions helper (from Three.js internals)
32
+ */
33
+ function WebGLExtensions$1(gl) {
34
+ const extensions = {};
35
+
36
+ function getExtension(name) {
37
+ if (extensions[name] !== undefined) {
38
+ return extensions[name];
39
+ }
40
+
41
+ let extension;
42
+ switch (name) {
43
+ case 'WEBGL_depth_texture':
44
+ extension = gl.getExtension('WEBGL_depth_texture') || gl.getExtension('MOZ_WEBGL_depth_texture') ||
45
+ gl.getExtension('WEBKIT_WEBGL_depth_texture');
46
+ break;
47
+ case 'EXT_texture_filter_anisotropic':
48
+ extension = gl.getExtension('EXT_texture_filter_anisotropic') ||
49
+ gl.getExtension('MOZ_EXT_texture_filter_anisotropic') ||
50
+ gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
51
+ break;
52
+ case 'WEBGL_compressed_texture_s3tc':
53
+ extension = gl.getExtension('WEBGL_compressed_texture_s3tc') ||
54
+ gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc') ||
55
+ gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
56
+ break;
57
+ case 'WEBGL_compressed_texture_pvrtc':
58
+ extension = gl.getExtension('WEBGL_compressed_texture_pvrtc') ||
59
+ gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
60
+ break;
61
+ default:
62
+ extension = gl.getExtension(name);
63
+ }
64
+
65
+ extensions[name] = extension;
66
+ return extension;
67
+ }
68
+
69
+ return {
70
+ has: function(name) {
71
+ return getExtension(name) !== null;
72
+ },
73
+ init: function(capabilities) {
74
+ if (capabilities.isWebGL2) {
75
+ getExtension('EXT_color_buffer_float');
76
+ getExtension('WEBGL_clip_cull_distance');
77
+ } else {
78
+ getExtension('WEBGL_depth_texture');
79
+ getExtension('OES_texture_float');
80
+ getExtension('OES_texture_half_float');
81
+ getExtension('OES_texture_half_float_linear');
82
+ getExtension('OES_standard_derivatives');
83
+ getExtension('OES_element_index_uint');
84
+ getExtension('OES_vertex_array_object');
85
+ getExtension('ANGLE_instanced_arrays');
86
+ }
87
+ getExtension('OES_texture_float_linear');
88
+ getExtension('EXT_color_buffer_half_float');
89
+ getExtension('WEBGL_multisampled_render_to_texture');
90
+ },
91
+ get: function(name) {
92
+ const extension = getExtension(name);
93
+ if (extension === null) {
94
+ console.warn('THREE.WebGLRenderer: ' + name + ' extension not supported.');
95
+ }
96
+ return extension;
97
+ }
98
+ };
99
+ }
100
+
101
+ /**
102
+ * WebGL Capabilities helper (from Three.js internals)
103
+ */
104
+ function WebGLCapabilities$1(gl, extensions, parameters) {
105
+ let maxAnisotropy;
106
+
107
+ function getMaxAnisotropy() {
108
+ if (maxAnisotropy !== undefined) return maxAnisotropy;
109
+ if (extensions.has('EXT_texture_filter_anisotropic') === true) {
110
+ const extension = extensions.get('EXT_texture_filter_anisotropic');
111
+ maxAnisotropy = gl.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
112
+ } else {
113
+ maxAnisotropy = 0;
114
+ }
115
+ return maxAnisotropy;
116
+ }
117
+
118
+ function getMaxPrecision(precision) {
119
+ if (precision === 'highp') {
120
+ if (gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 0 &&
121
+ gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {
122
+ return 'highp';
123
+ }
124
+ precision = 'mediump';
125
+ }
126
+ if (precision === 'mediump') {
127
+ if (gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT).precision > 0 &&
128
+ gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT).precision > 0) {
129
+ return 'mediump';
130
+ }
131
+ }
132
+ return 'lowp';
133
+ }
134
+
135
+ const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext';
136
+
137
+ let precision = parameters.precision !== undefined ? parameters.precision : 'highp';
138
+ const maxPrecision = getMaxPrecision(precision);
139
+
140
+ if (maxPrecision !== precision) {
141
+ console.warn('THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.');
142
+ precision = maxPrecision;
143
+ }
144
+
145
+ const drawBuffers = isWebGL2 || extensions.has('WEBGL_draw_buffers');
146
+ const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
147
+
148
+ const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
149
+ const maxVertexTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
150
+ const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
151
+ const maxCubemapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
152
+
153
+ const maxAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
154
+ const maxVertexUniforms = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
155
+ const maxVaryings = gl.getParameter(gl.MAX_VARYING_VECTORS);
156
+ const maxFragmentUniforms = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
157
+
158
+ const vertexTextures = maxVertexTextures > 0;
159
+ const floatFragmentTextures = isWebGL2 || extensions.has('OES_texture_float');
160
+ const floatVertexTextures = vertexTextures && floatFragmentTextures;
161
+
162
+ const maxSamples = isWebGL2 ? gl.getParameter(gl.MAX_SAMPLES) : 0;
163
+
164
+ return {
165
+ isWebGL2: isWebGL2,
166
+ drawBuffers: drawBuffers,
167
+ getMaxAnisotropy: getMaxAnisotropy,
168
+ getMaxPrecision: getMaxPrecision,
169
+ precision: precision,
170
+ logarithmicDepthBuffer: logarithmicDepthBuffer,
171
+ maxTextures: maxTextures,
172
+ maxVertexTextures: maxVertexTextures,
173
+ maxTextureSize: maxTextureSize,
174
+ maxCubemapSize: maxCubemapSize,
175
+ maxAttributes: maxAttributes,
176
+ maxVertexUniforms: maxVertexUniforms,
177
+ maxVaryings: maxVaryings,
178
+ maxFragmentUniforms: maxFragmentUniforms,
179
+ vertexTextures: vertexTextures,
180
+ floatFragmentTextures: floatFragmentTextures,
181
+ floatVertexTextures: floatVertexTextures,
182
+ maxSamples: maxSamples
183
+ };
184
+ }
185
+
186
+ /**
187
+ * WebGL Utils helper (from Three.js internals)
188
+ */
189
+ function WebGLUtils$1(gl, extensions, capabilities) {
190
+ const isWebGL2 = capabilities.isWebGL2;
191
+
192
+ function convert(p, colorSpace) {
193
+ let extension;
194
+
195
+ if (p === UnsignedByteType) return gl.UNSIGNED_BYTE;
196
+ if (p === 1017) return gl.UNSIGNED_SHORT_4_4_4_4;
197
+ if (p === 1018) return gl.UNSIGNED_SHORT_5_5_5_1;
198
+ if (p === 1010) return gl.BYTE;
199
+ if (p === 1011) return gl.SHORT;
200
+ if (p === 1012) return gl.UNSIGNED_SHORT;
201
+ if (p === 1013) return gl.INT;
202
+ if (p === 1014) return gl.UNSIGNED_INT;
203
+ if (p === FloatType) return gl.FLOAT;
204
+
205
+ if (p === HalfFloatType) {
206
+ if (isWebGL2) return gl.HALF_FLOAT;
207
+ extension = extensions.get('OES_texture_half_float');
208
+ if (extension !== null) {
209
+ return extension.HALF_FLOAT_OES;
210
+ } else {
211
+ return null;
212
+ }
213
+ }
214
+
215
+ if (p === 1021) return gl.ALPHA;
216
+ if (p === 1022) return gl.RGB;
217
+ if (p === RGBAFormat) return gl.RGBA;
218
+ if (p === 1024) return gl.LUMINANCE;
219
+ if (p === 1025) return gl.LUMINANCE_ALPHA;
220
+ if (p === 1026) return gl.DEPTH_COMPONENT;
221
+ if (p === 1027) return gl.DEPTH_STENCIL;
222
+
223
+ // WebGL2 formats
224
+ if (p === RedFormat) return gl.RED;
225
+ if (p === RedIntegerFormat) return gl.RED_INTEGER;
226
+ if (p === RGFormat) return gl.RG;
227
+ if (p === RGIntegerFormat) return gl.RG_INTEGER;
228
+ if (p === RGBIntegerFormat) return gl.RGB_INTEGER;
229
+ if (p === RGBAIntegerFormat) return gl.RGBA_INTEGER;
230
+
231
+ // S3TC
232
+ if (p === 33776 || p === 33777 || p === 33778 || p === 33779) {
233
+ extension = extensions.get('WEBGL_compressed_texture_s3tc');
234
+ if (extension !== null) {
235
+ if (p === 33776) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
236
+ if (p === 33777) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
237
+ if (p === 33778) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
238
+ if (p === 33779) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
239
+ } else {
240
+ return null;
241
+ }
242
+ }
243
+
244
+ // PVRTC
245
+ if (p === 35840 || p === 35841 || p === 35842 || p === 35843) {
246
+ extension = extensions.get('WEBGL_compressed_texture_pvrtc');
247
+ if (extension !== null) {
248
+ if (p === 35840) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
249
+ if (p === 35841) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
250
+ if (p === 35842) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
251
+ if (p === 35843) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
252
+ } else {
253
+ return null;
254
+ }
255
+ }
256
+
257
+ // ETC
258
+ if (p === 36196) {
259
+ extension = extensions.get('WEBGL_compressed_texture_etc1');
260
+ if (extension !== null) {
261
+ return extension.COMPRESSED_RGB_ETC1_WEBGL;
262
+ } else {
263
+ return null;
264
+ }
265
+ }
266
+
267
+ // ASTC
268
+ if (p >= 37808 && p <= 37814 || p >= 37840 && p <= 37846) {
269
+ extension = extensions.get('WEBGL_compressed_texture_astc');
270
+ if (extension !== null) {
271
+ return p;
272
+ } else {
273
+ return null;
274
+ }
275
+ }
276
+
277
+ // BPTC
278
+ if (p === 36492 || p === 36494 || p === 36495) {
279
+ extension = extensions.get('EXT_texture_compression_bptc');
280
+ if (extension !== null) {
281
+ return p;
282
+ } else {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ if (p === 34042) return gl.UNSIGNED_INT_24_8;
288
+
289
+ // Internal formats for float/half-float textures
290
+ if (isWebGL2) {
291
+ if (p === 6407) return gl.RGB;
292
+ if (p === 6408) return gl.RGBA;
293
+ }
294
+
295
+ return (gl[p] !== undefined) ? gl[p] : null;
296
+ }
297
+
298
+ return {
299
+ convert: convert
300
+ };
301
+ }
302
+
303
+ // Constants for texture element counts
304
+ const COVARIANCES_ELEMENTS_PER_SPLAT = 6;
305
+ const CENTER_COLORS_ELEMENTS_PER_SPLAT = 4;
306
+
307
+ const COVARIANCES_ELEMENTS_PER_TEXEL_STORED = 4;
308
+ const COVARIANCES_ELEMENTS_PER_TEXEL_ALLOCATED = 4;
309
+ const COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_STORED = 6;
310
+ const COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_ALLOCATED = 8;
311
+ const SCALES_ROTATIONS_ELEMENTS_PER_TEXEL = 4;
312
+ const CENTER_COLORS_ELEMENTS_PER_TEXEL = 4;
313
+ const SCENE_INDEXES_ELEMENTS_PER_TEXEL = 1;
314
+
315
+ const SCENE_FADEIN_RATE_FAST = 0.012;
316
+ const SCENE_FADEIN_RATE_GRADUAL = 0.003;
317
+
318
+ const VISIBLE_REGION_EXPANSION_DELTA = 1;
319
+
320
+ const MAX_TEXTURE_TEXELS = 16777216;
321
+
322
+ export class SplatMesh extends Mesh {
323
+
324
+ constructor(splatRenderMode = SplatRenderMode.ThreeD, dynamicMode = false, enableOptionalEffects = false,
325
+ halfPrecisionCovariancesOnGPU = false, devicePixelRatio = 1, enableDistancesComputationOnGPU = true,
326
+ integerBasedDistancesComputation = false, antialiased = false, maxScreenSpaceSplatSize = 1024, logLevel = LogLevel.None,
327
+ sphericalHarmonicsDegree = 0, sceneFadeInRateMultiplier = 1.0, kernel2DSize = 0.3) {
328
+ super(dummyGeometry, dummyMaterial);
329
+
330
+ // Reference to a Three.js renderer
331
+ this.renderer = undefined;
332
+
333
+ // Determine how the splats are rendered
334
+ this.splatRenderMode = splatRenderMode;
335
+
336
+ // When 'dynamicMode' is true, scenes are assumed to be non-static. Dynamic scenes are handled differently
337
+ // and certain optimizations cannot be made for them. Additionally, by default, all splat data retrieved from
338
+ // this splat mesh will not have their scene transform applied to them if the splat mesh is dynamic. That
339
+ // can be overriden via parameters to the individual functions that are used to retrieve splat data.
340
+ this.dynamicMode = dynamicMode;
341
+
342
+ // When true, allows for usage of extra properties and attributes during rendering for effects such as opacity adjustment.
343
+ // Default is false for performance reasons. These properties are separate from transform properties (scale, rotation, position)
344
+ // that are enabled by the 'dynamicScene' parameter.
345
+ this.enableOptionalEffects = enableOptionalEffects;
346
+
347
+ // Use 16-bit floating point values when storing splat covariance data in textures, instead of 32-bit
348
+ this.halfPrecisionCovariancesOnGPU = halfPrecisionCovariancesOnGPU;
349
+
350
+ // Ratio of the resolution in physical pixels to the resolution in CSS pixels for the current display device
351
+ this.devicePixelRatio = devicePixelRatio;
352
+
353
+ // Use a transform feedback to calculate splat distances from the camera
354
+ this.enableDistancesComputationOnGPU = enableDistancesComputationOnGPU;
355
+
356
+ // Use a faster integer-based approach for calculating splat distances from the camera
357
+ this.integerBasedDistancesComputation = integerBasedDistancesComputation;
358
+
359
+ // When true, will perform additional steps during rendering to address artifacts caused by the rendering of gaussians at a
360
+ // substantially different resolution than that at which they were rendered during training. This will only work correctly
361
+ // for models that were trained using a process that utilizes this compensation calculation. For more details:
362
+ // https://github.com/nerfstudio-project/gsplat/pull/117
363
+ // https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
364
+ this.antialiased = antialiased;
365
+
366
+ // The size of the 2D kernel used for splat rendering
367
+ // This will adjust the 2D kernel size after the projection
368
+ this.kernel2DSize = kernel2DSize;
369
+
370
+ // Specify the maximum clip space splat size, can help deal with large splats that get too unwieldy
371
+ this.maxScreenSpaceSplatSize = maxScreenSpaceSplatSize;
372
+
373
+ // The verbosity of console logging
374
+ this.logLevel = logLevel;
375
+
376
+ // Degree 0 means no spherical harmonics
377
+ this.sphericalHarmonicsDegree = sphericalHarmonicsDegree;
378
+ this.minSphericalHarmonicsDegree = 0;
379
+
380
+ this.sceneFadeInRateMultiplier = sceneFadeInRateMultiplier;
381
+
382
+ // The individual splat scenes stored in this splat mesh, each containing their own transform
383
+ this.scenes = [];
384
+
385
+ // Special octree tailored to SplatMesh instances
386
+ this.splatTree = null;
387
+ this.baseSplatTree = null;
388
+
389
+ // Cache textures and the intermediate data used to populate them
390
+ this.splatDataTextures = {};
391
+ this.flameModel = null;
392
+ this.expressionBSNum = 0;
393
+ this.bsWeight = [];
394
+
395
+ this.bonesMatrix = null;
396
+ this.bonesNum = null;
397
+ this.bonesWeight = null;
398
+ this.gaussianSplatCount = null;
399
+ this.useFlameModel = true;
400
+
401
+ this.morphTargetDictionary = null;
402
+ this.distancesTransformFeedback = {
403
+ 'id': null,
404
+ 'vertexShader': null,
405
+ 'fragmentShader': null,
406
+ 'program': null,
407
+ 'centersBuffer': null,
408
+ 'sceneIndexesBuffer': null,
409
+ 'outDistancesBuffer': null,
410
+ 'centersLoc': -1,
411
+ 'modelViewProjLoc': -1,
412
+ 'sceneIndexesLoc': -1,
413
+ 'transformsLocs': []
414
+ };
415
+
416
+ this.globalSplatIndexToLocalSplatIndexMap = [];
417
+ this.globalSplatIndexToSceneIndexMap = [];
418
+
419
+ this.lastBuildSplatCount = 0;
420
+ this.lastBuildScenes = [];
421
+ this.lastBuildMaxSplatCount = 0;
422
+ this.lastBuildSceneCount = 0;
423
+ this.firstRenderTime = -1;
424
+ this.finalBuild = false;
425
+
426
+ this.webGLUtils = null;
427
+
428
+ this.boundingBox = new Box3();
429
+ this.calculatedSceneCenter = new Vector3();
430
+ this.maxSplatDistanceFromSceneCenter = 0;
431
+ this.visibleRegionBufferRadius = 0;
432
+ this.visibleRegionRadius = 0;
433
+ this.visibleRegionFadeStartRadius = 0;
434
+ this.visibleRegionChanging = false;
435
+
436
+ this.splatScale = 1.0;
437
+ this.pointCloudModeEnabled = false;
438
+
439
+ this.disposed = false;
440
+ this.lastRenderer = null;
441
+ this.visible = false;
442
+ }
443
+
444
+ /**
445
+ * Build a container for each scene managed by this splat mesh based on an instance of SplatBuffer, along with optional
446
+ * transform data (position, scale, rotation) passed to the splat mesh during the build process.
447
+ * @param {Array<THREE.Matrix4>} splatBuffers SplatBuffer instances containing splats for each scene
448
+ * @param {Array<object>} sceneOptions Array of options objects: {
449
+ *
450
+ * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
451
+ *
452
+ * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
453
+ *
454
+ * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
455
+ * }
456
+ * @return {Array<THREE.Matrix4>}
457
+ */
458
+ static buildScenes(parentObject, splatBuffers, sceneOptions) {
459
+ const scenes = [];
460
+ scenes.length = splatBuffers.length;
461
+ for (let i = 0; i < splatBuffers.length; i++) {
462
+ const splatBuffer = splatBuffers[i];
463
+ const options = sceneOptions[i] || {};
464
+ let positionArray = options['position'] || [0, 0, 0];
465
+ let rotationArray = options['rotation'] || [0, 0, 0, 1];
466
+ let scaleArray = options['scale'] || [1, 1, 1];
467
+ const position = new Vector3().fromArray(positionArray);
468
+ const rotation = new Quaternion().fromArray(rotationArray);
469
+ const scale = new Vector3().fromArray(scaleArray);
470
+ const scene = SplatMesh.createScene(splatBuffer, position, rotation, scale,
471
+ options.splatAlphaRemovalThreshold || 1, options.opacity, options.visible);
472
+ parentObject.add(scene);
473
+ scenes[i] = scene;
474
+ }
475
+ return scenes;
476
+ }
477
+
478
+
479
+
480
+ static createScene(splatBuffer, position, rotation, scale, minimumAlpha, opacity = 1.0, visible = true) {
481
+ return new SplatScene(splatBuffer, position, rotation, scale, minimumAlpha, opacity, visible);
482
+ }
483
+
484
+ /**
485
+ * Build data structures that map global splat indexes (based on a unified index across all splat buffers) to
486
+ * local data within a single scene.
487
+ * @param {Array<SplatBuffer>} splatBuffers Instances of SplatBuffer off which to build the maps
488
+ * @return {object}
489
+ */
490
+ static buildSplatIndexMaps(splatBuffers) {
491
+ const localSplatIndexMap = [];
492
+ const sceneIndexMap = [];
493
+ let totalSplatCount = 0;
494
+ for (let s = 0; s < splatBuffers.length; s++) {
495
+ const splatBuffer = splatBuffers[s];
496
+ const maxSplatCount = splatBuffer.getMaxSplatCount();
497
+ for (let i = 0; i < maxSplatCount; i++) {
498
+ localSplatIndexMap[totalSplatCount] = i;
499
+ sceneIndexMap[totalSplatCount] = s;
500
+ totalSplatCount++;
501
+ }
502
+ }
503
+ return {
504
+ localSplatIndexMap,
505
+ sceneIndexMap
506
+ };
507
+ }
508
+
509
+ /**
510
+ * Build an instance of SplatTree (a specialized octree) for the given splat mesh.
511
+ * @param {Array<number>} minAlphas Array of minimum splat slphas for each scene
512
+ * @param {function} onSplatTreeIndexesUpload Function to be called when the upload of splat centers to the splat tree
513
+ * builder worker starts and finishes.
514
+ * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from
515
+ * the format produced by the splat tree builder worker starts and ends.
516
+ * @return {SplatTree}
517
+ */
518
+ buildSplatTree = (minAlphas = [], onSplatTreeIndexesUpload, onSplatTreeConstruction) => {
519
+ return new Promise((resolve) => {
520
+ this.disposeSplatTree();
521
+ // TODO: expose SplatTree constructor parameters (maximumDepth and maxCentersPerNode) so that they can
522
+ // be configured on a per-scene basis
523
+ this.baseSplatTree = new SplatTree(8, 1000);
524
+ const buildStartTime = performance.now();
525
+ const splatColor = new Vector4();
526
+ this.baseSplatTree.processSplatMesh(this, (splatIndex) => {
527
+ this.getSplatColor(splatIndex, splatColor);
528
+ const sceneIndex = this.getSceneIndexForSplat(splatIndex);
529
+ const minAlpha = minAlphas[sceneIndex] || 1;
530
+ return splatColor.w >= minAlpha;
531
+ }, onSplatTreeIndexesUpload, onSplatTreeConstruction)
532
+ .then(() => {
533
+ const buildTime = performance.now() - buildStartTime;
534
+ if (this.logLevel >= LogLevel.Info) console.log('SplatTree build: ' + buildTime + ' ms');
535
+ if (this.disposed) {
536
+ resolve();
537
+ } else {
538
+
539
+ this.splatTree = this.baseSplatTree;
540
+ this.baseSplatTree = null;
541
+
542
+ let leavesWithVertices = 0;
543
+ let avgSplatCount = 0;
544
+ let maxSplatCount = 0;
545
+ let nodeCount = 0;
546
+
547
+ this.splatTree.visitLeaves((node) => {
548
+ const nodeSplatCount = node.data.indexes.length;
549
+ if (nodeSplatCount > 0) {
550
+ avgSplatCount += nodeSplatCount;
551
+ maxSplatCount = Math.max(maxSplatCount, nodeSplatCount);
552
+ nodeCount++;
553
+ leavesWithVertices++;
554
+ }
555
+ });
556
+ if (this.logLevel >= LogLevel.Info) {
557
+ console.log(`SplatTree leaves: ${this.splatTree.countLeaves()}`);
558
+ console.log(`SplatTree leaves with splats:${leavesWithVertices}`);
559
+ avgSplatCount = avgSplatCount / nodeCount;
560
+ console.log(`Avg splat count per node: ${avgSplatCount}`);
561
+ console.log(`Total splat count: ${this.getSplatCount()}`);
562
+ }
563
+ resolve();
564
+ }
565
+ });
566
+ });
567
+ };
568
+
569
+ /**
570
+ * Construct this instance of SplatMesh.
571
+ * @param {Array<SplatBuffer>} splatBuffers The base splat data, instances of SplatBuffer
572
+ * @param {Array<object>} sceneOptions Dynamic options for each scene {
573
+ *
574
+ * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
575
+ * value (valid range: 0 - 255), defaults to 1
576
+ *
577
+ * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
578
+ *
579
+ * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
580
+ *
581
+ * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
582
+ *
583
+ * }
584
+ * @param {boolean} keepSceneTransforms For a scene that already exists and is being overwritten, this flag
585
+ * says to keep the transform from the existing scene.
586
+ * @param {boolean} finalBuild Will the splat mesh be in its final state after this build?
587
+ * @param {function} onSplatTreeIndexesUpload Function to be called when the upload of splat centers to the splat tree
588
+ * builder worker starts and finishes.
589
+ * @param {function} onSplatTreeConstruction Function to be called when the conversion of the local splat tree from
590
+ * the format produced by the splat tree builder worker starts and ends.
591
+ * @return {object} Object containing info about the splats that are updated
592
+ */
593
+ build(splatBuffers, sceneOptions, keepSceneTransforms = true, finalBuild = false,
594
+ onSplatTreeIndexesUpload, onSplatTreeConstruction, preserveVisibleRegion = true) {
595
+
596
+ this.sceneOptions = sceneOptions;
597
+ this.finalBuild = finalBuild;
598
+
599
+ const maxSplatCount = SplatMesh.getTotalMaxSplatCountForSplatBuffers(splatBuffers);
600
+
601
+ const newScenes = SplatMesh.buildScenes(this, splatBuffers, sceneOptions);
602
+ if (keepSceneTransforms) {
603
+ for (let i = 0; i < this.scenes.length && i < newScenes.length; i++) {
604
+ const newScene = newScenes[i];
605
+ const existingScene = this.getScene(i);
606
+ newScene.copyTransformData(existingScene);
607
+ }
608
+ }
609
+ this.scenes = newScenes;
610
+
611
+ let minSphericalHarmonicsDegree = 3;
612
+ for (let splatBuffer of splatBuffers) {
613
+ const splatBufferSphericalHarmonicsDegree = splatBuffer.getMinSphericalHarmonicsDegree();
614
+ if (splatBufferSphericalHarmonicsDegree < minSphericalHarmonicsDegree) {
615
+ minSphericalHarmonicsDegree = splatBufferSphericalHarmonicsDegree;
616
+ }
617
+ }
618
+ this.minSphericalHarmonicsDegree = Math.min(minSphericalHarmonicsDegree, this.sphericalHarmonicsDegree);
619
+
620
+ let splatBuffersChanged = false;
621
+ if (splatBuffers.length !== this.lastBuildScenes.length) {
622
+ splatBuffersChanged = true;
623
+ } else {
624
+ for (let i = 0; i < splatBuffers.length; i++) {
625
+ const splatBuffer = splatBuffers[i];
626
+ if (splatBuffer !== this.lastBuildScenes[i].splatBuffer) {
627
+ splatBuffersChanged = true;
628
+ break;
629
+ }
630
+ }
631
+ }
632
+
633
+ let isUpdateBuild = true;
634
+ if (this.scenes.length !== 1 ||
635
+ this.lastBuildSceneCount !== this.scenes.length ||
636
+ this.lastBuildMaxSplatCount !== maxSplatCount ||
637
+ splatBuffersChanged) {
638
+ isUpdateBuild = false;
639
+ }
640
+
641
+ if (!isUpdateBuild) {
642
+ this.boundingBox = new Box3();
643
+ if (!preserveVisibleRegion) {
644
+ this.maxSplatDistanceFromSceneCenter = 0;
645
+ this.visibleRegionBufferRadius = 0;
646
+ this.visibleRegionRadius = 0;
647
+ this.visibleRegionFadeStartRadius = 0;
648
+ this.firstRenderTime = -1;
649
+ }
650
+ this.lastBuildScenes = [];
651
+ this.lastBuildSplatCount = 0;
652
+ this.lastBuildMaxSplatCount = 0;
653
+ this.disposeMeshData();
654
+ this.geometry = SplatGeometry.build(maxSplatCount);
655
+ if (this.splatRenderMode === SplatRenderMode.ThreeD) {
656
+ this.material = SplatMaterial3D.build(this.dynamicMode, this.enableOptionalEffects, this.antialiased,
657
+ this.maxScreenSpaceSplatSize, this.splatScale, this.pointCloudModeEnabled,
658
+ this.minSphericalHarmonicsDegree, this.kernel2DSize, this.useFlameModel);
659
+ } else {
660
+ this.material = SplatMaterial2D.build(this.dynamicMode, this.enableOptionalEffects,
661
+ this.splatScale, this.pointCloudModeEnabled, this.minSphericalHarmonicsDegree);
662
+ }
663
+
664
+ const indexMaps = SplatMesh.buildSplatIndexMaps(splatBuffers);
665
+ this.globalSplatIndexToLocalSplatIndexMap = indexMaps.localSplatIndexMap;
666
+ this.globalSplatIndexToSceneIndexMap = indexMaps.sceneIndexMap;
667
+ }
668
+
669
+ const splatBufferSplatCount = this.getSplatCount(true);
670
+ if (this.enableDistancesComputationOnGPU) this.setupDistancesComputationTransformFeedback();
671
+ const dataUpdateResults = this.refreshGPUDataFromSplatBuffers(isUpdateBuild);
672
+
673
+ for (let i = 0; i < this.scenes.length; i++) {
674
+ this.lastBuildScenes[i] = this.scenes[i];
675
+ }
676
+ this.lastBuildSplatCount = splatBufferSplatCount;
677
+ this.lastBuildMaxSplatCount = this.getMaxSplatCount();
678
+ this.lastBuildSceneCount = this.scenes.length;
679
+
680
+ // if (finalBuild && this.scenes.length > 0) {
681
+ // this.buildSplatTree(sceneOptions.map(options => options.splatAlphaRemovalThreshold || 1),
682
+ // onSplatTreeIndexesUpload, onSplatTreeConstruction)
683
+ // .then(() => {
684
+ // if (this.onSplatTreeReadyCallback) this.onSplatTreeReadyCallback(this.splatTree);
685
+ // this.onSplatTreeReadyCallback = null;
686
+ // });
687
+ // }
688
+
689
+ this.visible = (this.scenes.length > 0);
690
+
691
+ return dataUpdateResults;
692
+ }
693
+
694
+ freeIntermediateSplatData() {
695
+
696
+ const deleteTextureData = (texture) => {
697
+ delete texture.source.data;
698
+ delete texture.image;
699
+ texture.onUpdate = null;
700
+ };
701
+
702
+ delete this.splatDataTextures.baseData.covariances;
703
+ delete this.splatDataTextures.baseData.centers;
704
+ delete this.splatDataTextures.baseData.colors;
705
+ delete this.splatDataTextures.baseData.sphericalHarmonics;
706
+
707
+ delete this.splatDataTextures.centerColors.data;
708
+ delete this.splatDataTextures.covariances.data;
709
+ if (this.splatDataTextures.sphericalHarmonics) {
710
+ delete this.splatDataTextures.sphericalHarmonics.data;
711
+ }
712
+ if (this.splatDataTextures.sceneIndexes) {
713
+ delete this.splatDataTextures.sceneIndexes.data;
714
+ }
715
+
716
+ this.splatDataTextures.centerColors.texture.needsUpdate = true;
717
+ this.splatDataTextures.centerColors.texture.onUpdate = () => {
718
+ deleteTextureData(this.splatDataTextures.centerColors.texture);
719
+ };
720
+
721
+ this.splatDataTextures.flameModelPosTexture.texture.needsUpdate = true;
722
+ this.splatDataTextures.flameModelPosTexture.texture.onUpdate = () => {
723
+ deleteTextureData(this.splatDataTextures.flameModelPosTexture.texture);
724
+ };
725
+
726
+ this.splatDataTextures.covariances.texture.needsUpdate = true;
727
+ this.splatDataTextures.covariances.texture.onUpdate = () => {
728
+ deleteTextureData(this.splatDataTextures.covariances.texture);
729
+ };
730
+
731
+ if (this.splatDataTextures.sphericalHarmonics) {
732
+ if (this.splatDataTextures.sphericalHarmonics.texture) {
733
+ this.splatDataTextures.sphericalHarmonics.texture.needsUpdate = true;
734
+ this.splatDataTextures.sphericalHarmonics.texture.onUpdate = () => {
735
+ deleteTextureData(this.splatDataTextures.sphericalHarmonics.texture);
736
+ };
737
+ } else {
738
+ this.splatDataTextures.sphericalHarmonics.textures.forEach((texture) => {
739
+ texture.needsUpdate = true;
740
+ texture.onUpdate = () => {
741
+ deleteTextureData(texture);
742
+ };
743
+ });
744
+ }
745
+ }
746
+ if (this.splatDataTextures.sceneIndexes) {
747
+ this.splatDataTextures.sceneIndexes.texture.needsUpdate = true;
748
+ this.splatDataTextures.sceneIndexes.texture.onUpdate = () => {
749
+ deleteTextureData(this.splatDataTextures.sceneIndexes.texture);
750
+ };
751
+ }
752
+ }
753
+ /**
754
+ * Dispose all resources held by the splat mesh
755
+ */
756
+ dispose() {
757
+ this.disposeMeshData();
758
+ this.disposeTextures();
759
+ this.disposeSplatTree();
760
+ if (this.enableDistancesComputationOnGPU) {
761
+ if (this.computeDistancesOnGPUSyncTimeout) {
762
+ clearTimeout(this.computeDistancesOnGPUSyncTimeout);
763
+ this.computeDistancesOnGPUSyncTimeout = null;
764
+ }
765
+ this.disposeDistancesComputationGPUResources();
766
+ }
767
+ this.scenes = [];
768
+ this.distancesTransformFeedback = {
769
+ 'id': null,
770
+ 'vertexShader': null,
771
+ 'fragmentShader': null,
772
+ 'program': null,
773
+ 'centersBuffer': null,
774
+ 'sceneIndexesBuffer': null,
775
+ 'outDistancesBuffer': null,
776
+ 'centersLoc': -1,
777
+ 'modelViewProjLoc': -1,
778
+ 'sceneIndexesLoc': -1,
779
+ 'transformsLocs': []
780
+ };
781
+ this.renderer = null;
782
+
783
+ this.globalSplatIndexToLocalSplatIndexMap = [];
784
+ this.globalSplatIndexToSceneIndexMap = [];
785
+
786
+ this.lastBuildSplatCount = 0;
787
+ this.lastBuildScenes = [];
788
+ this.lastBuildMaxSplatCount = 0;
789
+ this.lastBuildSceneCount = 0;
790
+ this.firstRenderTime = -1;
791
+ this.finalBuild = false;
792
+
793
+ this.webGLUtils = null;
794
+
795
+ this.boundingBox = new Box3();
796
+ this.calculatedSceneCenter = new Vector3();
797
+ this.maxSplatDistanceFromSceneCenter = 0;
798
+ this.visibleRegionBufferRadius = 0;
799
+ this.visibleRegionRadius = 0;
800
+ this.visibleRegionFadeStartRadius = 0;
801
+ this.visibleRegionChanging = false;
802
+
803
+ this.splatScale = 1.0;
804
+ this.pointCloudModeEnabled = false;
805
+
806
+ this.disposed = true;
807
+ this.lastRenderer = null;
808
+ this.visible = false;
809
+ }
810
+
811
+ /**
812
+ * Dispose of only the Three.js mesh resources (geometry, material, and texture)
813
+ */
814
+ disposeMeshData() {
815
+ if (this.geometry && this.geometry !== dummyGeometry) {
816
+ this.geometry.dispose();
817
+ this.geometry = null;
818
+ }
819
+ if (this.material) {
820
+ this.material.dispose();
821
+ this.material = null;
822
+ }
823
+ }
824
+
825
+ disposeTextures() {
826
+ for (let textureKey in this.splatDataTextures) {
827
+ if (Object.hasOwn(this.splatDataTextures, textureKey)) {
828
+ const textureContainer = this.splatDataTextures[textureKey];
829
+ if (textureContainer.texture) {
830
+ textureContainer.texture.dispose();
831
+ textureContainer.texture = null;
832
+ }
833
+ }
834
+ }
835
+ this.splatDataTextures = null;
836
+ }
837
+
838
+ disposeSplatTree() {
839
+ if (this.splatTree) {
840
+ this.splatTree.dispose();
841
+ this.splatTree = null;
842
+ }
843
+ if (this.baseSplatTree) {
844
+ this.baseSplatTree.dispose();
845
+ this.baseSplatTree = null;
846
+ }
847
+ }
848
+
849
+ getSplatTree() {
850
+ return this.splatTree;
851
+ }
852
+
853
+ onSplatTreeReady(callback) {
854
+ this.onSplatTreeReadyCallback = callback;
855
+ }
856
+
857
+ /**
858
+ * Get copies of data that are necessary for splat distance computation: splat center positions and splat
859
+ * scene indexes (necessary for applying dynamic scene transformations during distance computation)
860
+ * @param {*} start The index at which to start copying data
861
+ * @param {*} end The index at which to stop copying data
862
+ * @return {object}
863
+ */
864
+ getDataForDistancesComputation(start, end) {
865
+ const centers = this.integerBasedDistancesComputation ?
866
+ this.getIntegerCenters(start, end, true) :
867
+ this.getFloatCenters(start, end, true);
868
+ const sceneIndexes = this.getSceneIndexes(start, end);
869
+ return {
870
+ centers,
871
+ sceneIndexes
872
+ };
873
+ }
874
+
875
+ /**
876
+ * Refresh data textures and GPU buffers with splat data from the splat buffers belonging to this mesh.
877
+ * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
878
+ * @return {object}
879
+ */
880
+ refreshGPUDataFromSplatBuffers(sinceLastBuildOnly) {
881
+ const splatCount = this.getSplatCount(true);
882
+ this.refreshDataTexturesFromSplatBuffers(sinceLastBuildOnly);
883
+ const updateStart = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
884
+ const { centers, sceneIndexes } = this.getDataForDistancesComputation(updateStart, splatCount - 1);
885
+ if (this.enableDistancesComputationOnGPU) {
886
+ this.refreshGPUBuffersForDistancesComputation(centers, sceneIndexes, sinceLastBuildOnly);
887
+ }
888
+ return {
889
+ 'from': updateStart,
890
+ 'to': splatCount - 1,
891
+ 'count': splatCount - updateStart,
892
+ 'centers': centers,
893
+ 'sceneIndexes': sceneIndexes
894
+ };
895
+ }
896
+
897
+ /**
898
+ * Update the GPU buffers that are used for computing splat distances on the GPU.
899
+ * @param {Array<number>} centers Splat center positions
900
+ * @param {Array<number>} sceneIndexes Indexes of the scene to which each splat belongs
901
+ * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
902
+ */
903
+ refreshGPUBuffersForDistancesComputation(centers, sceneIndexes, sinceLastBuildOnly = false) {
904
+ const offset = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
905
+ this.updateGPUCentersBufferForDistancesComputation(sinceLastBuildOnly, centers, offset);
906
+ this.updateGPUTransformIndexesBufferForDistancesComputation(sinceLastBuildOnly, sceneIndexes, offset);
907
+ }
908
+
909
+ /**
910
+ * Refresh data textures with data from the splat buffers for this mesh.
911
+ * @param {boolean} sinceLastBuildOnly Specify whether or not to only update for splats that have been added since the last build.
912
+ */
913
+ refreshDataTexturesFromSplatBuffers(sinceLastBuildOnly) {
914
+ const splatCount = this.getSplatCount(true);
915
+ const fromSplat = this.lastBuildSplatCount;
916
+ const toSplat = splatCount - 1;
917
+
918
+ if (!sinceLastBuildOnly) {
919
+ this.setupDataTextures();
920
+ this.updateBaseDataFromSplatBuffers();
921
+ } else {
922
+ this.updateBaseDataFromSplatBuffers(fromSplat, toSplat);
923
+ }
924
+
925
+ this.updateDataTexturesFromBaseData(fromSplat, toSplat);
926
+ this.updateVisibleRegion(sinceLastBuildOnly);
927
+ }
928
+
929
+ setupDataTextures() {
930
+ const maxSplatCount = this.getMaxSplatCount();
931
+ const splatCount = this.getSplatCount(true);
932
+
933
+ this.disposeTextures();
934
+
935
+ const computeDataTextureSize = (elementsPerTexel, elementsPerSplat) => {
936
+ const texSize = new Vector2(4096, 1024);
937
+ while (texSize.x * texSize.y * elementsPerTexel < maxSplatCount * elementsPerSplat) texSize.y *= 2;
938
+ return texSize;
939
+ };
940
+
941
+ const getCovariancesElementsPertexelStored = (compressionLevel) => {
942
+ return compressionLevel >= 1 ? COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_STORED : COVARIANCES_ELEMENTS_PER_TEXEL_STORED;
943
+ };
944
+
945
+ const getCovariancesInitialTextureSpecs = (compressionLevel) => {
946
+ const elementsPerTexelStored = getCovariancesElementsPertexelStored(compressionLevel);
947
+ const texSize = computeDataTextureSize(elementsPerTexelStored, 6);
948
+ return {elementsPerTexelStored, texSize};
949
+ };
950
+
951
+ let covarianceCompressionLevel = this.getTargetCovarianceCompressionLevel();
952
+ const scaleRotationCompressionLevel = 0;
953
+ const shCompressionLevel = this.getTargetSphericalHarmonicsCompressionLevel();
954
+
955
+ let covariances;
956
+ let scales;
957
+ let rotations;
958
+ if (this.splatRenderMode === SplatRenderMode.ThreeD) {
959
+ const initialCovTexSpecs = getCovariancesInitialTextureSpecs(covarianceCompressionLevel);
960
+ if (initialCovTexSpecs.texSize.x * initialCovTexSpecs.texSize.y > MAX_TEXTURE_TEXELS && covarianceCompressionLevel === 0) {
961
+ covarianceCompressionLevel = 1;
962
+ }
963
+ covariances = new Float32Array(maxSplatCount * COVARIANCES_ELEMENTS_PER_SPLAT);
964
+ } else {
965
+ scales = new Float32Array(maxSplatCount * 3);
966
+ rotations = new Float32Array(maxSplatCount * 4);
967
+ }
968
+
969
+ const centers = new Float32Array(maxSplatCount * 3);
970
+ const colors = new Uint8Array(maxSplatCount * 4);
971
+
972
+ let SphericalHarmonicsArrayType = Float32Array;
973
+ if (shCompressionLevel === 1) SphericalHarmonicsArrayType = Uint16Array;
974
+ else if (shCompressionLevel === 2) SphericalHarmonicsArrayType = Uint8Array;
975
+ const shComponentCount = getSphericalHarmonicsComponentCountForDegree(this.minSphericalHarmonicsDegree);
976
+ const shData = this.minSphericalHarmonicsDegree ? new SphericalHarmonicsArrayType(maxSplatCount * shComponentCount) : undefined;
977
+
978
+ // set up centers/colors data texture
979
+ const centersColsTexSize = computeDataTextureSize(CENTER_COLORS_ELEMENTS_PER_TEXEL, 4);
980
+ const paddedCentersCols = new Uint32Array(centersColsTexSize.x * centersColsTexSize.y * CENTER_COLORS_ELEMENTS_PER_TEXEL);
981
+ SplatMesh.updateCenterColorsPaddedData(0, splatCount - 1, centers, colors, paddedCentersCols);
982
+
983
+ const centersColsTex = new DataTexture(paddedCentersCols, centersColsTexSize.x, centersColsTexSize.y,
984
+ RGBAIntegerFormat, UnsignedIntType);
985
+ centersColsTex.internalFormat = 'RGBA32UI';
986
+ centersColsTex.needsUpdate = true;
987
+ this.material.uniforms.centersColorsTexture.value = centersColsTex;
988
+ this.material.uniforms.centersColorsTextureSize.value.copy(centersColsTexSize);
989
+ this.material.uniformsNeedUpdate = true;
990
+
991
+ this.splatDataTextures = {
992
+ 'baseData': {
993
+ 'covariances': covariances,
994
+ 'scales': scales,
995
+ 'rotations': rotations,
996
+ 'centers': centers,
997
+ 'colors': colors,
998
+ 'sphericalHarmonics': shData
999
+ },
1000
+ 'centerColors': {
1001
+ 'data': paddedCentersCols,
1002
+ 'texture': centersColsTex,
1003
+ 'size': centersColsTexSize
1004
+ }
1005
+ };
1006
+
1007
+ if (this.splatRenderMode === SplatRenderMode.ThreeD) {
1008
+ // set up covariances data texture
1009
+
1010
+ const covTexSpecs = getCovariancesInitialTextureSpecs(covarianceCompressionLevel);
1011
+ const covariancesElementsPerTexelStored = covTexSpecs.elementsPerTexelStored;
1012
+ const covTexSize = covTexSpecs.texSize;
1013
+
1014
+ let CovariancesDataType = covarianceCompressionLevel >= 1 ? Uint32Array : Float32Array;
1015
+ const covariancesElementsPerTexelAllocated = covarianceCompressionLevel >= 1 ?
1016
+ COVARIANCES_ELEMENTS_PER_TEXEL_COMPRESSED_ALLOCATED :
1017
+ COVARIANCES_ELEMENTS_PER_TEXEL_ALLOCATED;
1018
+ const covariancesTextureData = new CovariancesDataType(covTexSize.x * covTexSize.y * covariancesElementsPerTexelAllocated);
1019
+
1020
+ if (covarianceCompressionLevel === 0) {
1021
+ covariancesTextureData.set(covariances);
1022
+ } else {
1023
+ SplatMesh.updatePaddedCompressedCovariancesTextureData(covariances, covariancesTextureData, 0, 0, covariances.length);
1024
+ }
1025
+
1026
+ let covTex;
1027
+ if (covarianceCompressionLevel >= 1) {
1028
+ covTex = new DataTexture(covariancesTextureData, covTexSize.x, covTexSize.y,
1029
+ RGBAIntegerFormat, UnsignedIntType);
1030
+ covTex.internalFormat = 'RGBA32UI';
1031
+ this.material.uniforms.covariancesTextureHalfFloat.value = covTex;
1032
+ } else {
1033
+ covTex = new DataTexture(covariancesTextureData, covTexSize.x, covTexSize.y, RGBAFormat, FloatType);
1034
+ this.material.uniforms.covariancesTexture.value = covTex;
1035
+
1036
+ // For some reason a usampler2D needs to have a valid texture attached or WebGL complains
1037
+ const dummyTex = new DataTexture(new Uint32Array(32), 2, 2, RGBAIntegerFormat, UnsignedIntType);
1038
+ dummyTex.internalFormat = 'RGBA32UI';
1039
+ this.material.uniforms.covariancesTextureHalfFloat.value = dummyTex;
1040
+ dummyTex.needsUpdate = true;
1041
+ }
1042
+ covTex.needsUpdate = true;
1043
+
1044
+ this.material.uniforms.covariancesAreHalfFloat.value = (covarianceCompressionLevel >= 1) ? 1 : 0;
1045
+ this.material.uniforms.covariancesTextureSize.value.copy(covTexSize);
1046
+
1047
+ this.splatDataTextures['covariances'] = {
1048
+ 'data': covariancesTextureData,
1049
+ 'texture': covTex,
1050
+ 'size': covTexSize,
1051
+ 'compressionLevel': covarianceCompressionLevel,
1052
+ 'elementsPerTexelStored': covariancesElementsPerTexelStored,
1053
+ 'elementsPerTexelAllocated': covariancesElementsPerTexelAllocated
1054
+ };
1055
+ } else {
1056
+ // set up scale & rotations data texture
1057
+ const elementsPerSplat = 6;
1058
+ const scaleRotationsTexSize = computeDataTextureSize(SCALES_ROTATIONS_ELEMENTS_PER_TEXEL, elementsPerSplat);
1059
+ let ScaleRotationsDataType = scaleRotationCompressionLevel >= 1 ? Uint16Array : Float32Array;
1060
+ let scaleRotationsTextureType = scaleRotationCompressionLevel >= 1 ? HalfFloatType : FloatType;
1061
+ const paddedScaleRotations = new ScaleRotationsDataType(scaleRotationsTexSize.x * scaleRotationsTexSize.y *
1062
+ SCALES_ROTATIONS_ELEMENTS_PER_TEXEL);
1063
+
1064
+ SplatMesh.updateScaleRotationsPaddedData(0, splatCount - 1, scales, rotations, paddedScaleRotations);
1065
+
1066
+ const scaleRotationsTex = new DataTexture(paddedScaleRotations, scaleRotationsTexSize.x, scaleRotationsTexSize.y,
1067
+ RGBAFormat, scaleRotationsTextureType);
1068
+ scaleRotationsTex.needsUpdate = true;
1069
+ this.material.uniforms.scaleRotationsTexture.value = scaleRotationsTex;
1070
+ this.material.uniforms.scaleRotationsTextureSize.value.copy(scaleRotationsTexSize);
1071
+
1072
+ this.splatDataTextures['scaleRotations'] = {
1073
+ 'data': paddedScaleRotations,
1074
+ 'texture': scaleRotationsTex,
1075
+ 'size': scaleRotationsTexSize,
1076
+ 'compressionLevel': scaleRotationCompressionLevel
1077
+ };
1078
+ }
1079
+
1080
+ if (shData) {
1081
+ const shTextureType = shCompressionLevel === 2 ? UnsignedByteType : HalfFloatType;
1082
+
1083
+ let paddedSHComponentCount = shComponentCount;
1084
+ if (paddedSHComponentCount % 2 !== 0) paddedSHComponentCount++;
1085
+ const shElementsPerTexel = this.minSphericalHarmonicsDegree === 2 ? 4 : 2;
1086
+ const texelFormat = shElementsPerTexel === 4 ? RGBAFormat : RGFormat;
1087
+ let shTexSize = computeDataTextureSize(shElementsPerTexel, paddedSHComponentCount);
1088
+
1089
+ // Use one texture for all spherical harmonics data
1090
+ if (shTexSize.x * shTexSize.y <= MAX_TEXTURE_TEXELS) {
1091
+ const paddedSHArraySize = shTexSize.x * shTexSize.y * shElementsPerTexel;
1092
+ const paddedSHArray = new SphericalHarmonicsArrayType(paddedSHArraySize);
1093
+ for (let c = 0; c < splatCount; c++) {
1094
+ const srcBase = shComponentCount * c;
1095
+ const destBase = paddedSHComponentCount * c;
1096
+ for (let i = 0; i < shComponentCount; i++) {
1097
+ paddedSHArray[destBase + i] = shData[srcBase + i];
1098
+ }
1099
+ }
1100
+
1101
+ const shTexture = new DataTexture(paddedSHArray, shTexSize.x, shTexSize.y, texelFormat, shTextureType);
1102
+ shTexture.needsUpdate = true;
1103
+ this.material.uniforms.sphericalHarmonicsTexture.value = shTexture;
1104
+ this.splatDataTextures['sphericalHarmonics'] = {
1105
+ 'componentCount': shComponentCount,
1106
+ 'paddedComponentCount': paddedSHComponentCount,
1107
+ 'data': paddedSHArray,
1108
+ 'textureCount': 1,
1109
+ 'texture': shTexture,
1110
+ 'size': shTexSize,
1111
+ 'compressionLevel': shCompressionLevel,
1112
+ 'elementsPerTexel': shElementsPerTexel
1113
+ };
1114
+ // Use three textures for spherical harmonics data, one per color channel
1115
+ } else {
1116
+ const shComponentCountPerChannel = shComponentCount / 3;
1117
+ paddedSHComponentCount = shComponentCountPerChannel;
1118
+ if (paddedSHComponentCount % 2 !== 0) paddedSHComponentCount++;
1119
+ shTexSize = computeDataTextureSize(shElementsPerTexel, paddedSHComponentCount);
1120
+
1121
+ const paddedSHArraySize = shTexSize.x * shTexSize.y * shElementsPerTexel;
1122
+ const textureUniforms = [this.material.uniforms.sphericalHarmonicsTextureR,
1123
+ this.material.uniforms.sphericalHarmonicsTextureG,
1124
+ this.material.uniforms.sphericalHarmonicsTextureB];
1125
+ const paddedSHArrays = [];
1126
+ const shTextures = [];
1127
+ for (let t = 0; t < 3; t++) {
1128
+ const paddedSHArray = new SphericalHarmonicsArrayType(paddedSHArraySize);
1129
+ paddedSHArrays.push(paddedSHArray);
1130
+ for (let c = 0; c < splatCount; c++) {
1131
+ const srcBase = shComponentCount * c;
1132
+ const destBase = paddedSHComponentCount * c;
1133
+ if (shComponentCountPerChannel >= 3) {
1134
+ for (let i = 0; i < 3; i++) paddedSHArray[destBase + i] = shData[srcBase + t * 3 + i];
1135
+ if (shComponentCountPerChannel >= 8) {
1136
+ for (let i = 0; i < 5; i++) paddedSHArray[destBase + 3 + i] = shData[srcBase + 9 + t * 5 + i];
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ const shTexture = new DataTexture(paddedSHArray, shTexSize.x, shTexSize.y, texelFormat, shTextureType);
1142
+ shTextures.push(shTexture);
1143
+ shTexture.needsUpdate = true;
1144
+ textureUniforms[t].value = shTexture;
1145
+ }
1146
+
1147
+ this.material.uniforms.sphericalHarmonicsMultiTextureMode.value = 1;
1148
+ this.splatDataTextures['sphericalHarmonics'] = {
1149
+ 'componentCount': shComponentCount,
1150
+ 'componentCountPerChannel': shComponentCountPerChannel,
1151
+ 'paddedComponentCount': paddedSHComponentCount,
1152
+ 'data': paddedSHArrays,
1153
+ 'textureCount': 3,
1154
+ 'textures': shTextures,
1155
+ 'size': shTexSize,
1156
+ 'compressionLevel': shCompressionLevel,
1157
+ 'elementsPerTexel': shElementsPerTexel
1158
+ };
1159
+ }
1160
+
1161
+ this.material.uniforms.sphericalHarmonicsTextureSize.value.copy(shTexSize);
1162
+ this.material.uniforms.sphericalHarmonics8BitMode.value = shCompressionLevel === 2 ? 1 : 0;
1163
+ for (let s = 0; s < this.scenes.length; s++) {
1164
+ const splatBuffer = this.scenes[s].splatBuffer;
1165
+ this.material.uniforms.sphericalHarmonics8BitCompressionRangeMin.value[s] =
1166
+ splatBuffer.minSphericalHarmonicsCoeff;
1167
+ this.material.uniforms.sphericalHarmonics8BitCompressionRangeMax.value[s] =
1168
+ splatBuffer.maxSphericalHarmonicsCoeff;
1169
+ }
1170
+ this.material.uniformsNeedUpdate = true;
1171
+ }
1172
+
1173
+ const sceneIndexesTexSize = computeDataTextureSize(SCENE_INDEXES_ELEMENTS_PER_TEXEL, 4);
1174
+ const paddedTransformIndexes = new Uint32Array(sceneIndexesTexSize.x *
1175
+ sceneIndexesTexSize.y * SCENE_INDEXES_ELEMENTS_PER_TEXEL);
1176
+ for (let c = 0; c < splatCount; c++) paddedTransformIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
1177
+ const sceneIndexesTexture = new DataTexture(paddedTransformIndexes, sceneIndexesTexSize.x, sceneIndexesTexSize.y,
1178
+ RedIntegerFormat, UnsignedIntType);
1179
+ sceneIndexesTexture.internalFormat = 'R32UI';
1180
+ sceneIndexesTexture.needsUpdate = true;
1181
+ this.material.uniforms.sceneIndexesTexture.value = sceneIndexesTexture;
1182
+ this.material.uniforms.sceneIndexesTextureSize.value.copy(sceneIndexesTexSize);
1183
+ this.material.uniformsNeedUpdate = true;
1184
+ this.splatDataTextures['sceneIndexes'] = {
1185
+ 'data': paddedTransformIndexes,
1186
+ 'texture': sceneIndexesTexture,
1187
+ 'size': sceneIndexesTexSize
1188
+ };
1189
+ this.material.uniforms.sceneCount.value = this.scenes.length;
1190
+
1191
+ this.expressionBSNum = this.flameModel.geometry.morphAttributes.position.length;
1192
+ this.material.uniforms.bsCount.value = this.expressionBSNum;
1193
+
1194
+ this.flameModel.skeleton.bones.forEach((bone, index) => {
1195
+ if (bone.name == 'head')
1196
+ this.material.uniforms.headBoneIndex.value = index;
1197
+ });
1198
+
1199
+ this.buildModelTexture(this.flameModel);
1200
+ this.buildBoneMatrixTexture();
1201
+ if(this.useFlameModel) {
1202
+ this.buildBoneWeightTexture(this.flameModel);
1203
+ }
1204
+ }
1205
+
1206
+ buildBoneMatrixTexture() {
1207
+ if (!this.bsWeight)
1208
+ return
1209
+ //this.bonesNum + this.expressionBSNum / 4 = 30, so 32
1210
+ const boneTextureSize = new Vector2(4, 32);
1211
+ let boneMatrixTextureData = new Float32Array(this.bonesMatrix);
1212
+ let boneMatrixTextureDataInt = new Uint32Array(boneTextureSize.x * boneTextureSize.y * 4);
1213
+ this.morphTargetDictionary = this.flameModel.morphTargetDictionary;
1214
+
1215
+ if(this.useFlameModel) {
1216
+ for (let c = 0; c < this.bonesNum * 16; c++) {
1217
+ boneMatrixTextureDataInt[c] = uintEncodedFloat(boneMatrixTextureData[c]);
1218
+ }
1219
+ if (this.flameModel && this.flameModel.skeleton) {
1220
+ this.material.uniforms.boneTexture0.value = this.flameModel.skeleton.boneTexture;
1221
+ this.material.uniforms.bindMatrix.value = this.flameModel.bindMatrix;
1222
+ this.material.uniforms.bindMatrixInverse.value = this.flameModel.bindMatrixInverse;
1223
+ }
1224
+ }
1225
+ for (const key in this.bsWeight) {
1226
+ if (Object.hasOwn(this.bsWeight, key)) {
1227
+ const value = this.bsWeight[key];
1228
+ const idx = this.morphTargetDictionary[key];
1229
+ boneMatrixTextureDataInt[idx + this.bonesNum * 16] = uintEncodedFloat(value);
1230
+ }
1231
+ }
1232
+
1233
+ // for (let c = 0; c < this.bsWeight.length; c++) {
1234
+ // this.morphTargetDictionary
1235
+ // boneMatrixTextureDataInt[c + this.bonesNum * 16] = uintEncodedFloat(this.bsWeight[c]);
1236
+ // }
1237
+
1238
+ const boneMatrixTex = new DataTexture(boneMatrixTextureDataInt, boneTextureSize.x, boneTextureSize.y,
1239
+ RGBAIntegerFormat, UnsignedIntType);
1240
+ boneMatrixTex.internalFormat = 'RGBA32UI';
1241
+ boneMatrixTex.needsUpdate = true;
1242
+ this.material.uniforms.boneTexture.value = boneMatrixTex;
1243
+ this.material.uniforms.boneTextureSize.value.copy(boneTextureSize);
1244
+
1245
+ this.material.uniformsNeedUpdate = true;
1246
+
1247
+ this.splatDataTextures['boneMatrix'] = {
1248
+ 'data': boneMatrixTextureDataInt,
1249
+ 'texture': boneMatrixTex,
1250
+ 'size': boneTextureSize,
1251
+ };
1252
+ this.splatDataTextures.baseData['boneMatrix'] = boneMatrixTextureDataInt;
1253
+ }
1254
+
1255
+ updateBoneMatrixTexture(updateFlameBoneMatrix = false) {
1256
+ if (!this.bsWeight || !this.morphTargetDictionary)
1257
+ return
1258
+
1259
+ if(updateFlameBoneMatrix == true) {
1260
+ let boneMatrixTextureData = new Float32Array(this.bonesMatrix);
1261
+ for (let c = 0; c < this.bonesNum * 16; c++) {
1262
+ this.splatDataTextures.baseData['boneMatrix'][c] = uintEncodedFloat(boneMatrixTextureData[c]);
1263
+ }
1264
+ }
1265
+
1266
+ for (const key in this.bsWeight) {
1267
+ if (Object.hasOwn(this.bsWeight, key)) {
1268
+ const value = this.bsWeight[key];
1269
+ const idx = this.morphTargetDictionary[key];
1270
+ this.splatDataTextures.baseData['boneMatrix'][idx + this.bonesNum * 16] = uintEncodedFloat(value);
1271
+ }
1272
+ }
1273
+
1274
+ // for (let c = 0; c < this.bsWeight.length; c++) {
1275
+ // this.splatDataTextures.baseData['boneMatrix'][c + this.bonesNum * 16] = uintEncodedFloat(this.bsWeight[c]);
1276
+ // }
1277
+ this.splatDataTextures['boneMatrix']['texture'].data = this.splatDataTextures.baseData['boneMatrix'];
1278
+
1279
+ this.splatDataTextures['boneMatrix']['texture'].needsUpdate = true;
1280
+ this.material.uniforms.boneTexture.value = this.splatDataTextures['boneMatrix']['texture'];
1281
+
1282
+ if (this.flameModel.skeleton) {
1283
+ this.material.uniforms.boneTexture0.value = this.flameModel.skeleton.boneTexture;
1284
+ this.material.uniforms.bindMatrix.value = this.flameModel.bindMatrix;
1285
+ this.material.uniforms.bindMatrixInverse.value = this.flameModel.bindMatrixInverse;
1286
+ }
1287
+
1288
+ this.material.uniformsNeedUpdate = true;
1289
+ }
1290
+
1291
+ buildBoneWeightTexture(flameModel) {
1292
+ let shapedMesh = flameModel.geometry.attributes.position.array;
1293
+
1294
+ let pointNum = shapedMesh.length / 3;
1295
+ const boneWeightTextureSize = new Vector2(512, 512);
1296
+ let boneWeightTextureData = new Float32Array(boneWeightTextureSize.x * boneWeightTextureSize.y * 4);
1297
+ let boneWeightTextureDataInt = new Uint32Array(boneWeightTextureSize.x * boneWeightTextureSize.y * 4);
1298
+ for (let i = 0; i < pointNum; i++) {
1299
+ boneWeightTextureData[i * 8 + 0] = this.bonesWeight[i][0];
1300
+ boneWeightTextureData[i * 8 + 1] = this.bonesWeight[i][1];
1301
+ boneWeightTextureData[i * 8 + 2] = this.bonesWeight[i][2];
1302
+ boneWeightTextureData[i * 8 + 3] = this.bonesWeight[i][3];
1303
+ boneWeightTextureData[i * 8 + 4] = this.bonesWeight[i][4];
1304
+
1305
+ boneWeightTextureDataInt[i * 8 + 0] = uintEncodedFloat(this.bonesWeight[i][0]);
1306
+ boneWeightTextureDataInt[i * 8 + 1] = uintEncodedFloat(this.bonesWeight[i][1]);
1307
+ boneWeightTextureDataInt[i * 8 + 2] = uintEncodedFloat(this.bonesWeight[i][2]);
1308
+ boneWeightTextureDataInt[i * 8 + 3] = uintEncodedFloat(this.bonesWeight[i][3]);
1309
+ boneWeightTextureDataInt[i * 8 + 4] = uintEncodedFloat(this.bonesWeight[i][4]);
1310
+
1311
+ }
1312
+ const boneWeightTex = new DataTexture(boneWeightTextureDataInt, boneWeightTextureSize.x, boneWeightTextureSize.y,
1313
+ RGBAIntegerFormat, UnsignedIntType);
1314
+
1315
+ boneWeightTex.internalFormat = 'RGBA32UI';
1316
+ boneWeightTex.needsUpdate = true;
1317
+ this.material.uniforms.boneWeightTexture.value = boneWeightTex;
1318
+ this.material.uniforms.boneWeightTextureSize.value.copy(boneWeightTextureSize);
1319
+ this.material.uniformsNeedUpdate = true;
1320
+
1321
+ this.splatDataTextures['boneWeight'] = {
1322
+ 'data': boneWeightTextureDataInt,
1323
+ 'texture': boneWeightTex,
1324
+ 'size': boneWeightTextureSize,
1325
+ };
1326
+ this.splatDataTextures.baseData['boneWeight'] = boneWeightTextureDataInt;
1327
+ }
1328
+
1329
+
1330
+ buildModelTexture(flameModel) {
1331
+ const flameModelTexSize = new Vector2(4096, 2048);
1332
+
1333
+ var shapedMesh = flameModel.geometry.attributes.position.array;
1334
+ var shapedMeshArray = [];//Array.from(shapedMesh);
1335
+ let pointNum = shapedMesh.length / 3;
1336
+
1337
+ let bsLength = flameModel.geometry.morphAttributes.position.length;
1338
+
1339
+ const morphTargetNames = Object.keys(flameModel.morphTargetDictionary);
1340
+ // if (this.useFlameModel == false) {
1341
+ // morphTargetNames.sort();
1342
+ // }
1343
+ morphTargetNames.forEach((name, newIndex) => {
1344
+ const originalIndex = flameModel.morphTargetDictionary[name];
1345
+ var bsMesh = flameModel.geometry.morphAttributes.position[originalIndex];
1346
+ shapedMeshArray = shapedMeshArray.concat(Array.from(bsMesh.array));
1347
+
1348
+ });
1349
+ shapedMeshArray = shapedMeshArray.concat(Array.from(shapedMesh));
1350
+
1351
+ let flameModelData = new Float32Array(flameModelTexSize.x * flameModelTexSize.y * 4);
1352
+ let flameModelDataInt = new Uint32Array(flameModelTexSize.x * flameModelTexSize.y * 4);
1353
+ for (let c = 0; c < pointNum * (bsLength + 1); c++) {
1354
+ flameModelData[c * 4 + 0] = shapedMeshArray[c * 3 + 0];
1355
+ flameModelData[c * 4 + 1] = shapedMeshArray[c * 3 + 1];
1356
+ flameModelData[c * 4 + 2] = shapedMeshArray[c * 3 + 2];
1357
+
1358
+ flameModelDataInt[c * 4 + 0] = uintEncodedFloat(flameModelData[c * 4 + 0]);
1359
+ flameModelDataInt[c * 4 + 1] = uintEncodedFloat(flameModelData[c * 4 + 1]);
1360
+ flameModelDataInt[c * 4 + 2] = uintEncodedFloat(flameModelData[c * 4 + 2]);
1361
+ }
1362
+
1363
+ const flameModelTex = new DataTexture(flameModelDataInt, flameModelTexSize.x, flameModelTexSize.y,
1364
+ RGBAIntegerFormat, UnsignedIntType);
1365
+ flameModelTex.internalFormat = 'RGBA32UI';
1366
+ flameModelTex.needsUpdate = true;
1367
+ this.material.uniforms.flameModelTexture.value = flameModelTex;
1368
+ this.material.uniforms.flameModelTextureSize.value.copy(flameModelTexSize);
1369
+ this.material.uniformsNeedUpdate = true;
1370
+ this.material.uniforms.gaussianSplatCount.value = this.gaussianSplatCount;
1371
+
1372
+ this.splatDataTextures['flameModel'] = {
1373
+ 'data': flameModelDataInt,
1374
+ 'texture': flameModelTex,
1375
+ 'size': flameModelTexSize,
1376
+ };
1377
+ this.splatDataTextures.baseData['flameModelPos'] = flameModelData;
1378
+ }
1379
+
1380
+ updateTetureAfterBSAndSkeleton(fromSplat, toSplat, useFlameModel = true) {
1381
+ const sceneTransform = new Matrix4();
1382
+
1383
+ this.getSceneTransform(0, sceneTransform);
1384
+ this.getScene(0).splatBuffer.fillSplatCenterArray(this.morphedMesh, this.splatDataTextures.baseData.centers, sceneTransform, fromSplat, toSplat, 0);
1385
+
1386
+ // Update center & color data texture
1387
+ const centerColorsTextureDescriptor = this.splatDataTextures['centerColors'];
1388
+ const paddedCenterColors = centerColorsTextureDescriptor.data;
1389
+ const centerColorsTexture = centerColorsTextureDescriptor.texture;
1390
+ SplatMesh.updateCenterColorsPaddedData(fromSplat, toSplat, this.splatDataTextures.baseData.centers,
1391
+ this.splatDataTextures.baseData.colors, paddedCenterColors);
1392
+ const centerColorsTextureProps = this.renderer ? this.renderer.properties.get(centerColorsTexture) : null;
1393
+ if (!centerColorsTextureProps || !centerColorsTextureProps.__webglTexture) {
1394
+ centerColorsTexture.needsUpdate = true;
1395
+ } else {
1396
+ this.updateDataTexture(paddedCenterColors, centerColorsTextureDescriptor.texture, centerColorsTextureDescriptor.size,
1397
+ centerColorsTextureProps, CENTER_COLORS_ELEMENTS_PER_TEXEL, CENTER_COLORS_ELEMENTS_PER_SPLAT, 4,
1398
+ fromSplat, toSplat);
1399
+ }
1400
+
1401
+ this.updateBoneMatrixTexture(useFlameModel);
1402
+ }
1403
+
1404
+ updateBaseDataFromSplatBuffers(fromSplat, toSplat) {
1405
+ const covarancesTextureDesc = this.splatDataTextures['covariances'];
1406
+ const covarianceCompressionLevel = covarancesTextureDesc ? covarancesTextureDesc.compressionLevel : undefined;
1407
+ const scaleRotationsTextureDesc = this.splatDataTextures['scaleRotations'];
1408
+ const scaleRotationCompressionLevel = scaleRotationsTextureDesc ? scaleRotationsTextureDesc.compressionLevel : undefined;
1409
+ const shITextureDesc = this.splatDataTextures['sphericalHarmonics'];
1410
+ const shCompressionLevel = shITextureDesc ? shITextureDesc.compressionLevel : 0;
1411
+
1412
+ this.fillSplatDataArrays(this.splatDataTextures.baseData.covariances, this.splatDataTextures.baseData.scales,
1413
+ this.splatDataTextures.baseData.rotations, this.splatDataTextures.baseData.centers,
1414
+ this.splatDataTextures.baseData.colors, this.splatDataTextures.baseData.sphericalHarmonics,
1415
+ this.splatDataTextures.baseData.flameModelPos,
1416
+ undefined,
1417
+ covarianceCompressionLevel, scaleRotationCompressionLevel, shCompressionLevel,
1418
+ fromSplat, toSplat, fromSplat);
1419
+ }
1420
+
1421
+ updateDataTexturesFromBaseData(fromSplat, toSplat) {
1422
+ const covarancesTextureDesc = this.splatDataTextures['covariances'];
1423
+ const covarianceCompressionLevel = covarancesTextureDesc ? covarancesTextureDesc.compressionLevel : undefined;
1424
+ const scaleRotationsTextureDesc = this.splatDataTextures['scaleRotations'];
1425
+ const scaleRotationCompressionLevel = scaleRotationsTextureDesc ? scaleRotationsTextureDesc.compressionLevel : undefined;
1426
+ const shTextureDesc = this.splatDataTextures['sphericalHarmonics'];
1427
+ const shCompressionLevel = shTextureDesc ? shTextureDesc.compressionLevel : 0;
1428
+
1429
+ // Update flame data texture
1430
+ const flameModelTextureDescriptor = this.splatDataTextures['flameModel'];
1431
+ const flameModelPos = flameModelTextureDescriptor.data;
1432
+ const flameModelPosTexture = flameModelTextureDescriptor.texture;
1433
+
1434
+ const flameModelPosTextureProps = this.renderer ? this.renderer.properties.get(flameModelPosTexture) : null;
1435
+ if (!flameModelPosTextureProps || !flameModelPosTextureProps.__webglTexture) {
1436
+ flameModelPosTexture.needsUpdate = true;
1437
+ } else {
1438
+ this.updateDataTexture(flameModelPos, flameModelTextureDescriptor.texture, flameModelTextureDescriptor.size,
1439
+ flameModelPosTextureProps, CENTER_COLORS_ELEMENTS_PER_TEXEL, CENTER_COLORS_ELEMENTS_PER_SPLAT, 3,
1440
+ fromSplat, toSplat);
1441
+ }
1442
+
1443
+ // Update center & color data texture
1444
+ const centerColorsTextureDescriptor = this.splatDataTextures['centerColors'];
1445
+ const paddedCenterColors = centerColorsTextureDescriptor.data;
1446
+ const centerColorsTexture = centerColorsTextureDescriptor.texture;
1447
+ SplatMesh.updateCenterColorsPaddedData(fromSplat, toSplat, this.splatDataTextures.baseData.centers,
1448
+ this.splatDataTextures.baseData.colors, paddedCenterColors);
1449
+ const centerColorsTextureProps = this.renderer ? this.renderer.properties.get(centerColorsTexture) : null;
1450
+ if (!centerColorsTextureProps || !centerColorsTextureProps.__webglTexture) {
1451
+ centerColorsTexture.needsUpdate = true;
1452
+ } else {
1453
+ this.updateDataTexture(paddedCenterColors, centerColorsTextureDescriptor.texture, centerColorsTextureDescriptor.size,
1454
+ centerColorsTextureProps, CENTER_COLORS_ELEMENTS_PER_TEXEL, CENTER_COLORS_ELEMENTS_PER_SPLAT, 4,
1455
+ fromSplat, toSplat);
1456
+ }
1457
+
1458
+ // update covariance data texture
1459
+ if (covarancesTextureDesc) {
1460
+ const covariancesTexture = covarancesTextureDesc.texture;
1461
+ const covarancesStartElement = fromSplat * COVARIANCES_ELEMENTS_PER_SPLAT;
1462
+ const covariancesEndElement = toSplat * COVARIANCES_ELEMENTS_PER_SPLAT;
1463
+
1464
+ if (covarianceCompressionLevel === 0) {
1465
+ for (let i = covarancesStartElement; i <= covariancesEndElement; i++) {
1466
+ const covariance = this.splatDataTextures.baseData.covariances[i];
1467
+ covarancesTextureDesc.data[i] = covariance;
1468
+ }
1469
+ } else {
1470
+ SplatMesh.updatePaddedCompressedCovariancesTextureData(this.splatDataTextures.baseData.covariances,
1471
+ covarancesTextureDesc.data,
1472
+ fromSplat * covarancesTextureDesc.elementsPerTexelAllocated,
1473
+ covarancesStartElement, covariancesEndElement);
1474
+ }
1475
+
1476
+ const covariancesTextureProps = this.renderer ? this.renderer.properties.get(covariancesTexture) : null;
1477
+ if (!covariancesTextureProps || !covariancesTextureProps.__webglTexture) {
1478
+ covariancesTexture.needsUpdate = true;
1479
+ } else {
1480
+ if (covarianceCompressionLevel === 0) {
1481
+ this.updateDataTexture(covarancesTextureDesc.data, covarancesTextureDesc.texture, covarancesTextureDesc.size,
1482
+ covariancesTextureProps, covarancesTextureDesc.elementsPerTexelStored,
1483
+ COVARIANCES_ELEMENTS_PER_SPLAT, 4, fromSplat, toSplat);
1484
+ } else {
1485
+ this.updateDataTexture(covarancesTextureDesc.data, covarancesTextureDesc.texture, covarancesTextureDesc.size,
1486
+ covariancesTextureProps, covarancesTextureDesc.elementsPerTexelAllocated,
1487
+ covarancesTextureDesc.elementsPerTexelAllocated, 2, fromSplat, toSplat);
1488
+ }
1489
+ }
1490
+ }
1491
+
1492
+ // update scale and rotation data texture
1493
+ if (scaleRotationsTextureDesc) {
1494
+ const paddedScaleRotations = scaleRotationsTextureDesc.data;
1495
+ const scaleRotationsTexture = scaleRotationsTextureDesc.texture;
1496
+ const elementsPerSplat = 6;
1497
+ const bytesPerElement = scaleRotationCompressionLevel === 0 ? 4 : 2;
1498
+
1499
+ SplatMesh.updateScaleRotationsPaddedData(fromSplat, toSplat, this.splatDataTextures.baseData.scales,
1500
+ this.splatDataTextures.baseData.rotations, paddedScaleRotations);
1501
+ const scaleRotationsTextureProps = this.renderer ? this.renderer.properties.get(scaleRotationsTexture) : null;
1502
+ if (!scaleRotationsTextureProps || !scaleRotationsTextureProps.__webglTexture) {
1503
+ scaleRotationsTexture.needsUpdate = true;
1504
+ } else {
1505
+ this.updateDataTexture(paddedScaleRotations, scaleRotationsTextureDesc.texture, scaleRotationsTextureDesc.size,
1506
+ scaleRotationsTextureProps, SCALES_ROTATIONS_ELEMENTS_PER_TEXEL, elementsPerSplat, bytesPerElement,
1507
+ fromSplat, toSplat);
1508
+ }
1509
+ }
1510
+
1511
+ // update spherical harmonics data texture
1512
+ const shData = this.splatDataTextures.baseData.sphericalHarmonics;
1513
+ if (shData) {
1514
+ let shBytesPerElement = 4;
1515
+ if (shCompressionLevel === 1) shBytesPerElement = 2;
1516
+ else if (shCompressionLevel === 2) shBytesPerElement = 1;
1517
+
1518
+ const updateTexture = (shTexture, shTextureSize, elementsPerTexel, paddedSHArray, paddedSHComponentCount) => {
1519
+ const shTextureProps = this.renderer ? this.renderer.properties.get(shTexture) : null;
1520
+ if (!shTextureProps || !shTextureProps.__webglTexture) {
1521
+ shTexture.needsUpdate = true;
1522
+ } else {
1523
+ this.updateDataTexture(paddedSHArray, shTexture, shTextureSize, shTextureProps, elementsPerTexel,
1524
+ paddedSHComponentCount, shBytesPerElement, fromSplat, toSplat);
1525
+ }
1526
+ };
1527
+
1528
+ const shComponentCount = shTextureDesc.componentCount;
1529
+ const paddedSHComponentCount = shTextureDesc.paddedComponentCount;
1530
+
1531
+ // Update for the case of a single texture for all spherical harmonics data
1532
+ if (shTextureDesc.textureCount === 1) {
1533
+ const paddedSHArray = shTextureDesc.data;
1534
+ for (let c = fromSplat; c <= toSplat; c++) {
1535
+ const srcBase = shComponentCount * c;
1536
+ const destBase = paddedSHComponentCount * c;
1537
+ for (let i = 0; i < shComponentCount; i++) {
1538
+ paddedSHArray[destBase + i] = shData[srcBase + i];
1539
+ }
1540
+ }
1541
+ updateTexture(shTextureDesc.texture, shTextureDesc.size,
1542
+ shTextureDesc.elementsPerTexel, paddedSHArray, paddedSHComponentCount);
1543
+ // Update for the case of spherical harmonics data split among three textures, one for each color channel
1544
+ } else {
1545
+ const shComponentCountPerChannel = shTextureDesc.componentCountPerChannel;
1546
+ for (let t = 0; t < 3; t++) {
1547
+ const paddedSHArray = shTextureDesc.data[t];
1548
+ for (let c = fromSplat; c <= toSplat; c++) {
1549
+ const srcBase = shComponentCount * c;
1550
+ const destBase = paddedSHComponentCount * c;
1551
+ if (shComponentCountPerChannel >= 3) {
1552
+ for (let i = 0; i < 3; i++) paddedSHArray[destBase + i] = shData[srcBase + t * 3 + i];
1553
+ if (shComponentCountPerChannel >= 8) {
1554
+ for (let i = 0; i < 5; i++) paddedSHArray[destBase + 3 + i] = shData[srcBase + 9 + t * 5 + i];
1555
+ }
1556
+ }
1557
+ }
1558
+ updateTexture(shTextureDesc.textures[t], shTextureDesc.size,
1559
+ shTextureDesc.elementsPerTexel, paddedSHArray, paddedSHComponentCount);
1560
+ }
1561
+ }
1562
+ }
1563
+
1564
+ // update scene index & transform data
1565
+ const sceneIndexesTexDesc = this.splatDataTextures['sceneIndexes'];
1566
+ const paddedSceneIndexes = sceneIndexesTexDesc.data;
1567
+ for (let c = this.lastBuildSplatCount; c <= toSplat; c++) {
1568
+ paddedSceneIndexes[c] = this.globalSplatIndexToSceneIndexMap[c];
1569
+ }
1570
+ const sceneIndexesTexture = sceneIndexesTexDesc.texture;
1571
+ const sceneIndexesTextureProps = this.renderer ? this.renderer.properties.get(sceneIndexesTexture) : null;
1572
+ if (!sceneIndexesTextureProps || !sceneIndexesTextureProps.__webglTexture) {
1573
+ sceneIndexesTexture.needsUpdate = true;
1574
+ } else {
1575
+ this.updateDataTexture(paddedSceneIndexes, sceneIndexesTexDesc.texture, sceneIndexesTexDesc.size,
1576
+ sceneIndexesTextureProps, 1, 1, 1, this.lastBuildSplatCount, toSplat);
1577
+ }
1578
+ }
1579
+
1580
+ getTargetCovarianceCompressionLevel() {
1581
+ return this.halfPrecisionCovariancesOnGPU ? 1 : 0;
1582
+ }
1583
+
1584
+ getTargetSphericalHarmonicsCompressionLevel() {
1585
+ return Math.max(1, this.getMaximumSplatBufferCompressionLevel());
1586
+ }
1587
+
1588
+ getMaximumSplatBufferCompressionLevel() {
1589
+ let maxCompressionLevel;
1590
+ for (let i = 0; i < this.scenes.length; i++) {
1591
+ const scene = this.getScene(i);
1592
+ const splatBuffer = scene.splatBuffer;
1593
+ if (i === 0 || splatBuffer.compressionLevel > maxCompressionLevel) {
1594
+ maxCompressionLevel = splatBuffer.compressionLevel;
1595
+ }
1596
+ }
1597
+ return maxCompressionLevel;
1598
+ }
1599
+
1600
+ getMinimumSplatBufferCompressionLevel() {
1601
+ let minCompressionLevel;
1602
+ for (let i = 0; i < this.scenes.length; i++) {
1603
+ const scene = this.getScene(i);
1604
+ const splatBuffer = scene.splatBuffer;
1605
+ if (i === 0 || splatBuffer.compressionLevel < minCompressionLevel) {
1606
+ minCompressionLevel = splatBuffer.compressionLevel;
1607
+ }
1608
+ }
1609
+ return minCompressionLevel;
1610
+ }
1611
+
1612
+ static computeTextureUpdateRegion(startSplat, endSplat, textureWidth, elementsPerTexel, elementsPerSplat) {
1613
+ const texelsPerSplat = elementsPerSplat / elementsPerTexel;
1614
+
1615
+ const startSplatTexels = startSplat * texelsPerSplat;
1616
+ const startRow = Math.floor(startSplatTexels / textureWidth);
1617
+ const startRowElement = startRow * textureWidth * elementsPerTexel;
1618
+
1619
+ const endSplatTexels = endSplat * texelsPerSplat;
1620
+ const endRow = Math.floor(endSplatTexels / textureWidth);
1621
+ const endRowEndElement = endRow * textureWidth * elementsPerTexel + (textureWidth * elementsPerTexel);
1622
+
1623
+ return {
1624
+ 'dataStart': startRowElement,
1625
+ 'dataEnd': endRowEndElement,
1626
+ 'startRow': startRow,
1627
+ 'endRow': endRow
1628
+ };
1629
+ }
1630
+
1631
+ updateDataTexture(paddedData, texture, textureSize, textureProps, elementsPerTexel, elementsPerSplat, bytesPerElement, from, to) {
1632
+ const gl = this.renderer.getContext();
1633
+ const updateRegion = SplatMesh.computeTextureUpdateRegion(from, to, textureSize.x, elementsPerTexel, elementsPerSplat);
1634
+ const updateElementCount = updateRegion.dataEnd - updateRegion.dataStart;
1635
+ const updateDataView = new paddedData.constructor(paddedData.buffer,
1636
+ updateRegion.dataStart * bytesPerElement, updateElementCount);
1637
+ const updateHeight = updateRegion.endRow - updateRegion.startRow + 1;
1638
+ const glType = this.webGLUtils.convert(texture.type);
1639
+ const glFormat = this.webGLUtils.convert(texture.format, texture.colorSpace);
1640
+ const currentTexture = gl.getParameter(gl.TEXTURE_BINDING_2D);
1641
+ gl.bindTexture(gl.TEXTURE_2D, textureProps.__webglTexture);
1642
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, updateRegion.startRow,
1643
+ textureSize.x, updateHeight, glFormat, glType, updateDataView);
1644
+ gl.bindTexture(gl.TEXTURE_2D, currentTexture);
1645
+ }
1646
+
1647
+ static updatePaddedCompressedCovariancesTextureData(sourceData, textureData, textureDataStartIndex, fromElement, toElement) {
1648
+ let textureDataView = new DataView(textureData.buffer);
1649
+ let textureDataIndex = textureDataStartIndex;
1650
+ let sequentialCount = 0;
1651
+ for (let i = fromElement; i <= toElement; i+=2) {
1652
+ textureDataView.setUint16(textureDataIndex * 2, sourceData[i], true);
1653
+ textureDataView.setUint16(textureDataIndex * 2 + 2, sourceData[i + 1], true);
1654
+ textureDataIndex += 2;
1655
+ sequentialCount++;
1656
+ if (sequentialCount >= 3) {
1657
+ textureDataIndex += 2;
1658
+ sequentialCount = 0;
1659
+ }
1660
+ }
1661
+ }
1662
+
1663
+ static updateCenterColorsPaddedData(from, to, centers, colors, paddedCenterColors) {
1664
+ for (let c = from; c <= to; c++) {
1665
+ const colorsBase = c * 4;
1666
+ const centersBase = c * 3;
1667
+ const centerColorsBase = c * 4;
1668
+ paddedCenterColors[centerColorsBase] = rgbaArrayToInteger(colors, colorsBase);
1669
+ paddedCenterColors[centerColorsBase + 1] = uintEncodedFloat(centers[centersBase]);
1670
+ paddedCenterColors[centerColorsBase + 2] = uintEncodedFloat(centers[centersBase + 1]);
1671
+ paddedCenterColors[centerColorsBase + 3] = uintEncodedFloat(centers[centersBase + 2]);
1672
+ }
1673
+ }
1674
+
1675
+ static updateScaleRotationsPaddedData(from, to, scales, rotations, paddedScaleRotations) {
1676
+ const combinedSize = 6;
1677
+ for (let c = from; c <= to; c++) {
1678
+ const scaleBase = c * 3;
1679
+ const rotationBase = c * 4;
1680
+ const scaleRotationsBase = c * combinedSize;
1681
+
1682
+ paddedScaleRotations[scaleRotationsBase] = scales[scaleBase];
1683
+ paddedScaleRotations[scaleRotationsBase + 1] = scales[scaleBase + 1];
1684
+ paddedScaleRotations[scaleRotationsBase + 2] = scales[scaleBase + 2];
1685
+
1686
+ paddedScaleRotations[scaleRotationsBase + 3] = rotations[rotationBase];
1687
+ paddedScaleRotations[scaleRotationsBase + 4] = rotations[rotationBase + 1];
1688
+ paddedScaleRotations[scaleRotationsBase + 5] = rotations[rotationBase + 2];
1689
+ }
1690
+ }
1691
+
1692
+ updateVisibleRegion(sinceLastBuildOnly) {
1693
+ const splatCount = this.getSplatCount(true);
1694
+ const tempCenter = new Vector3();
1695
+ if (!sinceLastBuildOnly) {
1696
+ const avgCenter = new Vector3();
1697
+ this.scenes.forEach((scene) => {
1698
+ avgCenter.add(scene.splatBuffer.sceneCenter);
1699
+ });
1700
+ avgCenter.multiplyScalar(1.0 / this.scenes.length);
1701
+ this.calculatedSceneCenter.copy(avgCenter);
1702
+ this.material.uniforms.sceneCenter.value.copy(this.calculatedSceneCenter);
1703
+ this.material.uniformsNeedUpdate = true;
1704
+ }
1705
+
1706
+ const startSplatFormMaxDistanceCalc = sinceLastBuildOnly ? this.lastBuildSplatCount : 0;
1707
+ for (let i = startSplatFormMaxDistanceCalc; i < splatCount; i++) {
1708
+ this.getSplatCenter(this.morphedMesh, i, tempCenter, true);
1709
+ const distFromCSceneCenter = tempCenter.sub(this.calculatedSceneCenter).length();
1710
+ if (distFromCSceneCenter > this.maxSplatDistanceFromSceneCenter) this.maxSplatDistanceFromSceneCenter = distFromCSceneCenter;
1711
+ }
1712
+
1713
+ if (this.maxSplatDistanceFromSceneCenter - this.visibleRegionBufferRadius > VISIBLE_REGION_EXPANSION_DELTA) {
1714
+ this.visibleRegionBufferRadius = this.maxSplatDistanceFromSceneCenter;
1715
+ this.visibleRegionRadius = Math.max(this.visibleRegionBufferRadius - VISIBLE_REGION_EXPANSION_DELTA, 0.0);
1716
+ }
1717
+ if (this.finalBuild) this.visibleRegionRadius = this.visibleRegionBufferRadius = this.maxSplatDistanceFromSceneCenter;
1718
+ this.updateVisibleRegionFadeDistance();
1719
+ }
1720
+
1721
+ updateVisibleRegionFadeDistance(sceneRevealMode = SceneRevealMode.Default) {
1722
+ const fastFadeRate = SCENE_FADEIN_RATE_FAST * this.sceneFadeInRateMultiplier;
1723
+ const gradualFadeRate = SCENE_FADEIN_RATE_GRADUAL * this.sceneFadeInRateMultiplier;
1724
+ const defaultFadeInRate = this.finalBuild ? fastFadeRate : gradualFadeRate;
1725
+ const fadeInRate = sceneRevealMode === SceneRevealMode.Default ? defaultFadeInRate : gradualFadeRate;
1726
+ this.visibleRegionFadeStartRadius = (this.visibleRegionRadius - this.visibleRegionFadeStartRadius) *
1727
+ fadeInRate + this.visibleRegionFadeStartRadius;
1728
+ const fadeInPercentage = (this.visibleRegionBufferRadius > 0) ?
1729
+ (this.visibleRegionFadeStartRadius / this.visibleRegionBufferRadius) : 0;
1730
+ const fadeInComplete = fadeInPercentage > 0.99;
1731
+ const shaderFadeInComplete = (fadeInComplete || sceneRevealMode === SceneRevealMode.Instant) ? 1 : 0;
1732
+
1733
+ this.material.uniforms.visibleRegionFadeStartRadius.value = this.visibleRegionFadeStartRadius;
1734
+ this.material.uniforms.visibleRegionRadius.value = this.visibleRegionRadius;
1735
+ this.material.uniforms.firstRenderTime.value = this.firstRenderTime;
1736
+ this.material.uniforms.currentTime.value = performance.now();
1737
+ this.material.uniforms.fadeInComplete.value = shaderFadeInComplete;
1738
+ this.material.uniformsNeedUpdate = true;
1739
+ this.visibleRegionChanging = !fadeInComplete;
1740
+ }
1741
+
1742
+ /**
1743
+ * Set the indexes of splats that should be rendered; should be sorted in desired render order.
1744
+ * @param {Uint32Array} globalIndexes Sorted index list of splats to be rendered
1745
+ * @param {number} renderSplatCount Total number of splats to be rendered. Necessary because we may not want to render
1746
+ * every splat.
1747
+ */
1748
+ updateRenderIndexes(globalIndexes, renderSplatCount) {
1749
+ const geometry = this.geometry;
1750
+ geometry.attributes.splatIndex.set(globalIndexes);
1751
+ geometry.attributes.splatIndex.needsUpdate = true;
1752
+ if (renderSplatCount > 0 && this.firstRenderTime === -1) this.firstRenderTime = performance.now();
1753
+ geometry.instanceCount = renderSplatCount;
1754
+ geometry.setDrawRange(0, renderSplatCount);
1755
+ }
1756
+
1757
+ /**
1758
+ * Update the transforms for each scene in this splat mesh from their individual components (position,
1759
+ * quaternion, and scale)
1760
+ */
1761
+ updateTransforms() {
1762
+ for (let i = 0; i < this.scenes.length; i++) {
1763
+ const scene = this.getScene(i);
1764
+ scene.updateTransform(this.dynamicMode);
1765
+ }
1766
+ }
1767
+
1768
+ updateUniforms = function() {
1769
+
1770
+ const viewport = new Vector2();
1771
+
1772
+ return function(renderDimensions, cameraFocalLengthX, cameraFocalLengthY,
1773
+ orthographicMode, orthographicZoom, inverseFocalAdjustment) {
1774
+ const splatCount = this.getSplatCount();
1775
+ if (splatCount > 0) {
1776
+ viewport.set(renderDimensions.x * this.devicePixelRatio,
1777
+ renderDimensions.y * this.devicePixelRatio);
1778
+ this.material.uniforms.viewport.value.copy(viewport);
1779
+ this.material.uniforms.basisViewport.value.set(1.0 / viewport.x, 1.0 / viewport.y);
1780
+ this.material.uniforms.focal.value.set(cameraFocalLengthX, cameraFocalLengthY);
1781
+ this.material.uniforms.orthographicMode.value = orthographicMode ? 1 : 0;
1782
+ this.material.uniforms.orthoZoom.value = orthographicZoom;
1783
+ this.material.uniforms.inverseFocalAdjustment.value = inverseFocalAdjustment;
1784
+ if (this.dynamicMode) {
1785
+ for (let i = 0; i < this.scenes.length; i++) {
1786
+ this.material.uniforms.transforms.value[i].copy(this.getScene(i).transform);
1787
+ }
1788
+ }
1789
+ if (this.enableOptionalEffects) {
1790
+ for (let i = 0; i < this.scenes.length; i++) {
1791
+ this.material.uniforms.sceneOpacity.value[i] = clamp(this.getScene(i).opacity, 0.0, 1.0);
1792
+ this.material.uniforms.sceneVisibility.value[i] = this.getScene(i).visible ? 1 : 0;
1793
+ this.material.uniformsNeedUpdate = true;
1794
+ }
1795
+ }
1796
+ this.material.uniformsNeedUpdate = true;
1797
+ }
1798
+ };
1799
+
1800
+ }();
1801
+
1802
+ setSplatScale(splatScale = 1) {
1803
+ this.splatScale = splatScale;
1804
+ this.material.uniforms.splatScale.value = splatScale;
1805
+ this.material.uniformsNeedUpdate = true;
1806
+ }
1807
+
1808
+ getSplatScale() {
1809
+ return this.splatScale;
1810
+ }
1811
+
1812
+ setPointCloudModeEnabled(enabled) {
1813
+ this.pointCloudModeEnabled = enabled;
1814
+ this.material.uniforms.pointCloudModeEnabled.value = enabled ? 1 : 0;
1815
+ this.material.uniformsNeedUpdate = true;
1816
+ }
1817
+
1818
+ getPointCloudModeEnabled() {
1819
+ return this.pointCloudModeEnabled;
1820
+ }
1821
+
1822
+ getSplatDataTextures() {
1823
+ return this.splatDataTextures;
1824
+ }
1825
+
1826
+ getSplatCount(includeSinceLastBuild = false) {
1827
+ if (!includeSinceLastBuild) return this.lastBuildSplatCount;
1828
+ else return SplatMesh.getTotalSplatCountForScenes(this.scenes);
1829
+ }
1830
+
1831
+ static getTotalSplatCountForScenes(scenes) {
1832
+ let totalSplatCount = 0;
1833
+ for (let scene of scenes) {
1834
+ if (scene && scene.splatBuffer) totalSplatCount += scene.splatBuffer.getSplatCount();
1835
+ }
1836
+ return totalSplatCount;
1837
+ }
1838
+
1839
+ static getTotalSplatCountForSplatBuffers(splatBuffers) {
1840
+ let totalSplatCount = 0;
1841
+ for (let splatBuffer of splatBuffers) totalSplatCount += splatBuffer.getSplatCount();
1842
+ return totalSplatCount;
1843
+ }
1844
+
1845
+ getMaxSplatCount() {
1846
+ return SplatMesh.getTotalMaxSplatCountForScenes(this.scenes);
1847
+ }
1848
+
1849
+ static getTotalMaxSplatCountForScenes(scenes) {
1850
+ let totalSplatCount = 0;
1851
+ for (let scene of scenes) {
1852
+ if (scene && scene.splatBuffer) totalSplatCount += scene.splatBuffer.getMaxSplatCount();
1853
+ }
1854
+ return totalSplatCount;
1855
+ }
1856
+
1857
+ static getTotalMaxSplatCountForSplatBuffers(splatBuffers) {
1858
+ let totalSplatCount = 0;
1859
+ for (let splatBuffer of splatBuffers) totalSplatCount += splatBuffer.getMaxSplatCount();
1860
+ return totalSplatCount;
1861
+ }
1862
+
1863
+ disposeDistancesComputationGPUResources() {
1864
+
1865
+ if (!this.renderer) return;
1866
+
1867
+ const gl = this.renderer.getContext();
1868
+
1869
+ if (this.distancesTransformFeedback.vao) {
1870
+ gl.deleteVertexArray(this.distancesTransformFeedback.vao);
1871
+ this.distancesTransformFeedback.vao = null;
1872
+ }
1873
+ if (this.distancesTransformFeedback.program) {
1874
+ gl.deleteProgram(this.distancesTransformFeedback.program);
1875
+ gl.deleteShader(this.distancesTransformFeedback.vertexShader);
1876
+ gl.deleteShader(this.distancesTransformFeedback.fragmentShader);
1877
+ this.distancesTransformFeedback.program = null;
1878
+ this.distancesTransformFeedback.vertexShader = null;
1879
+ this.distancesTransformFeedback.fragmentShader = null;
1880
+ }
1881
+ this.disposeDistancesComputationGPUBufferResources();
1882
+ if (this.distancesTransformFeedback.id) {
1883
+ gl.deleteTransformFeedback(this.distancesTransformFeedback.id);
1884
+ this.distancesTransformFeedback.id = null;
1885
+ }
1886
+ }
1887
+
1888
+ disposeDistancesComputationGPUBufferResources() {
1889
+
1890
+ if (!this.renderer) return;
1891
+
1892
+ const gl = this.renderer.getContext();
1893
+
1894
+ if (this.distancesTransformFeedback.centersBuffer) {
1895
+ this.distancesTransformFeedback.centersBuffer = null;
1896
+ gl.deleteBuffer(this.distancesTransformFeedback.centersBuffer);
1897
+ }
1898
+ if (this.distancesTransformFeedback.outDistancesBuffer) {
1899
+ gl.deleteBuffer(this.distancesTransformFeedback.outDistancesBuffer);
1900
+ this.distancesTransformFeedback.outDistancesBuffer = null;
1901
+ }
1902
+ }
1903
+
1904
+ /**
1905
+ * Set the Three.js renderer used by this splat mesh
1906
+ * @param {THREE.WebGLRenderer} renderer Instance of THREE.WebGLRenderer
1907
+ */
1908
+ setRenderer(renderer) {
1909
+ if (renderer !== this.renderer) {
1910
+ this.renderer = renderer;
1911
+ const gl = this.renderer.getContext();
1912
+ const extensions = new WebGLExtensions$1(gl);
1913
+ const capabilities = new WebGLCapabilities$1(gl, extensions, {});
1914
+ extensions.init(capabilities);
1915
+ this.webGLUtils = new WebGLUtils$1(gl, extensions, capabilities);
1916
+ if (this.enableDistancesComputationOnGPU && this.getSplatCount() > 0) {
1917
+ this.setupDistancesComputationTransformFeedback();
1918
+ const { centers, sceneIndexes } = this.getDataForDistancesComputation(0, this.getSplatCount() - 1);
1919
+ this.refreshGPUBuffersForDistancesComputation(centers, sceneIndexes);
1920
+ }
1921
+ }
1922
+ }
1923
+
1924
+ setupDistancesComputationTransformFeedback = function() {
1925
+
1926
+ let currentMaxSplatCount;
1927
+
1928
+ return () => {
1929
+ const maxSplatCount = this.getMaxSplatCount();
1930
+
1931
+ if (!this.renderer) return;
1932
+
1933
+ const rebuildGPUObjects = (this.lastRenderer !== this.renderer);
1934
+ const rebuildBuffers = currentMaxSplatCount !== maxSplatCount;
1935
+
1936
+ if (!rebuildGPUObjects && !rebuildBuffers) return;
1937
+
1938
+ if (rebuildGPUObjects) {
1939
+ this.disposeDistancesComputationGPUResources();
1940
+ } else if (rebuildBuffers) {
1941
+ this.disposeDistancesComputationGPUBufferResources();
1942
+ }
1943
+
1944
+ const gl = this.renderer.getContext();
1945
+
1946
+ const createShader = (gl, type, source) => {
1947
+ const shader = gl.createShader(type);
1948
+ if (!shader) {
1949
+ console.error('Fatal error: gl could not create a shader object.');
1950
+ return null;
1951
+ }
1952
+
1953
+ gl.shaderSource(shader, source);
1954
+ gl.compileShader(shader);
1955
+
1956
+ const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
1957
+ if (!compiled) {
1958
+ let typeName = 'unknown';
1959
+ if (type === gl.VERTEX_SHADER) typeName = 'vertex shader';
1960
+ else if (type === gl.FRAGMENT_SHADER) typeName = 'fragement shader';
1961
+ const errors = gl.getShaderInfoLog(shader);
1962
+ console.error('Failed to compile ' + typeName + ' with these errors:' + errors);
1963
+ gl.deleteShader(shader);
1964
+ return null;
1965
+ }
1966
+
1967
+ return shader;
1968
+ };
1969
+
1970
+ let vsSource;
1971
+ if (this.integerBasedDistancesComputation) {
1972
+ vsSource =
1973
+ `#version 300 es
1974
+ in ivec4 center;
1975
+ flat out int distance;`;
1976
+ if (this.dynamicMode) {
1977
+ vsSource += `
1978
+ in uint sceneIndex;
1979
+ uniform ivec4 transforms[${Constants.MaxScenes}];
1980
+ void main(void) {
1981
+ ivec4 transform = transforms[sceneIndex];
1982
+ distance = center.x * transform.x + center.y * transform.y + center.z * transform.z + transform.w * center.w;
1983
+ }
1984
+ `;
1985
+ } else {
1986
+ vsSource += `
1987
+ uniform ivec3 modelViewProj;
1988
+ void main(void) {
1989
+ distance = center.x * modelViewProj.x + center.y * modelViewProj.y + center.z * modelViewProj.z;
1990
+ }
1991
+ `;
1992
+ }
1993
+ } else {
1994
+ vsSource =
1995
+ `#version 300 es
1996
+ in vec4 center;
1997
+ flat out float distance;`;
1998
+ if (this.dynamicMode) {
1999
+ vsSource += `
2000
+ in uint sceneIndex;
2001
+ uniform mat4 transforms[${Constants.MaxScenes}];
2002
+ void main(void) {
2003
+ vec4 transformedCenter = transforms[sceneIndex] * vec4(center.xyz, 1.0);
2004
+ distance = transformedCenter.z;
2005
+ }
2006
+ `;
2007
+ } else {
2008
+ vsSource += `
2009
+ uniform vec3 modelViewProj;
2010
+ void main(void) {
2011
+ distance = center.x * modelViewProj.x + center.y * modelViewProj.y + center.z * modelViewProj.z;
2012
+ }
2013
+ `;
2014
+ }
2015
+ }
2016
+
2017
+ const fsSource =
2018
+ `#version 300 es
2019
+ precision lowp float;
2020
+ out vec4 fragColor;
2021
+ void main(){}
2022
+ `;
2023
+
2024
+ const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
2025
+ const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
2026
+ const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false;
2027
+
2028
+ if (rebuildGPUObjects) {
2029
+ this.distancesTransformFeedback.vao = gl.createVertexArray();
2030
+ }
2031
+
2032
+ gl.bindVertexArray(this.distancesTransformFeedback.vao);
2033
+
2034
+ if (rebuildGPUObjects) {
2035
+ const program = gl.createProgram();
2036
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
2037
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
2038
+ if (!vertexShader || !fragmentShader) {
2039
+ throw new Error('Could not compile shaders for distances computation on GPU.');
2040
+ }
2041
+ gl.attachShader(program, vertexShader);
2042
+ gl.attachShader(program, fragmentShader);
2043
+ gl.transformFeedbackVaryings(program, ['distance'], gl.SEPARATE_ATTRIBS);
2044
+ gl.linkProgram(program);
2045
+
2046
+ const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
2047
+ if (!linked) {
2048
+ const error = gl.getProgramInfoLog(program);
2049
+ console.error('Fatal error: Failed to link program: ' + error);
2050
+ gl.deleteProgram(program);
2051
+ gl.deleteShader(fragmentShader);
2052
+ gl.deleteShader(vertexShader);
2053
+ throw new Error('Could not link shaders for distances computation on GPU.');
2054
+ }
2055
+
2056
+ this.distancesTransformFeedback.program = program;
2057
+ this.distancesTransformFeedback.vertexShader = vertexShader;
2058
+ this.distancesTransformFeedback.vertexShader = fragmentShader;
2059
+ }
2060
+
2061
+ gl.useProgram(this.distancesTransformFeedback.program);
2062
+
2063
+ this.distancesTransformFeedback.centersLoc =
2064
+ gl.getAttribLocation(this.distancesTransformFeedback.program, 'center');
2065
+ if (this.dynamicMode) {
2066
+ this.distancesTransformFeedback.sceneIndexesLoc =
2067
+ gl.getAttribLocation(this.distancesTransformFeedback.program, 'sceneIndex');
2068
+ for (let i = 0; i < this.scenes.length; i++) {
2069
+ this.distancesTransformFeedback.transformsLocs[i] =
2070
+ gl.getUniformLocation(this.distancesTransformFeedback.program, `transforms[${i}]`);
2071
+ }
2072
+ } else {
2073
+ this.distancesTransformFeedback.modelViewProjLoc =
2074
+ gl.getUniformLocation(this.distancesTransformFeedback.program, 'modelViewProj');
2075
+ }
2076
+
2077
+ if (rebuildGPUObjects || rebuildBuffers) {
2078
+ this.distancesTransformFeedback.centersBuffer = gl.createBuffer();
2079
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
2080
+ gl.enableVertexAttribArray(this.distancesTransformFeedback.centersLoc);
2081
+ if (this.integerBasedDistancesComputation) {
2082
+ gl.vertexAttribIPointer(this.distancesTransformFeedback.centersLoc, 4, gl.INT, 0, 0);
2083
+ } else {
2084
+ gl.vertexAttribPointer(this.distancesTransformFeedback.centersLoc, 4, gl.FLOAT, false, 0, 0);
2085
+ }
2086
+
2087
+ if (this.dynamicMode) {
2088
+ this.distancesTransformFeedback.sceneIndexesBuffer = gl.createBuffer();
2089
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
2090
+ gl.enableVertexAttribArray(this.distancesTransformFeedback.sceneIndexesLoc);
2091
+ gl.vertexAttribIPointer(this.distancesTransformFeedback.sceneIndexesLoc, 1, gl.UNSIGNED_INT, 0, 0);
2092
+ }
2093
+ }
2094
+
2095
+ if (rebuildGPUObjects || rebuildBuffers) {
2096
+ this.distancesTransformFeedback.outDistancesBuffer = gl.createBuffer();
2097
+ }
2098
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer);
2099
+ gl.bufferData(gl.ARRAY_BUFFER, maxSplatCount * 4, gl.STATIC_READ);
2100
+
2101
+ if (rebuildGPUObjects) {
2102
+ this.distancesTransformFeedback.id = gl.createTransformFeedback();
2103
+ }
2104
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.distancesTransformFeedback.id);
2105
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.distancesTransformFeedback.outDistancesBuffer);
2106
+
2107
+ if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram);
2108
+ if (currentVao) gl.bindVertexArray(currentVao);
2109
+
2110
+ this.lastRenderer = this.renderer;
2111
+ currentMaxSplatCount = maxSplatCount;
2112
+ };
2113
+
2114
+ }();
2115
+
2116
+ /**
2117
+ * Refresh GPU buffers used for computing splat distances with centers data from the scenes for this mesh.
2118
+ * @param {boolean} isUpdate Specify whether or not to update the GPU buffer or to initialize & fill
2119
+ * @param {Array<number>} centers The splat centers data
2120
+ * @param {number} offsetSplats Offset in the GPU buffer at which to start updating data, specified in splats
2121
+ */
2122
+ updateGPUCentersBufferForDistancesComputation(isUpdate, centers, offsetSplats) {
2123
+
2124
+ if (!this.renderer) return;
2125
+
2126
+ const gl = this.renderer.getContext();
2127
+
2128
+ const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
2129
+ gl.bindVertexArray(this.distancesTransformFeedback.vao);
2130
+
2131
+ const ArrayType = this.integerBasedDistancesComputation ? Uint32Array : Float32Array;
2132
+ const attributeBytesPerCenter = 16;
2133
+ const subBufferOffset = offsetSplats * attributeBytesPerCenter;
2134
+
2135
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
2136
+
2137
+ if (isUpdate) {
2138
+ gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, centers);
2139
+ } else {
2140
+ const maxArray = new ArrayType(this.getMaxSplatCount() * attributeBytesPerCenter);
2141
+ maxArray.set(centers);
2142
+ gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW);
2143
+ }
2144
+
2145
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
2146
+
2147
+ if (currentVao) gl.bindVertexArray(currentVao);
2148
+ }
2149
+
2150
+ /**
2151
+ * Refresh GPU buffers used for pre-computing splat distances with centers data from the scenes for this mesh.
2152
+ * @param {boolean} isUpdate Specify whether or not to update the GPU buffer or to initialize & fill
2153
+ * @param {Array<number>} sceneIndexes The splat scene indexes
2154
+ * @param {number} offsetSplats Offset in the GPU buffer at which to start updating data, specified in splats
2155
+ */
2156
+ updateGPUTransformIndexesBufferForDistancesComputation(isUpdate, sceneIndexes, offsetSplats) {
2157
+
2158
+ if (!this.renderer || !this.dynamicMode) return;
2159
+
2160
+ const gl = this.renderer.getContext();
2161
+
2162
+ const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
2163
+ gl.bindVertexArray(this.distancesTransformFeedback.vao);
2164
+
2165
+ const subBufferOffset = offsetSplats * 4;
2166
+
2167
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
2168
+
2169
+ if (isUpdate) {
2170
+ gl.bufferSubData(gl.ARRAY_BUFFER, subBufferOffset, sceneIndexes);
2171
+ } else {
2172
+ const maxArray = new Uint32Array(this.getMaxSplatCount() * 4);
2173
+ maxArray.set(sceneIndexes);
2174
+ gl.bufferData(gl.ARRAY_BUFFER, maxArray, gl.STATIC_DRAW);
2175
+ }
2176
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
2177
+
2178
+ if (currentVao) gl.bindVertexArray(currentVao);
2179
+ }
2180
+
2181
+ /**
2182
+ * Get a typed array containing a mapping from global splat indexes to their scene index.
2183
+ * @param {number} start Starting splat index to store
2184
+ * @param {number} end Ending splat index to store
2185
+ * @return {Uint32Array}
2186
+ */
2187
+ getSceneIndexes(start, end) {
2188
+
2189
+ let sceneIndexes;
2190
+ const fillCount = end - start + 1;
2191
+ sceneIndexes = new Uint32Array(fillCount);
2192
+ for (let i = start; i <= end; i++) {
2193
+ sceneIndexes[i] = this.globalSplatIndexToSceneIndexMap[i];
2194
+ }
2195
+
2196
+ return sceneIndexes;
2197
+ }
2198
+
2199
+ /**
2200
+ * Fill 'array' with the transforms for each scene in this splat mesh.
2201
+ * @param {Array} array Empty array to be filled with scene transforms. If not empty, contents will be overwritten.
2202
+ */
2203
+ fillTransformsArray = function() {
2204
+
2205
+ const tempArray = [];
2206
+
2207
+ return function(array) {
2208
+ if (tempArray.length !== array.length) tempArray.length = array.length;
2209
+ for (let i = 0; i < this.scenes.length; i++) {
2210
+ const sceneTransform = this.getScene(i).transform;
2211
+ const sceneTransformElements = sceneTransform.elements;
2212
+ for (let j = 0; j < 16; j++) {
2213
+ tempArray[i * 16 + j] = sceneTransformElements[j];
2214
+ }
2215
+ }
2216
+ array.set(tempArray);
2217
+ };
2218
+
2219
+ }();
2220
+
2221
+ computeDistancesOnGPU = function() {
2222
+
2223
+ const tempMatrix = new Matrix4();
2224
+
2225
+ return (modelViewProjMatrix, outComputedDistances) => {
2226
+ if (!this.renderer) return;
2227
+
2228
+ // console.time("gpu_compute_distances");
2229
+ const gl = this.renderer.getContext();
2230
+
2231
+ const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
2232
+ const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
2233
+ const currentProgramDeleted = currentProgram ? gl.getProgramParameter(currentProgram, gl.DELETE_STATUS) : false;
2234
+
2235
+ gl.bindVertexArray(this.distancesTransformFeedback.vao);
2236
+ gl.useProgram(this.distancesTransformFeedback.program);
2237
+
2238
+ gl.enable(gl.RASTERIZER_DISCARD);
2239
+
2240
+ if (this.dynamicMode) {
2241
+ for (let i = 0; i < this.scenes.length; i++) {
2242
+ tempMatrix.copy(this.getScene(i).transform);
2243
+ tempMatrix.premultiply(modelViewProjMatrix);
2244
+
2245
+ if (this.integerBasedDistancesComputation) {
2246
+ const iTempMatrix = SplatMesh.getIntegerMatrixArray(tempMatrix);
2247
+ const iTransform = [iTempMatrix[2], iTempMatrix[6], iTempMatrix[10], iTempMatrix[14]];
2248
+ gl.uniform4i(this.distancesTransformFeedback.transformsLocs[i], iTransform[0], iTransform[1],
2249
+ iTransform[2], iTransform[3]);
2250
+ } else {
2251
+ gl.uniformMatrix4fv(this.distancesTransformFeedback.transformsLocs[i], false, tempMatrix.elements);
2252
+ }
2253
+ }
2254
+ } else {
2255
+ if (this.integerBasedDistancesComputation) {
2256
+ const iViewProjMatrix = SplatMesh.getIntegerMatrixArray(modelViewProjMatrix);
2257
+ const iViewProj = [iViewProjMatrix[2], iViewProjMatrix[6], iViewProjMatrix[10]];
2258
+ gl.uniform3i(this.distancesTransformFeedback.modelViewProjLoc, iViewProj[0], iViewProj[1], iViewProj[2]);
2259
+ } else {
2260
+ const viewProj = [modelViewProjMatrix.elements[2], modelViewProjMatrix.elements[6], modelViewProjMatrix.elements[10]];
2261
+ gl.uniform3f(this.distancesTransformFeedback.modelViewProjLoc, viewProj[0], viewProj[1], viewProj[2]);
2262
+ }
2263
+ }
2264
+
2265
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.centersBuffer);
2266
+ gl.enableVertexAttribArray(this.distancesTransformFeedback.centersLoc);
2267
+ if (this.integerBasedDistancesComputation) {
2268
+ gl.vertexAttribIPointer(this.distancesTransformFeedback.centersLoc, 4, gl.INT, 0, 0);
2269
+ } else {
2270
+ gl.vertexAttribPointer(this.distancesTransformFeedback.centersLoc, 4, gl.FLOAT, false, 0, 0);
2271
+ }
2272
+
2273
+ if (this.dynamicMode) {
2274
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.sceneIndexesBuffer);
2275
+ gl.enableVertexAttribArray(this.distancesTransformFeedback.sceneIndexesLoc);
2276
+ gl.vertexAttribIPointer(this.distancesTransformFeedback.sceneIndexesLoc, 1, gl.UNSIGNED_INT, 0, 0);
2277
+ }
2278
+
2279
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.distancesTransformFeedback.id);
2280
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.distancesTransformFeedback.outDistancesBuffer);
2281
+
2282
+ gl.beginTransformFeedback(gl.POINTS);
2283
+ gl.drawArrays(gl.POINTS, 0, this.getSplatCount());
2284
+ gl.endTransformFeedback();
2285
+
2286
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
2287
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
2288
+
2289
+ gl.disable(gl.RASTERIZER_DISCARD);
2290
+
2291
+ const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
2292
+ gl.flush();
2293
+
2294
+ const promise = new Promise((resolve) => {
2295
+ const checkSync = () => {
2296
+ if (this.disposed) {
2297
+ resolve();
2298
+ } else {
2299
+ const timeout = 0;
2300
+ const bitflags = 0;
2301
+ const status = gl.clientWaitSync(sync, bitflags, timeout);
2302
+ switch (status) {
2303
+ case gl.TIMEOUT_EXPIRED:
2304
+ this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync);
2305
+ return this.computeDistancesOnGPUSyncTimeout;
2306
+ case gl.WAIT_FAILED:
2307
+ throw new Error('should never get here');
2308
+ default: {
2309
+ this.computeDistancesOnGPUSyncTimeout = null;
2310
+ gl.deleteSync(sync);
2311
+ const currentVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
2312
+ gl.bindVertexArray(this.distancesTransformFeedback.vao);
2313
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.distancesTransformFeedback.outDistancesBuffer);
2314
+ gl.getBufferSubData(gl.ARRAY_BUFFER, 0, outComputedDistances);
2315
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
2316
+
2317
+ if (currentVao) gl.bindVertexArray(currentVao);
2318
+
2319
+ // console.timeEnd("gpu_compute_distances");
2320
+
2321
+ resolve();
2322
+ }
2323
+ }
2324
+ }
2325
+ };
2326
+ this.computeDistancesOnGPUSyncTimeout = setTimeout(checkSync);
2327
+ });
2328
+
2329
+ if (currentProgram && currentProgramDeleted !== true) gl.useProgram(currentProgram);
2330
+ if (currentVao) gl.bindVertexArray(currentVao);
2331
+
2332
+ return promise;
2333
+ };
2334
+
2335
+ }();
2336
+
2337
+ /**
2338
+ * Given a global splat index, return corresponding local data (splat buffer, index of splat in that splat
2339
+ * buffer, and the corresponding transform)
2340
+ * @param {number} globalIndex Global splat index
2341
+ * @param {object} paramsObj Object in which to store local data
2342
+ * @param {boolean} returnSceneTransform By default, the transform of the scene to which the splat at 'globalIndex' belongs will be
2343
+ * returned via the 'sceneTransform' property of 'paramsObj' only if the splat mesh is static.
2344
+ * If 'returnSceneTransform' is true, the 'sceneTransform' property will always contain the scene
2345
+ * transform, and if 'returnSceneTransform' is false, the 'sceneTransform' property will always
2346
+ * be null.
2347
+ */
2348
+ getLocalSplatParameters(globalIndex, paramsObj, returnSceneTransform) {
2349
+ if (returnSceneTransform === undefined || returnSceneTransform === null) {
2350
+ returnSceneTransform = this.dynamicMode ? false : true;
2351
+ }
2352
+ paramsObj.splatBuffer = this.getSplatBufferForSplat(globalIndex);
2353
+ paramsObj.localIndex = this.getSplatLocalIndex(globalIndex);
2354
+ paramsObj.sceneTransform = returnSceneTransform ? this.getSceneTransformForSplat(globalIndex) : null;
2355
+ }
2356
+
2357
+ /**
2358
+ * Fill arrays with splat data and apply transforms if appropriate. Each array is optional.
2359
+ * @param {Float32Array} covariances Target storage for splat covariances
2360
+ * @param {Float32Array} scales Target storage for splat scales
2361
+ * @param {Float32Array} rotations Target storage for splat rotations
2362
+ * @param {Float32Array} centers Target storage for splat centers
2363
+ * @param {Uint8Array} colors Target storage for splat colors
2364
+ * @param {Float32Array} sphericalHarmonics Target storage for spherical harmonics
2365
+ * @param {boolean} applySceneTransform By default, scene transforms are applied to relevant splat data only if the splat mesh is
2366
+ * static. If 'applySceneTransform' is true, scene transforms will always be applied and if
2367
+ * it is false, they will never be applied. If undefined, the default behavior will apply.
2368
+ * @param {number} covarianceCompressionLevel The compression level for covariances in the destination array
2369
+ * @param {number} sphericalHarmonicsCompressionLevel The compression level for spherical harmonics in the destination array
2370
+ * @param {number} srcStart The start location from which to pull source data
2371
+ * @param {number} srcEnd The end location from which to pull source data
2372
+ * @param {number} destStart The start location from which to write data
2373
+ */
2374
+ fillSplatDataArrays(covariances, scales, rotations, centers, colors, sphericalHarmonics, flameModelPos,
2375
+ applySceneTransform,
2376
+ covarianceCompressionLevel = 0, scaleRotationCompressionLevel = 0, sphericalHarmonicsCompressionLevel = 1,
2377
+ srcStart, srcEnd, destStart = 0, sceneIndex) {
2378
+ const scaleOverride = new Vector3();
2379
+ scaleOverride.x = undefined;
2380
+ scaleOverride.y = undefined;
2381
+ if (this.splatRenderMode === SplatRenderMode.ThreeD) {
2382
+ scaleOverride.z = undefined;
2383
+ } else {
2384
+ scaleOverride.z = 1;
2385
+ }
2386
+ const tempTransform = new Matrix4();
2387
+
2388
+ let startSceneIndex = 0;
2389
+ let endSceneIndex = this.scenes.length - 1;
2390
+ if (sceneIndex !== undefined && sceneIndex !== null && sceneIndex >= 0 && sceneIndex <= this.scenes.length) {
2391
+ startSceneIndex = sceneIndex;
2392
+ endSceneIndex = sceneIndex;
2393
+ }
2394
+ for (let i = startSceneIndex; i <= endSceneIndex; i++) {
2395
+ if (applySceneTransform === undefined || applySceneTransform === null) {
2396
+ applySceneTransform = this.dynamicMode ? false : true;
2397
+ }
2398
+
2399
+ const scene = this.getScene(i);
2400
+ const splatBuffer = scene.splatBuffer;
2401
+ let sceneTransform;
2402
+ if (applySceneTransform) {
2403
+ this.getSceneTransform(i, tempTransform);
2404
+ sceneTransform = tempTransform;
2405
+ }
2406
+ if (covariances) {
2407
+ splatBuffer.fillSplatCovarianceArray(covariances, sceneTransform, srcStart, srcEnd, destStart, covarianceCompressionLevel);
2408
+ }
2409
+ if (scales || rotations) {
2410
+ if (!scales || !rotations) {
2411
+ throw new Error('SplatMesh::fillSplatDataArrays() -> "scales" and "rotations" must both be valid.');
2412
+ }
2413
+ splatBuffer.fillSplatScaleRotationArray(scales, rotations, sceneTransform,
2414
+ srcStart, srcEnd, destStart, scaleRotationCompressionLevel, scaleOverride);
2415
+ }
2416
+ if (centers) splatBuffer.fillSplatCenterArray(this.morphedMesh, centers, sceneTransform, srcStart, srcEnd, destStart);
2417
+ if (colors) splatBuffer.fillSplatColorArray(colors, scene.minimumAlpha, srcStart, srcEnd, destStart);
2418
+ if (sphericalHarmonics) {
2419
+ splatBuffer.fillSphericalHarmonicsArray(sphericalHarmonics, this.minSphericalHarmonicsDegree,
2420
+ sceneTransform, srcStart, srcEnd, destStart, sphericalHarmonicsCompressionLevel);
2421
+ }
2422
+
2423
+ destStart += splatBuffer.getSplatCount();
2424
+ }
2425
+ }
2426
+
2427
+ morphedMesh;
2428
+ /**
2429
+ * Convert splat centers, which are floating point values, to an array of integers and multiply
2430
+ * each by 1000. Centers will get transformed as appropriate before conversion to integer.
2431
+ * @param {number} start The index at which to start retrieving data
2432
+ * @param {number} end The index at which to stop retrieving data
2433
+ * @param {boolean} padFour Enforce alignment of 4 by inserting a 1 after every 3 values
2434
+ * @return {Int32Array}
2435
+ */
2436
+ getIntegerCenters(start, end, padFour = false) {
2437
+ const splatCount = end - start + 1;
2438
+ const floatCenters = new Float32Array(splatCount * 3);
2439
+ this.fillSplatDataArrays(null, null, null, floatCenters, null, null, undefined, undefined, undefined, undefined, start);
2440
+ let intCenters;
2441
+ let componentCount = padFour ? 4 : 3;
2442
+ intCenters = new Int32Array(splatCount * componentCount);
2443
+ for (let i = 0; i < splatCount; i++) {
2444
+ for (let t = 0; t < 3; t++) {
2445
+ intCenters[i * componentCount + t] = Math.round(floatCenters[i * 3 + t] * 1000.0);
2446
+ }
2447
+ if (padFour) intCenters[i * componentCount + 3] = 1000;
2448
+ }
2449
+ return intCenters;
2450
+ }
2451
+
2452
+ /**
2453
+ * Returns an array of splat centers, transformed as appropriate, optionally padded.
2454
+ * @param {number} start The index at which to start retrieving data
2455
+ * @param {number} end The index at which to stop retrieving data
2456
+ * @param {boolean} padFour Enforce alignment of 4 by inserting a 1 after every 3 values
2457
+ * @return {Float32Array}
2458
+ */
2459
+ getFloatCenters(start, end, padFour = false) {
2460
+ const splatCount = end - start + 1;
2461
+ const floatCenters = new Float32Array(splatCount * 3);
2462
+ this.fillSplatDataArrays(null, null, null, floatCenters, null, null, undefined, undefined, undefined, undefined, start);
2463
+ if (!padFour) return floatCenters;
2464
+ let paddedFloatCenters = new Float32Array(splatCount * 4);
2465
+ for (let i = 0; i < splatCount; i++) {
2466
+ for (let t = 0; t < 3; t++) {
2467
+ paddedFloatCenters[i * 4 + t] = floatCenters[i * 3 + t];
2468
+ }
2469
+ paddedFloatCenters[i * 4 + 3] = 1.0;
2470
+ }
2471
+ return paddedFloatCenters;
2472
+ }
2473
+
2474
+ /**
2475
+ * Get the center for a splat, transformed as appropriate.
2476
+ * @param {number} globalIndex Global index of splat
2477
+ * @param {THREE.Vector3} outCenter THREE.Vector3 instance in which to store splat center
2478
+ * @param {boolean} applySceneTransform By default, if the splat mesh is static, the transform of the scene to which the splat at
2479
+ * 'globalIndex' belongs will be applied to the splat center. If 'applySceneTransform' is true,
2480
+ * the scene transform will always be applied and if 'applySceneTransform' is false, the
2481
+ * scene transform will never be applied. If undefined, the default behavior will apply.
2482
+ */
2483
+ getSplatCenter = function() {
2484
+
2485
+ const paramsObj = {};
2486
+
2487
+ return function(morphedMesh, globalIndex, outCenter, applySceneTransform) {
2488
+ this.getLocalSplatParameters(globalIndex, paramsObj, applySceneTransform);
2489
+ paramsObj.splatBuffer.getSplatCenter(morphedMesh, paramsObj.localIndex, outCenter, paramsObj.sceneTransform);
2490
+ };
2491
+
2492
+ }();
2493
+
2494
+ /**
2495
+ * Get the scale and rotation for a splat, transformed as appropriate.
2496
+ * @param {number} globalIndex Global index of splat
2497
+ * @param {THREE.Vector3} outScale THREE.Vector3 instance in which to store splat scale
2498
+ * @param {THREE.Quaternion} outRotation THREE.Quaternion instance in which to store splat rotation
2499
+ * @param {boolean} applySceneTransform By default, if the splat mesh is static, the transform of the scene to which the splat at
2500
+ * 'globalIndex' belongs will be applied to the splat scale and rotation. If
2501
+ * 'applySceneTransform' is true, the scene transform will always be applied and if
2502
+ * 'applySceneTransform' is false, the scene transform will never be applied. If undefined,
2503
+ * the default behavior will apply.
2504
+ */
2505
+ getSplatScaleAndRotation = function() {
2506
+
2507
+ const paramsObj = {};
2508
+ const scaleOverride = new Vector3();
2509
+
2510
+ return function(globalIndex, outScale, outRotation, applySceneTransform) {
2511
+ this.getLocalSplatParameters(globalIndex, paramsObj, applySceneTransform);
2512
+ scaleOverride.x = undefined;
2513
+ scaleOverride.y = undefined;
2514
+ scaleOverride.z = undefined;
2515
+ if (this.splatRenderMode === SplatRenderMode.TwoD) scaleOverride.z = 0;
2516
+ paramsObj.splatBuffer.getSplatScaleAndRotation(paramsObj.localIndex, outScale, outRotation,
2517
+ paramsObj.sceneTransform, scaleOverride);
2518
+ };
2519
+
2520
+ }();
2521
+
2522
+ /**
2523
+ * Get the color for a splat.
2524
+ * @param {number} globalIndex Global index of splat
2525
+ * @param {THREE.Vector4} outColor THREE.Vector4 instance in which to store splat color
2526
+ */
2527
+ getSplatColor = function() {
2528
+
2529
+ const paramsObj = {};
2530
+
2531
+ return function(globalIndex, outColor) {
2532
+ this.getLocalSplatParameters(globalIndex, paramsObj);
2533
+ paramsObj.splatBuffer.getSplatColor(paramsObj.localIndex, outColor);
2534
+ };
2535
+
2536
+ }();
2537
+
2538
+ /**
2539
+ * Store the transform of the scene at 'sceneIndex' in 'outTransform'.
2540
+ * @param {number} sceneIndex Index of the desired scene
2541
+ * @param {THREE.Matrix4} outTransform Instance of THREE.Matrix4 in which to store the scene's transform
2542
+ */
2543
+ getSceneTransform(sceneIndex, outTransform) {
2544
+ const scene = this.getScene(sceneIndex);
2545
+ scene.updateTransform(this.dynamicMode);
2546
+ outTransform.copy(scene.transform);
2547
+ }
2548
+
2549
+ /**
2550
+ * Get the scene at 'sceneIndex'.
2551
+ * @param {number} sceneIndex Index of the desired scene
2552
+ * @return {SplatScene}
2553
+ */
2554
+ getScene(sceneIndex) {
2555
+ if (sceneIndex < 0 || sceneIndex >= this.scenes.length) {
2556
+ throw new Error('SplatMesh::getScene() -> Invalid scene index.');
2557
+ }
2558
+ return this.scenes[sceneIndex];
2559
+ }
2560
+
2561
+ getSceneCount() {
2562
+ return this.scenes.length;
2563
+ }
2564
+
2565
+ getSplatBufferForSplat(globalIndex) {
2566
+ return this.getScene(this.globalSplatIndexToSceneIndexMap[globalIndex]).splatBuffer;
2567
+ }
2568
+
2569
+ getSceneIndexForSplat(globalIndex) {
2570
+ return this.globalSplatIndexToSceneIndexMap[globalIndex];
2571
+ }
2572
+
2573
+ getSceneTransformForSplat(globalIndex) {
2574
+ return this.getScene(this.globalSplatIndexToSceneIndexMap[globalIndex]).transform;
2575
+ }
2576
+
2577
+ getSplatLocalIndex(globalIndex) {
2578
+ return this.globalSplatIndexToLocalSplatIndexMap[globalIndex];
2579
+ }
2580
+
2581
+ static getIntegerMatrixArray(matrix) {
2582
+ const matrixElements = matrix.elements;
2583
+ const intMatrixArray = [];
2584
+ for (let i = 0; i < 16; i++) {
2585
+ intMatrixArray[i] = Math.round(matrixElements[i] * 1000.0);
2586
+ }
2587
+ return intMatrixArray;
2588
+ }
2589
+
2590
+ computeBoundingBox(applySceneTransforms = false, sceneIndex) {
2591
+ let splatCount = this.getSplatCount();
2592
+ if (sceneIndex !== undefined && sceneIndex !== null) {
2593
+ if (sceneIndex < 0 || sceneIndex >= this.scenes.length) {
2594
+ throw new Error('SplatMesh::computeBoundingBox() -> Invalid scene index.');
2595
+ }
2596
+ splatCount = this.scenes[sceneIndex].splatBuffer.getSplatCount();
2597
+ }
2598
+
2599
+ const floatCenters = new Float32Array(splatCount * 3);
2600
+ this.fillSplatDataArrays(null, null, null, floatCenters, null, null, applySceneTransforms,
2601
+ undefined, undefined, undefined, undefined, sceneIndex);
2602
+
2603
+ const min = new Vector3();
2604
+ const max = new Vector3();
2605
+ for (let i = 0; i < splatCount; i++) {
2606
+ const offset = i * 3;
2607
+ const x = floatCenters[offset];
2608
+ const y = floatCenters[offset + 1];
2609
+ const z = floatCenters[offset + 2];
2610
+ if (i === 0 || x < min.x) min.x = x;
2611
+ if (i === 0 || y < min.y) min.y = y;
2612
+ if (i === 0 || z < min.z) min.z = z;
2613
+ if (i === 0 || x > max.x) max.x = x;
2614
+ if (i === 0 || y > max.y) max.y = y;
2615
+ if (i === 0 || z > max.z) max.z = z;
2616
+ }
2617
+
2618
+ return new Box3(min, max);
2619
+ }
2620
+ }