@myned-ai/gsplat-flame-avatar-renderer 1.0.2 → 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 +1 -21
  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,2895 @@
1
+ /**
2
+ * Viewer
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 head model integration
9
+ * - Extended with expression/pose controls
10
+ * - Additional rendering pipeline modifications
11
+ * - Additional ~900 lines of FLAME-specific code
12
+ */
13
+
14
+ import { Bone, DynamicDrawUsage, InstancedBufferAttribute, MathUtils, Matrix4, OrthographicCamera, PerspectiveCamera, Quaternion, Scene, Skeleton, Vector2, Vector3, WebGLRenderer } from 'three';
15
+ import {
16
+ getCurrentTime,
17
+ clamp,
18
+ delayedExecute,
19
+ isIOS,
20
+ getIOSSemever,
21
+ Semver,
22
+ fetchWithProgress,
23
+ nativePromiseWithExtractedComponents,
24
+ abortablePromiseWithExtractedComponents,
25
+ disposeAllMeshes,
26
+ AbortedPromiseError
27
+ } from '../utils/Util.js';
28
+ import { RenderMode } from '../enums/RenderMode.js';
29
+ import { LogLevel } from '../enums/LogLevel.js';
30
+ import { SplatRenderMode } from '../enums/SplatRenderMode.js';
31
+ import { SceneFormat, sceneFormatFromPath } from '../enums/SceneFormat.js';
32
+ import { SceneRevealMode } from '../enums/SceneRevealMode.js';
33
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
34
+ import { SplatMesh } from './SplatMesh.js';
35
+ import { SplatScene } from './SplatScene.js';
36
+ import { DirectLoadError } from '../loaders/DirectLoadError.js';
37
+ import { PlyLoader } from '../loaders/PlyLoader.js';
38
+ import { Raycaster } from '../raycaster/Raycaster.js';
39
+ import { createSortWorker } from '../worker/SortWorker.js';
40
+ import {
41
+ SCENE_FADEIN_RATE_FAST,
42
+ SCENE_FADEIN_RATE_GRADUAL,
43
+ THREE_CAMERA_FOV,
44
+ MINIMUM_DISTANCE_TO_NEW_FOCAL_POINT,
45
+ CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION,
46
+ MIN_SPLAT_COUNT_TO_SHOW_SPLAT_TREE_LOADING_SPINNER,
47
+ FOCUS_MARKER_FADE_IN_SPEED,
48
+ FOCUS_MARKER_FADE_OUT_SPEED,
49
+ Constants,
50
+ LoaderStatus
51
+ } from '../enums/EngineConstants.js';
52
+
53
+ // UI components - stub implementations for now
54
+ class LoadingSpinner {
55
+ constructor(msg, container) { this.tasks = []; }
56
+ show() {}
57
+ hide() {}
58
+ setContainer(c) {}
59
+ addTask(msg) { return this.tasks.push(msg); }
60
+ removeTask(id) {}
61
+ removeAllTasks() { this.tasks = []; }
62
+ setMessageForTask(id, msg) {}
63
+ }
64
+
65
+ class LoadingProgressBar {
66
+ constructor(container) {}
67
+ show() {}
68
+ hide() {}
69
+ setContainer(c) {}
70
+ setProgress(p) {}
71
+ }
72
+
73
+ class SceneHelper {
74
+ constructor(scene) {
75
+ this.scene = scene;
76
+ this.meshCursor = null;
77
+ this.meshCursorVisible = false;
78
+ this.focusMarker = null;
79
+ this.focusMarkerOpacity = 0;
80
+ this.controlPlane = null;
81
+ this.controlPlaneVisible = false;
82
+ }
83
+ setupMeshCursor() {}
84
+ setupFocusMarker() {}
85
+ setupControlPlane() {}
86
+ updateMeshCursor(pos, active) {}
87
+ updateFocusMarker(target, camera, renderDimensions) {}
88
+ updateControlPlane(cam, ctrl) {}
89
+ setFocusMarkerVisibility(v) {}
90
+ setFocusMarkerOpacity(o) { this.focusMarkerOpacity = o; }
91
+ getFocusMarkerOpacity() { return this.focusMarkerOpacity; }
92
+ positionAndOrientFocusMarker(pos, cam) {}
93
+ positionAndOrientMeshCursor(pos, cam) {}
94
+ setMeshCursorVisibility(visible) { this.meshCursorVisible = visible; }
95
+ getMeschCursorVisibility() { return this.meshCursorVisible; }
96
+ setControlPlaneVisibility(visible) { this.controlPlaneVisible = visible; }
97
+ positionAndOrientControlPlane(pos, quat, scale) {}
98
+ updateForRenderMode(mode, mesh) {}
99
+ dispose() {}
100
+ }
101
+
102
+ /**
103
+ * Viewer - Core Gaussian Splatting Viewer
104
+ * Handles the Three.js scene, camera, controls, and rendering loop.
105
+ */
106
+ export class Viewer {
107
+ /**
108
+ * @param {object} options - Configuration options
109
+ * @param {Array<number>} [options.cameraUp=[0, 1, 0]] - Camera up vector
110
+ * @param {Array<number>} [options.initialCameraPosition=[0, 10, 15]] - Initial camera position
111
+ * @param {Array<number>} [options.initialCameraRotation=[0, 0, 0]] - Initial camera rotation
112
+ * @param {Array<number>} [options.initialCameraLookAt=[0, 0, 0]] - Initial camera look-at point
113
+ * @param {boolean} [options.dropInMode=false] - If true, viewer is used as a Three.js scene object
114
+ * @param {boolean} [options.selfDrivenMode=true] - If true, viewer manages its own render loop
115
+ * @param {boolean} [options.useBuiltInControls=true] - If true, uses OrbitControls
116
+ * @param {HTMLElement} [options.rootElement] - Parent element for the canvas
117
+ * @param {HTMLCanvasElement} [options.threejsCanvas] - Existing canvas to use
118
+ * @param {boolean} [options.ignoreDevicePixelRatio=false] - If true, forces DPR to 1
119
+ * @param {boolean} [options.halfPrecisionCovariancesOnGPU=false] - Use 16-bit float for covariances
120
+ * @param {THREE.Scene} [options.threeScene] - External Three.js scene to render
121
+ * @param {THREE.WebGLRenderer} [options.renderer] - External Three.js renderer
122
+ * @param {THREE.Camera} [options.camera] - External Three.js camera
123
+ * @param {boolean} [options.gpuAcceleratedSort=false] - Use GPU for sorting distances
124
+ * @param {boolean} [options.integerBasedSort=true] - Use integer arithmetic for sorting
125
+ * @param {boolean} [options.sharedMemoryForWorkers=true] - Use SharedArrayBuffer for workers
126
+ * @param {boolean} [options.dynamicScene=false] - Optimize for dynamic scenes
127
+ */
128
+ constructor(options = {}) {
129
+ // The natural 'up' vector for viewing the scene (only has an effect when used with orbit controls and
130
+ // when the viewer uses its own camera).
131
+ if (!options.cameraUp) options.cameraUp = [0, 1, 0];
132
+ this.cameraUp = new Vector3().fromArray(options.cameraUp);
133
+
134
+ // The camera's initial position (only used when the viewer uses its own camera).
135
+ if (!options.initialCameraPosition)
136
+ options.initialCameraPosition = [0, 10, 15];
137
+ this.initialCameraPosition = new Vector3().fromArray(
138
+ options.initialCameraPosition
139
+ );
140
+
141
+ if (!options.initialCameraRotation)
142
+ options.initialCameraRotation = [0, 0, 0];
143
+ this.initialCameraRotation = new Vector3().fromArray(
144
+ options.initialCameraRotation
145
+ );
146
+ this.backgroundColor = options.backgroundColor;
147
+
148
+ // The initial focal point of the camera and center of the camera's orbit (only used when the viewer uses its own camera).
149
+ if (!options.initialCameraLookAt) options.initialCameraLookAt = [0, 0, 0];
150
+ this.initialCameraLookAt = new Vector3().fromArray(
151
+ options.initialCameraLookAt
152
+ );
153
+
154
+ // 'dropInMode' is a flag that is used internally to support the usage of the viewer as a Three.js scene object
155
+ this.dropInMode = options.dropInMode || false;
156
+
157
+ // If 'selfDrivenMode' is true, the viewer manages its own update/animation loop via requestAnimationFrame()
158
+ if (options.selfDrivenMode === undefined || options.selfDrivenMode === null)
159
+ options.selfDrivenMode = true;
160
+ this.selfDrivenMode = options.selfDrivenMode && !this.dropInMode;
161
+ this.selfDrivenUpdateFunc = this.selfDrivenUpdate.bind(this);
162
+
163
+ // If 'useBuiltInControls' is true, the viewer will create its own instance of OrbitControls and attach to the camera
164
+ if (options.useBuiltInControls === undefined)
165
+ options.useBuiltInControls = true;
166
+ this.useBuiltInControls = options.useBuiltInControls;
167
+
168
+ // parent element of the Three.js renderer canvas
169
+ this.rootElement = options.rootElement;
170
+ this.canvas = options.threejsCanvas;
171
+ // Tells the viewer to pretend the device pixel ratio is 1, which can boost performance on devices where it is larger,
172
+ // at a small cost to visual quality
173
+ this.ignoreDevicePixelRatio = options.ignoreDevicePixelRatio || false;
174
+ this.devicePixelRatio = this.ignoreDevicePixelRatio
175
+ ? 1
176
+ : window.devicePixelRatio || 1;
177
+
178
+ // Tells the viewer to use 16-bit floating point values when storing splat covariance data in textures, instead of 32-bit
179
+ this.halfPrecisionCovariancesOnGPU =
180
+ options.halfPrecisionCovariancesOnGPU || false;
181
+
182
+ // If 'threeScene' is valid, it will be rendered by the viewer along with the splat mesh
183
+ this.threeScene = options.threeScene;
184
+ // Allows for usage of an external Three.js renderer
185
+ this.renderer = options.renderer;
186
+ // Allows for usage of an external Three.js camera
187
+ this.camera = options.camera;
188
+
189
+ // If 'gpuAcceleratedSort' is true, a partially GPU-accelerated approach to sorting splats will be used.
190
+ // Currently this means pre-computing splat distances from the camera on the GPU
191
+ this.gpuAcceleratedSort = options.gpuAcceleratedSort || false;
192
+
193
+ // if 'integerBasedSort' is true, the integer version of splat centers as well as other values used to calculate
194
+ // splat distances are used instead of the float version. This speeds up computation, but introduces the possibility of
195
+ // overflow in larger scenes.
196
+ if (
197
+ options.integerBasedSort === undefined ||
198
+ options.integerBasedSort === null
199
+ ) {
200
+ options.integerBasedSort = true;
201
+ }
202
+ this.integerBasedSort = options.integerBasedSort;
203
+
204
+ // If 'sharedMemoryForWorkers' is true, a SharedArrayBuffer will be used to communicate with web workers. This method
205
+ // is faster than copying memory to or from web workers, but comes with security implications as outlined here:
206
+ // https://web.dev/articles/cross-origin-isolation-guide
207
+ // If enabled, it requires specific CORS headers to be present in the response from the server that is sent when
208
+ // loading the application. More information is available in the README.
209
+ if (
210
+ options.sharedMemoryForWorkers === undefined ||
211
+ options.sharedMemoryForWorkers === null
212
+ )
213
+ options.sharedMemoryForWorkers = true;
214
+ this.sharedMemoryForWorkers = false; //options.sharedMemoryForWorkers;
215
+
216
+ // if 'dynamicScene' is true, it tells the viewer to assume scene elements are not stationary or that the number of splats in the
217
+ // scene may change. This prevents optimizations that depend on a static scene from being made. Additionally, if 'dynamicScene' is
218
+ // true it tells the splat mesh to not apply scene tranforms to splat data that is returned by functions like
219
+ // SplatMesh.getSplatCenter() by default.
220
+ this.dynamicScene = !!options.dynamicScene;
221
+
222
+ // When true, will perform additional steps during rendering to address artifacts caused by the rendering of gaussians at a
223
+ // substantially different resolution than that at which they were rendered during training. This will only work correctly
224
+ // for models that were trained using a process that utilizes this compensation calculation. For more details:
225
+ // https://github.com/nerfstudio-project/gsplat/pull/117
226
+ // https://github.com/graphdeco-inria/gaussian-splatting/issues/294#issuecomment-1772688093
227
+ this.antialiased = options.antialiased || false;
228
+
229
+ // This constant is added to the projected 2D screen-space splat scales
230
+ this.kernel2DSize =
231
+ options.kernel2DSize === undefined ? 0.3 : options.kernel2DSize;
232
+
233
+ // if 'renderMode' is RenderMode.Always, then the viewer will rrender the scene on every update. If it is RenderMode.OnChange,
234
+ // it will only render when something in the scene has changed.
235
+ this.renderMode = options.renderMode || RenderMode.Always;
236
+
237
+ // SceneRevealMode.Default results in a nice, slow fade-in effect for progressively loaded scenes,
238
+ // and a fast fade-in for non progressively loaded scenes.
239
+ // SceneRevealMode.Gradual will force a slow fade-in for all scenes.
240
+ // SceneRevealMode.Instant will force all loaded scene data to be immediately visible.
241
+ this.sceneRevealMode = options.sceneRevealMode || SceneRevealMode.Default;
242
+
243
+ // Hacky, experimental, non-scientific parameter for tweaking focal length related calculations. For scenes with very
244
+ // small gaussians, small details, and small dimensions -- increasing this value can help improve visual quality.
245
+ this.focalAdjustment = options.focalAdjustment || 1.0;
246
+
247
+ // Specify the maximum screen-space splat size, can help deal with large splats that get too unwieldy
248
+ this.maxScreenSpaceSplatSize = options.maxScreenSpaceSplatSize || 1024;
249
+
250
+ // The verbosity of console logging
251
+ this.logLevel = options.logLevel || LogLevel.None;
252
+
253
+ // Degree of spherical harmonics to utilize in rendering splats (assuming the data is present in the splat scene).
254
+ // Valid values are 0 - 2. Default value is 0.
255
+ this.sphericalHarmonicsDegree = options.sphericalHarmonicsDegree || 0;
256
+
257
+ // When true, allows for usage of extra properties and attributes during rendering for effects such as opacity adjustment.
258
+ // Default is false for performance reasons. These properties are separate from transform properties (scale, rotation, position)
259
+ // that are enabled by the 'dynamicScene' parameter.
260
+ this.enableOptionalEffects = options.enableOptionalEffects || false;
261
+
262
+ // Enable the usage of SIMD WebAssembly instructions for the splat sort
263
+ if (
264
+ options.enableSIMDInSort === undefined ||
265
+ options.enableSIMDInSort === null
266
+ )
267
+ options.enableSIMDInSort = true;
268
+ this.enableSIMDInSort = options.enableSIMDInSort;
269
+
270
+ // Level to compress non KSPLAT files when loading them for direct rendering
271
+ if (
272
+ options.inMemoryCompressionLevel === undefined ||
273
+ options.inMemoryCompressionLevel === null
274
+ ) {
275
+ options.inMemoryCompressionLevel = 0;
276
+ }
277
+ this.inMemoryCompressionLevel = options.inMemoryCompressionLevel;
278
+
279
+ // Reorder splat data in memory after loading is complete to optimize cache utilization. Default is true.
280
+ // Does not apply if splat scene is progressively loaded.
281
+ if (
282
+ options.optimizeSplatData === undefined ||
283
+ options.optimizeSplatData === null
284
+ ) {
285
+ options.optimizeSplatData = true;
286
+ }
287
+ this.optimizeSplatData = options.optimizeSplatData;
288
+
289
+ // When true, the intermediate splat data that is the result of decompressing splat bufffer(s) and is used to
290
+ // populate the data textures will be freed. This will reduces memory usage, but if that data needs to be modified
291
+ // it will need to be re-populated from the splat buffer(s). Default is false.
292
+ if (
293
+ options.freeIntermediateSplatData === undefined ||
294
+ options.freeIntermediateSplatData === null
295
+ ) {
296
+ options.freeIntermediateSplatData = false;
297
+ }
298
+ this.freeIntermediateSplatData = options.freeIntermediateSplatData;
299
+
300
+ // It appears that for certain iOS versions, special actions need to be taken with the
301
+ // usage of SIMD instructions and shared memory
302
+ if (isIOS()) {
303
+ const semver = getIOSSemever();
304
+ if (semver.major < 17) {
305
+ this.enableSIMDInSort = false;
306
+ }
307
+ if (semver.major < 16) {
308
+ this.sharedMemoryForWorkers = false;
309
+ }
310
+ }
311
+
312
+ // Tell the viewer how to render the splats
313
+ if (
314
+ options.splatRenderMode === undefined ||
315
+ options.splatRenderMode === null
316
+ ) {
317
+ options.splatRenderMode = SplatRenderMode.ThreeD;
318
+ }
319
+ this.splatRenderMode = options.splatRenderMode;
320
+
321
+ // Customize the speed at which the scene is revealed
322
+ this.sceneFadeInRateMultiplier = options.sceneFadeInRateMultiplier || 1.0;
323
+
324
+ // Set the range for the depth map for the counting sort used to sort the splats
325
+ this.splatSortDistanceMapPrecision =
326
+ options.splatSortDistanceMapPrecision ||
327
+ Constants.DefaultSplatSortDistanceMapPrecision;
328
+ const maxPrecision = this.integerBasedSort ? 20 : 24;
329
+ this.splatSortDistanceMapPrecision = clamp(
330
+ this.splatSortDistanceMapPrecision,
331
+ 10,
332
+ maxPrecision
333
+ );
334
+
335
+ this.onSplatMeshChangedCallback = null;
336
+ this.createSplatMesh();
337
+
338
+ this.controls = null;
339
+ this.perspectiveControls = null;
340
+ this.orthographicControls = null;
341
+
342
+ this.orthographicCamera = null;
343
+ this.perspectiveCamera = null;
344
+
345
+ this.showMeshCursor = false;
346
+ this.showControlPlane = false;
347
+ this.showInfo = false;
348
+
349
+ this.sceneHelper = null;
350
+
351
+ this.sortWorker = null;
352
+ this.sortRunning = false;
353
+ this.splatRenderCount = 0;
354
+ this.splatSortCount = 0;
355
+ this.lastSplatSortCount = 0;
356
+ this.sortWorkerIndexesToSort = null;
357
+ this.sortWorkerSortedIndexes = null;
358
+ this.sortWorkerPrecomputedDistances = null;
359
+ this.sortWorkerTransforms = null;
360
+ this.preSortMessages = [];
361
+ this.runAfterNextSort = [];
362
+
363
+ this.selfDrivenModeRunning = false;
364
+ this.splatRenderReady = false;
365
+
366
+ this.raycaster = new Raycaster();
367
+
368
+ this.infoPanel = null;
369
+
370
+ this.startInOrthographicMode = false;
371
+
372
+ this.currentFPS = 0;
373
+ this.lastSortTime = 0;
374
+ this.consecutiveRenderFrames = 0;
375
+
376
+ this.previousCameraTarget = new Vector3();
377
+ this.nextCameraTarget = new Vector3();
378
+
379
+ this.mousePosition = new Vector2();
380
+ this.mouseDownPosition = new Vector2();
381
+ this.mouseDownTime = null;
382
+
383
+ this.resizeObserver = null;
384
+ this.mouseMoveListener = null;
385
+ this.mouseDownListener = null;
386
+ this.mouseUpListener = null;
387
+ this.keyDownListener = null;
388
+
389
+ this.sortPromise = null;
390
+ this.sortPromiseResolver = null;
391
+ this.splatSceneDownloadControllers = [];
392
+ this.splatSceneDownloadPromises = {};
393
+ this.splatSceneDownloadAndBuildPromise = null;
394
+ this.splatSceneRemovalPromise = null;
395
+
396
+ this.loadingSpinner = new LoadingSpinner(
397
+ null,
398
+ this.rootElement || document.body
399
+ );
400
+ this.loadingSpinner.hide();
401
+ this.loadingProgressBar = new LoadingProgressBar(
402
+ this.rootElement || document.body
403
+ );
404
+ this.loadingProgressBar.hide();
405
+ // this.infoPanel = new InfoPanel(this.rootElement || document.body)
406
+ // this.infoPanel.hide()
407
+
408
+ this.usingExternalCamera = this.dropInMode || this.camera ? true : false;
409
+ this.usingExternalRenderer = this.dropInMode || this.renderer ? true : false;
410
+
411
+ this.initialized = false;
412
+ this.disposing = false;
413
+ this.disposed = false;
414
+ this.disposePromise = null;
415
+
416
+ this.lastTime = 0;
417
+ this.gaussianSplatCount = 0;
418
+ this.totalFrames = 0;
419
+ this.flame_params = null;
420
+ this.bone_tree = null;
421
+ this.lbs_weight_80k = null;
422
+ this.frame = 0;
423
+
424
+ this.useFlame = true;
425
+ this.bones = null;
426
+ this.skeleton = null;
427
+ this. avatarMesh = null;
428
+ this.skinModel = null;
429
+ this.boneRoot = null;
430
+ this.baseMesh = null;
431
+ this.setSkinAttibutes = false;
432
+
433
+ if (!this.dropInMode) this.init();
434
+ }
435
+
436
+ createSplatMesh() {
437
+ this.splatMesh = new SplatMesh(
438
+ this.splatRenderMode,
439
+ this.dynamicScene,
440
+ this.enableOptionalEffects,
441
+ this.halfPrecisionCovariancesOnGPU,
442
+ this.devicePixelRatio,
443
+ this.gpuAcceleratedSort,
444
+ this.integerBasedSort,
445
+ this.antialiased,
446
+ this.maxScreenSpaceSplatSize,
447
+ this.logLevel,
448
+ this.sphericalHarmonicsDegree,
449
+ this.sceneFadeInRateMultiplier,
450
+ this.kernel2DSize
451
+ );
452
+ this.splatMesh.frustumCulled = false;
453
+ if (this.onSplatMeshChangedCallback) this.onSplatMeshChangedCallback();
454
+ }
455
+
456
+ init() {
457
+ if (this.initialized) return
458
+
459
+ if (!this.rootElement) {
460
+ if (!this.usingExternalRenderer) {
461
+ this.rootElement = document.createElement('div');
462
+ this.rootElement.style.width = '100%';
463
+ this.rootElement.style.height = '100%';
464
+ this.rootElement.style.position = 'absolute';
465
+ document.body.appendChild(this.rootElement);
466
+ } else {
467
+ this.rootElement = this.renderer.domElement || document.body;
468
+ }
469
+ }
470
+
471
+ this.setupCamera();
472
+ this.setupRenderer();
473
+ // 暂时去掉控制器
474
+ // this.setupControls()
475
+ this.setupEventHandlers();
476
+
477
+ this.threeScene = this.threeScene || new Scene();
478
+ this.sceneHelper = new SceneHelper(this.threeScene);
479
+ this.sceneHelper.setupMeshCursor();
480
+ this.sceneHelper.setupFocusMarker();
481
+ this.sceneHelper.setupControlPlane();
482
+
483
+ this.loadingProgressBar.setContainer(this.rootElement);
484
+ this.loadingSpinner.setContainer(this.rootElement);
485
+ // this.infoPanel.setContainer(this.rootElement)
486
+
487
+ this.initialized = true;
488
+ }
489
+
490
+ setupCamera() {
491
+ if (!this.usingExternalCamera) {
492
+ const renderDimensions = new Vector2();
493
+ this.getRenderDimensions(renderDimensions);
494
+
495
+ this.perspectiveCamera = new PerspectiveCamera(
496
+ THREE_CAMERA_FOV,
497
+ renderDimensions.x / renderDimensions.y,
498
+ 0.1,
499
+ 1000
500
+ );
501
+ this.orthographicCamera = new OrthographicCamera(
502
+ renderDimensions.x / -2,
503
+ renderDimensions.x / 2,
504
+ renderDimensions.y / 2,
505
+ renderDimensions.y / -2,
506
+ 0.1,
507
+ 1000
508
+ );
509
+ this.camera = this.startInOrthographicMode
510
+ ? this.orthographicCamera
511
+ : this.perspectiveCamera;
512
+ this.camera.position.copy(this.initialCameraPosition);
513
+ // this.camera.up.copy(this.cameraUp).normalize()
514
+ // this.camera.lookAt(this.initialCameraLookAt)
515
+ this.camera.rotateX(MathUtils.degToRad(this.initialCameraRotation.x));
516
+ this.camera.rotateY(MathUtils.degToRad(this.initialCameraRotation.y));
517
+ this.camera.rotateZ(MathUtils.degToRad(this.initialCameraRotation.z));
518
+ }
519
+ }
520
+
521
+ setupRenderer() {
522
+ if (!this.usingExternalRenderer) {
523
+ const renderDimensions = new Vector2();
524
+ this.getRenderDimensions(renderDimensions);
525
+
526
+ this.renderer = new WebGLRenderer({
527
+ antialias: false,
528
+ precision: 'highp',
529
+ canvas: this.canvas
530
+ });
531
+ this.renderer.setPixelRatio(this.devicePixelRatio);
532
+ this.renderer.autoClear = true;
533
+ this.renderer.setClearColor(this.backgroundColor, 1.0); // set background color according to the config
534
+
535
+ this.renderer.setSize(renderDimensions.x, renderDimensions.y);
536
+
537
+ this.resizeObserver = new ResizeObserver(() => {
538
+ this.getRenderDimensions(renderDimensions);
539
+ this.renderer.setSize(renderDimensions.x, renderDimensions.y);
540
+ this.forceRenderNextFrame();
541
+ });
542
+ this.resizeObserver.observe(this.rootElement);
543
+ this.rootElement.appendChild(this.renderer.domElement);
544
+ }
545
+ }
546
+
547
+ setupControls() {
548
+ if (this.useBuiltInControls) {
549
+ if (!this.usingExternalCamera) {
550
+ this.perspectiveControls = new OrbitControls(
551
+ this.perspectiveCamera,
552
+ this.renderer.domElement
553
+ );
554
+ this.orthographicControls = new OrbitControls(
555
+ this.orthographicCamera,
556
+ this.renderer.domElement
557
+ );
558
+ } else {
559
+ if (this.camera.isOrthographicCamera) {
560
+ this.orthographicControls = new OrbitControls(
561
+ this.camera,
562
+ this.renderer.domElement
563
+ );
564
+ } else {
565
+ this.perspectiveControls = new OrbitControls(
566
+ this.camera,
567
+ this.renderer.domElement
568
+ );
569
+ }
570
+ }
571
+ for (let controls of [
572
+ this.orthographicControls,
573
+ this.perspectiveControls
574
+ ]) {
575
+ if (controls) {
576
+ controls.listenToKeyEvents(window);
577
+ controls.rotateSpeed = 0.5;
578
+ controls.maxPolarAngle = Math.PI * 0.5;// + Math.PI / 24
579
+ controls.minPolarAngle = Math.PI * 0.5;// - Math.PI / 24
580
+ controls.minAzimuthAngle = -Math.PI / 72;
581
+ controls.maxAzimuthAngle = Math.PI / 72;
582
+ controls.enableDamping = true;
583
+ controls.dampingFactor = 0.05;
584
+ controls.target.copy(this.initialCameraLookAt);
585
+ controls.update();
586
+ }
587
+ }
588
+ this.controls = this.camera.isOrthographicCamera
589
+ ? this.orthographicControls
590
+ : this.perspectiveControls;
591
+ this.controls.update();
592
+ }
593
+ }
594
+
595
+ setupEventHandlers() {
596
+ if (this.useBuiltInControls) {
597
+ this.mouseMoveListener = this.onMouseMove.bind(this);
598
+ this.renderer.domElement.addEventListener(
599
+ 'pointermove',
600
+ this.mouseMoveListener,
601
+ false
602
+ );
603
+ this.mouseDownListener = this.onMouseDown.bind(this);
604
+ this.renderer.domElement.addEventListener(
605
+ 'pointerdown',
606
+ this.mouseDownListener,
607
+ false
608
+ );
609
+ this.mouseUpListener = this.onMouseUp.bind(this);
610
+ this.renderer.domElement.addEventListener(
611
+ 'pointerup',
612
+ this.mouseUpListener,
613
+ false
614
+ );
615
+ this.keyDownListener = this.onKeyDown.bind(this);
616
+ // 暂时去掉键盘事件的监听
617
+ // window.addEventListener('keydown', this.keyDownListener, false)
618
+ }
619
+ }
620
+
621
+ removeEventHandlers() {
622
+ if (this.useBuiltInControls) {
623
+ this.renderer.domElement.removeEventListener(
624
+ 'pointermove',
625
+ this.mouseMoveListener
626
+ );
627
+ this.mouseMoveListener = null;
628
+ this.renderer.domElement.removeEventListener(
629
+ 'pointerdown',
630
+ this.mouseDownListener
631
+ );
632
+ this.mouseDownListener = null;
633
+ this.renderer.domElement.removeEventListener(
634
+ 'pointerup',
635
+ this.mouseUpListener
636
+ );
637
+ this.mouseUpListener = null;
638
+ window.removeEventListener('keydown', this.keyDownListener);
639
+ this.keyDownListener = null;
640
+ }
641
+ }
642
+
643
+ setRenderMode(renderMode) {
644
+ this.renderMode = renderMode;
645
+ }
646
+
647
+ setActiveSphericalHarmonicsDegrees(activeSphericalHarmonicsDegrees) {
648
+ this.splatMesh.material.uniforms.sphericalHarmonicsDegree.value =
649
+ activeSphericalHarmonicsDegrees;
650
+ this.splatMesh.material.uniformsNeedUpdate = true;
651
+ }
652
+
653
+ onSplatMeshChanged(callback) {
654
+ this.onSplatMeshChangedCallback = callback;
655
+ }
656
+
657
+ tempForward = new Vector3()
658
+ tempMatrixLeft = new Matrix4()
659
+ tempMatrixRight = new Matrix4()
660
+ onKeyDown = (e) => {
661
+ this.tempForward.set(0, 0, -1);
662
+ this.tempForward.transformDirection(this.camera.matrixWorld);
663
+ this.tempMatrixLeft.makeRotationAxis(this.tempForward, Math.PI / 128);
664
+ this.tempMatrixRight.makeRotationAxis(this.tempForward, -Math.PI / 128);
665
+ switch (e.code) {
666
+ case 'KeyG':
667
+ this.focalAdjustment += 0.02;
668
+ this.forceRenderNextFrame();
669
+ break
670
+ case 'KeyF':
671
+ this.focalAdjustment -= 0.02;
672
+ this.forceRenderNextFrame();
673
+ break
674
+ case 'ArrowLeft':
675
+ this.camera.up.transformDirection(this.tempMatrixLeft);
676
+ break
677
+ case 'ArrowRight':
678
+ this.camera.up.transformDirection(this.tempMatrixRight);
679
+ break
680
+ case 'KeyC':
681
+ this.showMeshCursor = !this.showMeshCursor;
682
+ break
683
+ case 'KeyU':
684
+ this.showControlPlane = !this.showControlPlane;
685
+ break
686
+ case 'KeyI':
687
+ this.showInfo = !this.showInfo;
688
+ if (this.showInfo) {
689
+ // this.infoPanel.show()
690
+ } else {
691
+ // this.infoPanel.hide()
692
+ }
693
+ break
694
+ case 'KeyO':
695
+ if (!this.usingExternalCamera) {
696
+ this.setOrthographicMode(!this.camera.isOrthographicCamera);
697
+ }
698
+ break
699
+ case 'KeyP':
700
+ if (!this.usingExternalCamera) {
701
+ this.splatMesh.setPointCloudModeEnabled(
702
+ !this.splatMesh.getPointCloudModeEnabled()
703
+ );
704
+ }
705
+ break
706
+ case 'Equal':
707
+ if (!this.usingExternalCamera) {
708
+ this.splatMesh.setSplatScale(this.splatMesh.getSplatScale() + 0.05);
709
+ }
710
+ break
711
+ case 'Minus':
712
+ if (!this.usingExternalCamera) {
713
+ this.splatMesh.setSplatScale(
714
+ Math.max(this.splatMesh.getSplatScale() - 0.05, 0.0)
715
+ );
716
+ }
717
+ break
718
+ }
719
+ }
720
+
721
+ onMouseMove(mouse) {
722
+ this.mousePosition.set(mouse.offsetX, mouse.offsetY);
723
+ }
724
+
725
+ onMouseDown() {
726
+ this.mouseDownPosition.copy(this.mousePosition);
727
+ this.mouseDownTime = getCurrentTime();
728
+ }
729
+
730
+ onMouseUp = (function () {
731
+ const clickOffset = new Vector2();
732
+
733
+ return function (mouse) {
734
+ clickOffset.copy(this.mousePosition).sub(this.mouseDownPosition);
735
+ const mouseUpTime = getCurrentTime();
736
+ const wasClick =
737
+ mouseUpTime - this.mouseDownTime < 0.5 && clickOffset.length() < 2;
738
+ if (wasClick) {
739
+ this.onMouseClick(mouse);
740
+ }
741
+ }
742
+ })()
743
+
744
+ onMouseClick(mouse) {
745
+ this.mousePosition.set(mouse.offsetX, mouse.offsetY);
746
+ this.checkForFocalPointChange();
747
+ }
748
+
749
+ checkPointRenderDimensions = new Vector2()
750
+ checkPointToNewFocalPoint = new Vector3()
751
+ checkPointOutHits = []
752
+ checkForFocalPointChange = () => {
753
+ if (!this.transitioningCameraTarget) {
754
+ this.getRenderDimensions(this.checkPointRenderDimensions);
755
+ this.checkPointOutHits.length = 0;
756
+ this.raycaster.setFromCameraAndScreenPosition(
757
+ this.camera,
758
+ this.mousePosition,
759
+ this.checkPointRenderDimensions
760
+ );
761
+ this.raycaster.intersectSplatMesh(this.splatMesh, this.checkPointOutHits);
762
+ if (this.checkPointOutHits.length > 0) {
763
+ const hit = this.checkPointOutHits[0];
764
+ const intersectionPoint = hit.origin;
765
+ this.checkPointToNewFocalPoint
766
+ .copy(intersectionPoint)
767
+ .sub(this.camera.position);
768
+ if (
769
+ this.checkPointToNewFocalPoint.length() >
770
+ MINIMUM_DISTANCE_TO_NEW_FOCAL_POINT
771
+ ) {
772
+ this.previousCameraTarget.copy(this.controls.target);
773
+ this.nextCameraTarget.copy(intersectionPoint);
774
+ this.transitioningCameraTarget = true;
775
+ this.transitioningCameraTargetStartTime = getCurrentTime();
776
+ }
777
+ }
778
+ }
779
+ }
780
+
781
+ getRenderDimensions(outDimensions) {
782
+ if (this.rootElement) {
783
+ outDimensions.x = this.rootElement.offsetWidth;
784
+ outDimensions.y = this.rootElement.offsetHeight;
785
+ } else {
786
+ this.renderer.getSize(outDimensions);
787
+ }
788
+ }
789
+
790
+ setOrthographicMode(orthographicMode) {
791
+ if (orthographicMode === this.camera.isOrthographicCamera) return
792
+ const fromCamera = this.camera;
793
+ const toCamera = orthographicMode
794
+ ? this.orthographicCamera
795
+ : this.perspectiveCamera;
796
+ toCamera.position.copy(fromCamera.position);
797
+ toCamera.up.copy(fromCamera.up);
798
+ toCamera.rotation.copy(fromCamera.rotation);
799
+ toCamera.quaternion.copy(fromCamera.quaternion);
800
+ toCamera.matrix.copy(fromCamera.matrix);
801
+ this.camera = toCamera;
802
+
803
+ if (this.controls) {
804
+ const resetControls = (controls) => {
805
+ controls.saveState();
806
+ controls.reset();
807
+ };
808
+
809
+ const fromControls = this.controls;
810
+ const toControls = orthographicMode
811
+ ? this.orthographicControls
812
+ : this.perspectiveControls;
813
+
814
+ resetControls(toControls);
815
+ resetControls(fromControls);
816
+
817
+ toControls.target.copy(fromControls.target);
818
+ if (orthographicMode) {
819
+ Viewer.setCameraZoomFromPosition(toCamera, fromCamera, fromControls);
820
+ } else {
821
+ Viewer.setCameraPositionFromZoom(toCamera, fromCamera, toControls);
822
+ }
823
+ this.controls = toControls;
824
+ this.camera.lookAt(this.controls.target);
825
+ }
826
+ }
827
+
828
+ static setCameraPositionFromZoom = (function () {
829
+ const tempVector = new Vector3();
830
+
831
+ return function (positionCamera, zoomedCamera, controls) {
832
+ const toLookAtDistance = 1 / (zoomedCamera.zoom * 0.001);
833
+ tempVector
834
+ .copy(controls.target)
835
+ .sub(positionCamera.position)
836
+ .normalize()
837
+ .multiplyScalar(toLookAtDistance)
838
+ .negate();
839
+ positionCamera.position.copy(controls.target).add(tempVector);
840
+ }
841
+ })()
842
+
843
+ static setCameraZoomFromPosition = (function () {
844
+ const tempVector = new Vector3();
845
+
846
+ return function (zoomCamera, positionZamera, controls) {
847
+ const toLookAtDistance = tempVector
848
+ .copy(controls.target)
849
+ .sub(positionZamera.position)
850
+ .length();
851
+ zoomCamera.zoom = 1 / (toLookAtDistance * 0.001);
852
+ }
853
+ })()
854
+
855
+ updateSplatMesh = (function () {
856
+ const renderDimensions = new Vector2();
857
+
858
+ return function () {
859
+ if (!this.splatMesh) return
860
+ const splatCount = this.splatMesh.getSplatCount();
861
+ if (splatCount > 0) {
862
+ this.splatMesh.updateVisibleRegionFadeDistance(this.sceneRevealMode);
863
+ this.splatMesh.updateTransforms();
864
+ this.getRenderDimensions(renderDimensions);
865
+ const focalLengthX =
866
+ this.camera.projectionMatrix.elements[0] *
867
+ 0.5 *
868
+ this.devicePixelRatio *
869
+ renderDimensions.x;
870
+ const focalLengthY =
871
+ this.camera.projectionMatrix.elements[5] *
872
+ 0.5 *
873
+ this.devicePixelRatio *
874
+ renderDimensions.y;
875
+
876
+ const focalMultiplier = this.camera.isOrthographicCamera
877
+ ? 1.0 / this.devicePixelRatio
878
+ : 1.0;
879
+ const focalAdjustment = this.focalAdjustment * focalMultiplier;
880
+ const inverseFocalAdjustment = 1.0 / focalAdjustment;
881
+
882
+ this.adjustForWebXRStereo(renderDimensions);
883
+ this.splatMesh.updateUniforms(
884
+ renderDimensions,
885
+ focalLengthX * focalAdjustment,
886
+ focalLengthY * focalAdjustment,
887
+ this.camera.isOrthographicCamera,
888
+ this.camera.zoom || 1.0,
889
+ inverseFocalAdjustment
890
+ );
891
+ }
892
+ }
893
+ })()
894
+
895
+ adjustForWebXRStereo(renderDimensions) {
896
+ // TODO: Figure out a less hacky way to determine if stereo rendering is active
897
+ if (this.camera && this.webXRActive) {
898
+ const xrCamera = this.renderer.xr.getCamera();
899
+ const xrCameraProj00 = xrCamera.projectionMatrix.elements[0];
900
+ const cameraProj00 = this.camera.projectionMatrix.elements[0];
901
+ renderDimensions.x *= cameraProj00 / xrCameraProj00;
902
+ }
903
+ }
904
+
905
+ isLoadingOrUnloading() {
906
+ return (
907
+ Object.keys(this.splatSceneDownloadPromises).length > 0 ||
908
+ this.splatSceneDownloadAndBuildPromise !== null ||
909
+ this.splatSceneRemovalPromise !== null
910
+ )
911
+ }
912
+
913
+ isDisposingOrDisposed() {
914
+ return this.disposing || this.disposed
915
+ }
916
+
917
+ addSplatSceneDownloadController(controller) {
918
+ this.splatSceneDownloadControllers.push(controller);
919
+ }
920
+
921
+ removeSplatSceneDownloadController(controller) {
922
+ const index = this.splatSceneDownloadControllers.indexOf(controller);
923
+ if (index > -1) {
924
+ this.splatSceneDownloadControllers.splice(index, 1);
925
+ }
926
+ }
927
+
928
+ addSplatSceneDownloadPromise(promise) {
929
+ this.splatSceneDownloadPromises[promise.id] = promise;
930
+ }
931
+
932
+ removeSplatSceneDownloadPromise(promise) {
933
+ delete this.splatSceneDownloadPromises[promise.id];
934
+ }
935
+
936
+ setSplatSceneDownloadAndBuildPromise(promise) {
937
+ this.splatSceneDownloadAndBuildPromise = promise;
938
+ }
939
+
940
+ clearSplatSceneDownloadAndBuildPromise() {
941
+ this.splatSceneDownloadAndBuildPromise = null;
942
+ }
943
+
944
+ /**
945
+ * Add a splat scene to the viewer and display any loading UI if appropriate.
946
+ * @param {string} path Path to splat scene to be loaded
947
+ * @param {object} options {
948
+ *
949
+ * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
950
+ * value (valid range: 0 - 255), defaults to 1
951
+ *
952
+ * showLoadingUI: Display a loading spinner while the scene is loading, defaults to true
953
+ *
954
+ * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
955
+ *
956
+ * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
957
+ *
958
+ * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
959
+ *
960
+ * onProgress: Function to be called as file data are received, or other processing occurs
961
+ *
962
+ * headers: Optional HTTP headers to be sent along with splat requests
963
+ * }
964
+ * @return {Promise}
965
+ */
966
+ addSplatScene(path, options = {}) {
967
+ if (this.isLoadingOrUnloading()) {
968
+ throw new Error(
969
+ 'Cannot add splat scene while another load or unload is already in progress.'
970
+ )
971
+ }
972
+
973
+ if (this.isDisposingOrDisposed()) {
974
+ throw new Error('Cannot add splat scene after dispose() is called.')
975
+ }
976
+
977
+ if (
978
+ options.progressiveLoad &&
979
+ this.splatMesh.scenes &&
980
+ this.splatMesh.scenes.length > 0
981
+ ) {
982
+ console.log(
983
+ 'addSplatScene(): "progressiveLoad" option ignore because there are multiple splat scenes'
984
+ );
985
+ options.progressiveLoad = false;
986
+ }
987
+
988
+ const format =
989
+ options.format !== undefined && options.format !== null
990
+ ? options.format
991
+ : sceneFormatFromPath(path);
992
+ const progressiveLoad =
993
+ Viewer.isProgressivelyLoadable(format) && options.progressiveLoad;
994
+ const showLoadingUI =
995
+ options.showLoadingUI !== undefined && options.showLoadingUI !== null
996
+ ? options.showLoadingUI
997
+ : true;
998
+
999
+ let loadingUITaskId = null;
1000
+ if (showLoadingUI) {
1001
+ this.loadingSpinner.removeAllTasks();
1002
+ loadingUITaskId = this.loadingSpinner.addTask('Downloading...');
1003
+ }
1004
+ const hideLoadingUI = () => {
1005
+ this.loadingProgressBar.hide();
1006
+ this.loadingSpinner.removeAllTasks();
1007
+ };
1008
+
1009
+ const onProgressUIUpdate = (
1010
+ percentComplete,
1011
+ percentCompleteLabel,
1012
+ loaderStatus
1013
+ ) => {
1014
+ if (showLoadingUI) {
1015
+ if (loaderStatus === LoaderStatus.Downloading) {
1016
+ if (percentComplete == 100) {
1017
+ this.loadingSpinner.setMessageForTask(
1018
+ loadingUITaskId,
1019
+ 'Download complete!'
1020
+ );
1021
+ } else {
1022
+ if (progressiveLoad) {
1023
+ this.loadingSpinner.setMessageForTask(
1024
+ loadingUITaskId,
1025
+ 'Downloading splats...'
1026
+ );
1027
+ } else {
1028
+ const suffix = percentCompleteLabel
1029
+ ? `: ${percentCompleteLabel}`
1030
+ : `...`;
1031
+ this.loadingSpinner.setMessageForTask(
1032
+ loadingUITaskId,
1033
+ `Downloading${suffix}`
1034
+ );
1035
+ }
1036
+ }
1037
+ } else if (loaderStatus === LoaderStatus.Processing) {
1038
+ console.log('loaderStatus === LoaderStatus.Processing');
1039
+ this.loadingSpinner.setMessageForTask(
1040
+ loadingUITaskId,
1041
+ 'Processing splats...'
1042
+ );
1043
+ }
1044
+ }
1045
+ };
1046
+
1047
+ let downloadDone = false;
1048
+ let downloadedPercentage = 0;
1049
+ const splatBuffersAddedUIUpdate = (firstBuild, finalBuild) => {
1050
+ if (showLoadingUI) {
1051
+ if (
1052
+ (firstBuild && progressiveLoad) ||
1053
+ (finalBuild && !progressiveLoad)
1054
+ ) {
1055
+ this.loadingSpinner.removeTask(loadingUITaskId);
1056
+ if (!finalBuild && !downloadDone) this.loadingProgressBar.show();
1057
+ }
1058
+ if (progressiveLoad) {
1059
+ if (finalBuild) {
1060
+ downloadDone = true;
1061
+ this.loadingProgressBar.hide();
1062
+ } else {
1063
+ this.loadingProgressBar.setProgress(downloadedPercentage);
1064
+ }
1065
+ }
1066
+ }
1067
+ };
1068
+
1069
+ const onProgress = (
1070
+ percentComplete,
1071
+ percentCompleteLabel,
1072
+ loaderStatus
1073
+ ) => {
1074
+ downloadedPercentage = percentComplete;
1075
+ onProgressUIUpdate(percentComplete, percentCompleteLabel, loaderStatus);
1076
+ if (options.onProgress)
1077
+ options.onProgress(percentComplete, percentCompleteLabel, loaderStatus);
1078
+ };
1079
+
1080
+ const buildSection = (splatBuffer, firstBuild, finalBuild) => {
1081
+ if (!progressiveLoad && options.onProgress)
1082
+ options.onProgress(0, '0%', LoaderStatus.Processing);
1083
+ const addSplatBufferOptions = {
1084
+ rotation: options.rotation || options.orientation,
1085
+ position: options.position,
1086
+ scale: options.scale,
1087
+ splatAlphaRemovalThreshold: options.splatAlphaRemovalThreshold
1088
+ };
1089
+ return this.addSplatBuffers(
1090
+ [splatBuffer],
1091
+ [addSplatBufferOptions],
1092
+ finalBuild,
1093
+ firstBuild && showLoadingUI,
1094
+ showLoadingUI,
1095
+ progressiveLoad,
1096
+ progressiveLoad
1097
+ ).then(() => {
1098
+ if (!progressiveLoad && options.onProgress)
1099
+ options.onProgress(100, '100%', LoaderStatus.Processing);
1100
+ splatBuffersAddedUIUpdate(firstBuild, finalBuild);
1101
+ })
1102
+ };
1103
+
1104
+ const loadFunc = progressiveLoad
1105
+ ? this.downloadAndBuildSingleSplatSceneProgressiveLoad.bind(this)
1106
+ : this.downloadAndBuildSingleSplatSceneStandardLoad.bind(this);
1107
+ return loadFunc(
1108
+ path,
1109
+ format,
1110
+ options.splatAlphaRemovalThreshold,
1111
+ buildSection.bind(this),
1112
+ onProgress,
1113
+ hideLoadingUI.bind(this),
1114
+ options.headers
1115
+ )
1116
+ }
1117
+
1118
+ /**
1119
+ * Download a single splat scene, convert to splat buffer and then rebuild the viewer's splat mesh
1120
+ * by calling 'buildFunc' -- all before displaying the scene. Also sets/clears relevant instance synchronization objects,
1121
+ * and calls appropriate functions on success or failure.
1122
+ * @param {string} path Path to splat scene to be loaded
1123
+ * @param {SceneFormat} format Format of the splat scene file
1124
+ * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified value (valid range: 0 - 255)
1125
+ * @param {function} buildFunc Function to build the viewer's splat mesh with the downloaded splat buffer
1126
+ * @param {function} onProgress Function to be called as file data are received, or other processing occurs
1127
+ * @param {function} onException Function to be called when exception occurs
1128
+ * @param {object} headers Optional HTTP headers to pass to use for downloading splat scene
1129
+ * @return {Promise}
1130
+ */
1131
+ downloadAndBuildSingleSplatSceneStandardLoad(
1132
+ path,
1133
+ format,
1134
+ splatAlphaRemovalThreshold,
1135
+ buildFunc,
1136
+ onProgress,
1137
+ onException,
1138
+ headers
1139
+ ) {
1140
+ const downloadPromise = this.downloadSplatSceneToSplatBuffer(
1141
+ path,
1142
+ splatAlphaRemovalThreshold,
1143
+ onProgress,
1144
+ false,
1145
+ undefined,
1146
+ format,
1147
+ headers
1148
+ );
1149
+
1150
+ // Create a promise that can be resolved/rejected externally, with abort capability
1151
+ const downloadAndBuildPromise = abortablePromiseWithExtractedComponents(
1152
+ downloadPromise.abort ? downloadPromise.abort.bind(downloadPromise) : undefined
1153
+ );
1154
+
1155
+ downloadPromise
1156
+ .then((splatBuffer) => {
1157
+ this.removeSplatSceneDownloadPromise(downloadPromise);
1158
+ return buildFunc(splatBuffer, true, true).then(() => {
1159
+ downloadAndBuildPromise.resolve();
1160
+ this.clearSplatSceneDownloadAndBuildPromise();
1161
+ })
1162
+ })
1163
+ .catch((e) => {
1164
+ if (onException) onException();
1165
+ this.clearSplatSceneDownloadAndBuildPromise();
1166
+ this.removeSplatSceneDownloadPromise(downloadPromise);
1167
+ const error =
1168
+ (e instanceof AbortedPromiseError || e.name === 'AbortError')
1169
+ ? e
1170
+ : new Error(`Viewer::addSplatScene -> Could not load file ${path}`);
1171
+ downloadAndBuildPromise.reject(error);
1172
+ });
1173
+
1174
+ this.addSplatSceneDownloadPromise(downloadPromise);
1175
+ this.setSplatSceneDownloadAndBuildPromise(downloadAndBuildPromise.promise);
1176
+
1177
+ return downloadAndBuildPromise.promise
1178
+ }
1179
+
1180
+ /**
1181
+ * Download a single splat scene and convert to splat buffer in a progressive manner, allowing rendering as the file downloads.
1182
+ * As each section is downloaded, the viewer's splat mesh is rebuilt by calling 'buildFunc'
1183
+ * Also sets/clears relevant instance synchronization objects, and calls appropriate functions on success or failure.
1184
+ * @param {string} path Path to splat scene to be loaded
1185
+ * @param {SceneFormat} format Format of the splat scene file
1186
+ * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified value (valid range: 0 - 255)
1187
+ * @param {function} buildFunc Function to rebuild the viewer's splat mesh after a new splat buffer section is downloaded
1188
+ * @param {function} onDownloadProgress Function to be called as file data are received
1189
+ * @param {function} onDownloadException Function to be called when exception occurs at any point during the full download
1190
+ * @param {object} headers Optional HTTP headers to pass to use for downloading splat scene
1191
+ * @return {Promise}
1192
+ */
1193
+ downloadAndBuildSingleSplatSceneProgressiveLoad(
1194
+ path,
1195
+ format,
1196
+ splatAlphaRemovalThreshold,
1197
+ buildFunc,
1198
+ onDownloadProgress,
1199
+ onDownloadException,
1200
+ headers
1201
+ ) {
1202
+ let progressiveLoadedSectionBuildCount = 0;
1203
+ let progressiveLoadedSectionBuilding = false;
1204
+ const queuedProgressiveLoadSectionBuilds = [];
1205
+
1206
+ const checkAndBuildProgressiveLoadSections = () => {
1207
+ if (
1208
+ queuedProgressiveLoadSectionBuilds.length > 0 &&
1209
+ !progressiveLoadedSectionBuilding &&
1210
+ !this.isDisposingOrDisposed()
1211
+ ) {
1212
+ progressiveLoadedSectionBuilding = true;
1213
+ const queuedBuild = queuedProgressiveLoadSectionBuilds.shift();
1214
+ buildFunc(
1215
+ queuedBuild.splatBuffer,
1216
+ queuedBuild.firstBuild,
1217
+ queuedBuild.finalBuild
1218
+ ).then(() => {
1219
+ progressiveLoadedSectionBuilding = false;
1220
+ if (queuedBuild.firstBuild) {
1221
+ progressiveLoadFirstSectionBuildPromise.resolve();
1222
+ } else if (queuedBuild.finalBuild) {
1223
+ splatSceneDownloadAndBuildPromise.resolve();
1224
+ this.clearSplatSceneDownloadAndBuildPromise();
1225
+ }
1226
+ if (queuedProgressiveLoadSectionBuilds.length > 0) {
1227
+ delayedExecute(() => checkAndBuildProgressiveLoadSections());
1228
+ }
1229
+ });
1230
+ }
1231
+ };
1232
+
1233
+ const onProgressiveLoadSectionProgress = (splatBuffer, finalBuild) => {
1234
+ if (!this.isDisposingOrDisposed()) {
1235
+ if (
1236
+ finalBuild ||
1237
+ queuedProgressiveLoadSectionBuilds.length === 0 ||
1238
+ splatBuffer.getSplatCount() >
1239
+ queuedProgressiveLoadSectionBuilds[0].splatBuffer.getSplatCount()
1240
+ ) {
1241
+ queuedProgressiveLoadSectionBuilds.push({
1242
+ splatBuffer,
1243
+ firstBuild: progressiveLoadedSectionBuildCount === 0,
1244
+ finalBuild
1245
+ });
1246
+ progressiveLoadedSectionBuildCount++;
1247
+ checkAndBuildProgressiveLoadSections();
1248
+ }
1249
+ }
1250
+ };
1251
+
1252
+ const splatSceneDownloadPromise = this.downloadSplatSceneToSplatBuffer(
1253
+ path,
1254
+ splatAlphaRemovalThreshold,
1255
+ onDownloadProgress,
1256
+ true,
1257
+ onProgressiveLoadSectionProgress,
1258
+ format,
1259
+ headers
1260
+ );
1261
+
1262
+ // Get abort handler from download promise
1263
+ const abortHandler = splatSceneDownloadPromise.abort
1264
+ ? splatSceneDownloadPromise.abort.bind(splatSceneDownloadPromise)
1265
+ : undefined;
1266
+
1267
+ const progressiveLoadFirstSectionBuildPromise =
1268
+ abortablePromiseWithExtractedComponents(abortHandler);
1269
+
1270
+ const splatSceneDownloadAndBuildPromise =
1271
+ abortablePromiseWithExtractedComponents(abortHandler);
1272
+
1273
+ this.addSplatSceneDownloadPromise(splatSceneDownloadPromise);
1274
+ this.setSplatSceneDownloadAndBuildPromise(
1275
+ splatSceneDownloadAndBuildPromise.promise
1276
+ );
1277
+
1278
+ splatSceneDownloadPromise
1279
+ .then(() => {
1280
+ this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
1281
+ })
1282
+ .catch((e) => {
1283
+ console.error('Viewer::addSplatScene actual error:', e);
1284
+ this.clearSplatSceneDownloadAndBuildPromise();
1285
+ this.removeSplatSceneDownloadPromise(splatSceneDownloadPromise);
1286
+ const error =
1287
+ (e instanceof AbortedPromiseError || e.name === 'AbortError')
1288
+ ? e
1289
+ : new Error(
1290
+ `Viewer::addSplatScene -> Could not load one or more scenes: ${e.message}`
1291
+ );
1292
+ progressiveLoadFirstSectionBuildPromise.reject(error);
1293
+ if (onDownloadException) onDownloadException(error);
1294
+ });
1295
+
1296
+ return progressiveLoadFirstSectionBuildPromise.promise
1297
+ }
1298
+
1299
+ /**
1300
+ * Add multiple splat scenes to the viewer and display any loading UI if appropriate.
1301
+ * @param {Array<object>} sceneOptions Array of per-scene options: {
1302
+ *
1303
+ * path: Path to splat scene to be loaded
1304
+ *
1305
+ * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
1306
+ * value (valid range: 0 - 255), defaults to 1
1307
+ *
1308
+ * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
1309
+ *
1310
+ * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
1311
+ *
1312
+ * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
1313
+ *
1314
+ * headers: Optional HTTP headers to be sent along with splat requests
1315
+ *
1316
+ * format (SceneFormat) Optional, the format of the scene data (.ply, .ksplat, .splat). If not present, the
1317
+ * file extension in 'path' will be used to determine the format (if it is present)
1318
+ * }
1319
+ * @param {boolean} showLoadingUI Display a loading spinner while the scene is loading, defaults to true
1320
+ * @param {function} onProgress Function to be called as file data are received
1321
+ * @return {Promise}
1322
+ */
1323
+ addSplatScenes(sceneOptions, showLoadingUI = true, onProgress = undefined) {
1324
+ if (this.isLoadingOrUnloading()) {
1325
+ throw new Error(
1326
+ 'Cannot add splat scene while another load or unload is already in progress.'
1327
+ )
1328
+ }
1329
+
1330
+ if (this.isDisposingOrDisposed()) {
1331
+ throw new Error('Cannot add splat scene after dispose() is called.')
1332
+ }
1333
+
1334
+ const fileCount = sceneOptions.length;
1335
+ const percentComplete = [];
1336
+
1337
+ let loadingUITaskId;
1338
+ if (showLoadingUI) {
1339
+ this.loadingSpinner.removeAllTasks();
1340
+ loadingUITaskId = this.loadingSpinner.addTask('Downloading...');
1341
+ }
1342
+
1343
+ const onLoadProgress = (fileIndex, percent, percentLabel, loaderStatus) => {
1344
+ percentComplete[fileIndex] = percent;
1345
+ let totalPercent = 0;
1346
+ for (let i = 0; i < fileCount; i++)
1347
+ totalPercent += percentComplete[i] || 0;
1348
+ totalPercent = totalPercent / fileCount;
1349
+ percentLabel = `${totalPercent.toFixed(2)}%`;
1350
+ if (showLoadingUI) {
1351
+ if (loaderStatus === LoaderStatus.Downloading) {
1352
+ this.loadingSpinner.setMessageForTask(
1353
+ loadingUITaskId,
1354
+ totalPercent == 100
1355
+ ? `Download complete!`
1356
+ : `Downloading: ${percentLabel}`
1357
+ );
1358
+ }
1359
+ }
1360
+ if (onProgress) onProgress(totalPercent, percentLabel, loaderStatus);
1361
+ };
1362
+
1363
+ const abortController = new AbortController();
1364
+ const signal = abortController.signal;
1365
+ this.addSplatSceneDownloadController(abortController);
1366
+
1367
+ const downloadPromises = [];
1368
+ for (let i = 0; i < sceneOptions.length; i++) {
1369
+ const options = sceneOptions[i];
1370
+ const format =
1371
+ options.format !== undefined && options.format !== null
1372
+ ? options.format
1373
+ : sceneFormatFromPath(options.path);
1374
+ const downloadPromise = this.downloadSplatSceneToSplatBuffer(
1375
+ options.path,
1376
+ options.splatAlphaRemovalThreshold,
1377
+ onLoadProgress.bind(this, i),
1378
+ false,
1379
+ undefined,
1380
+ format,
1381
+ options.headers,
1382
+ signal
1383
+ );
1384
+ downloadPromises.push(downloadPromise);
1385
+ }
1386
+
1387
+ const downloadAndBuildPromise = Promise.all(downloadPromises)
1388
+ .then((splatBuffers) => {
1389
+ if (showLoadingUI) this.loadingSpinner.removeTask(loadingUITaskId);
1390
+ if (onProgress) onProgress(0, '0%', LoaderStatus.Processing);
1391
+ return this.addSplatBuffers(
1392
+ splatBuffers,
1393
+ sceneOptions,
1394
+ true,
1395
+ showLoadingUI,
1396
+ showLoadingUI,
1397
+ false,
1398
+ false
1399
+ ).then(() => {
1400
+ if (onProgress) onProgress(100, '100%', LoaderStatus.Processing);
1401
+ this.clearSplatSceneDownloadAndBuildPromise();
1402
+ });
1403
+ })
1404
+ .catch((e) => {
1405
+ if (showLoadingUI) this.loadingSpinner.removeTask(loadingUITaskId);
1406
+ this.clearSplatSceneDownloadAndBuildPromise();
1407
+ const error =
1408
+ e.name === 'AbortError'
1409
+ ? e
1410
+ : new Error(
1411
+ `Viewer::addSplatScenes -> Could not load one or more splat scenes.`
1412
+ );
1413
+ throw error;
1414
+ })
1415
+ .finally(() => {
1416
+ this.removeSplatSceneDownloadController(abortController);
1417
+ });
1418
+
1419
+ this.setSplatSceneDownloadAndBuildPromise(downloadAndBuildPromise);
1420
+ return downloadAndBuildPromise
1421
+ }
1422
+
1423
+ /**
1424
+ * Download a splat scene and convert to SplatBuffer instance.
1425
+ * @param {string} path Path to splat scene to be loaded
1426
+ * @param {number} splatAlphaRemovalThreshold Ignore any splats with an alpha less than the specified
1427
+ * value (valid range: 0 - 255), defaults to 1
1428
+ *
1429
+ * @param {function} onProgress Function to be called as file data are received
1430
+ * @param {boolean} progressiveBuild Construct file sections into splat buffers as they are downloaded
1431
+ * @param {function} onSectionBuilt Function to be called when new section is added to the file
1432
+ * @param {string} format File format of the scene
1433
+ * @param {object} headers Optional HTTP headers to pass to use for downloading splat scene
1434
+ * @param {AbortSignal} signal Optional AbortSignal to cancel the download
1435
+ * @return {AbortablePromise}
1436
+ */
1437
+ downloadSplatSceneToSplatBuffer(
1438
+ path,
1439
+ splatAlphaRemovalThreshold = 1,
1440
+ onProgress = undefined,
1441
+ progressiveBuild = false,
1442
+ onSectionBuilt = undefined,
1443
+ format,
1444
+ headers
1445
+ ) {
1446
+ const optimizeSplatData = progressiveBuild ? false : this.optimizeSplatData;
1447
+ try {
1448
+ if (format === SceneFormat.Ply) {
1449
+ return PlyLoader.loadFromURL(
1450
+ path,
1451
+ onProgress,
1452
+ progressiveBuild,
1453
+ onSectionBuilt,
1454
+ splatAlphaRemovalThreshold,
1455
+ this.inMemoryCompressionLevel,
1456
+ optimizeSplatData,
1457
+ this.sphericalHarmonicsDegree,
1458
+ headers
1459
+ )
1460
+ }
1461
+ } catch (e) {
1462
+ if (e instanceof DirectLoadError) {
1463
+ throw new Error(
1464
+ 'File type or server does not support progressive loading.'
1465
+ )
1466
+ } else {
1467
+ throw e
1468
+ }
1469
+ }
1470
+
1471
+ throw new Error(
1472
+ `Viewer::downloadSplatSceneToSplatBuffer -> File format not supported: ${path}`
1473
+ )
1474
+ }
1475
+
1476
+ static isProgressivelyLoadable(format) {
1477
+ return format === SceneFormat.Ply
1478
+ }
1479
+
1480
+ /**
1481
+ * Add one or more instances of SplatBuffer to the SplatMesh instance managed by the viewer and set up the sorting web worker.
1482
+ * This function will terminate the existing sort worker (if there is one).
1483
+ */
1484
+ addSplatBuffers = (
1485
+ splatBuffers,
1486
+ splatBufferOptions = [],
1487
+ finalBuild = true,
1488
+ showLoadingUI = true,
1489
+ showLoadingUIForSplatTreeBuild = true,
1490
+ replaceExisting = false,
1491
+ enableRenderBeforeFirstSort = false,
1492
+ preserveVisibleRegion = true
1493
+ ) => {
1494
+ if (this.isDisposingOrDisposed()) return Promise.resolve()
1495
+
1496
+ let splatProcessingTaskId = null;
1497
+ const removeSplatProcessingTask = () => {
1498
+ if (splatProcessingTaskId !== null) {
1499
+ this.loadingSpinner.removeTask(splatProcessingTaskId);
1500
+ splatProcessingTaskId = null;
1501
+ }
1502
+ };
1503
+
1504
+ this.splatRenderReady = false;
1505
+ return new Promise((resolve) => {
1506
+ if (showLoadingUI) {
1507
+ splatProcessingTaskId = this.loadingSpinner.addTask(
1508
+ 'Processing splats...'
1509
+ );
1510
+ }
1511
+ delayedExecute(() => {
1512
+ if (this.isDisposingOrDisposed()) {
1513
+ resolve();
1514
+ } else {
1515
+ const buildResults = this.addSplatBuffersToMesh(
1516
+ splatBuffers,
1517
+ splatBufferOptions,
1518
+ finalBuild,
1519
+ showLoadingUIForSplatTreeBuild,
1520
+ replaceExisting,
1521
+ preserveVisibleRegion
1522
+ );
1523
+
1524
+ const maxSplatCount = this.splatMesh.getMaxSplatCount();
1525
+ if (
1526
+ this.sortWorker &&
1527
+ this.sortWorker.maxSplatCount !== maxSplatCount
1528
+ )
1529
+ this.disposeSortWorker();
1530
+ // If we aren't calculating the splat distances from the center on the GPU, the sorting worker needs
1531
+ // splat centers and transform indexes so that it can calculate those distance values.
1532
+ if (!this.gpuAcceleratedSort) {
1533
+ this.preSortMessages.push({
1534
+ centers: buildResults.centers.buffer,
1535
+ sceneIndexes: buildResults.sceneIndexes.buffer,
1536
+ range: {
1537
+ from: buildResults.from,
1538
+ to: buildResults.to,
1539
+ count: buildResults.count
1540
+ }
1541
+ });
1542
+ }
1543
+ const sortWorkerSetupPromise =
1544
+ !this.sortWorker && maxSplatCount > 0
1545
+ ? this.setupSortWorker(this.splatMesh)
1546
+ : Promise.resolve();
1547
+ sortWorkerSetupPromise.then(() => {
1548
+ if (this.isDisposingOrDisposed()) return
1549
+ this.runSplatSort(true, true).then((sortRunning) => {
1550
+ if (!this.sortWorker || !sortRunning) {
1551
+ this.splatRenderReady = true;
1552
+ removeSplatProcessingTask();
1553
+ resolve();
1554
+ } else {
1555
+ if (enableRenderBeforeFirstSort) {
1556
+ this.splatRenderReady = true;
1557
+ } else {
1558
+ this.runAfterNextSort.push(() => {
1559
+ this.splatRenderReady = true;
1560
+ });
1561
+ }
1562
+ this.runAfterNextSort.push(() => {
1563
+ removeSplatProcessingTask();
1564
+ resolve();
1565
+ });
1566
+ }
1567
+ });
1568
+ });
1569
+ }
1570
+ }, true);
1571
+ })
1572
+ }
1573
+
1574
+ /**
1575
+ * Add one or more instances of SplatBuffer to the SplatMesh instance managed by the viewer. By default, this function is additive;
1576
+ * all splat buffers contained by the viewer's splat mesh before calling this function will be preserved. This behavior can be
1577
+ * changed by passing 'true' for 'replaceExisting'.
1578
+ * @param {Array<SplatBuffer>} splatBuffers SplatBuffer instances
1579
+ * @param {Array<object>} splatBufferOptions Array of options objects: {
1580
+ *
1581
+ * splatAlphaRemovalThreshold: Ignore any splats with an alpha less than the specified
1582
+ * value (valid range: 0 - 255), defaults to 1
1583
+ *
1584
+ * position (Array<number>): Position of the scene, acts as an offset from its default position, defaults to [0, 0, 0]
1585
+ *
1586
+ * rotation (Array<number>): Rotation of the scene represented as a quaternion, defaults to [0, 0, 0, 1]
1587
+ *
1588
+ * scale (Array<number>): Scene's scale, defaults to [1, 1, 1]
1589
+ * }
1590
+ * @param {boolean} finalBuild Will the splat mesh be in its final state after this build?
1591
+ * @param {boolean} showLoadingUIForSplatTreeBuild Whether or not to show the loading spinner during construction of the splat tree.
1592
+ * @return {object} Object containing info about the splats that are updated
1593
+ */
1594
+ addSplatBuffersToMesh = (function () {
1595
+ let splatOptimizingTaskId;
1596
+
1597
+ return function (
1598
+ splatBuffers,
1599
+ splatBufferOptions,
1600
+ finalBuild = true,
1601
+ showLoadingUIForSplatTreeBuild = false,
1602
+ replaceExisting = false,
1603
+ preserveVisibleRegion = true
1604
+ ) {
1605
+ if (this.isDisposingOrDisposed()) return
1606
+ let allSplatBuffers = [];
1607
+ let allSplatBufferOptions = [];
1608
+ if (!replaceExisting) {
1609
+ allSplatBuffers =
1610
+ this.splatMesh.scenes.map((scene) => scene.splatBuffer) || [];
1611
+ allSplatBufferOptions = this.splatMesh.sceneOptions
1612
+ ? this.splatMesh.sceneOptions.map((sceneOptions) => sceneOptions)
1613
+ : [];
1614
+ }
1615
+ allSplatBuffers.push(...splatBuffers);
1616
+ allSplatBufferOptions.push(...splatBufferOptions);
1617
+ if (this.renderer) this.splatMesh.setRenderer(this.renderer);
1618
+ const onSplatTreeIndexesUpload = (finished) => {
1619
+ if (this.isDisposingOrDisposed()) return
1620
+ const splatCount = this.splatMesh.getSplatCount();
1621
+ if (
1622
+ showLoadingUIForSplatTreeBuild &&
1623
+ splatCount >= MIN_SPLAT_COUNT_TO_SHOW_SPLAT_TREE_LOADING_SPINNER
1624
+ ) {
1625
+ if (!finished && !splatOptimizingTaskId) {
1626
+ this.loadingSpinner.setMinimized(true, true);
1627
+ splatOptimizingTaskId = this.loadingSpinner.addTask(
1628
+ 'Optimizing data structures...'
1629
+ );
1630
+ }
1631
+ }
1632
+ };
1633
+ const onSplatTreeReady = (finished) => {
1634
+ if (this.isDisposingOrDisposed()) return
1635
+ if (finished && splatOptimizingTaskId) {
1636
+ this.loadingSpinner.removeTask(splatOptimizingTaskId);
1637
+ splatOptimizingTaskId = null;
1638
+ }
1639
+ };
1640
+ const buildResults = this.splatMesh.build(
1641
+ allSplatBuffers,
1642
+ allSplatBufferOptions,
1643
+ true,
1644
+ finalBuild,
1645
+ onSplatTreeIndexesUpload,
1646
+ onSplatTreeReady,
1647
+ preserveVisibleRegion
1648
+ );
1649
+ if (finalBuild && this.freeIntermediateSplatData)
1650
+ this.splatMesh.freeIntermediateSplatData();
1651
+ return buildResults
1652
+ }
1653
+ })()
1654
+
1655
+ /**
1656
+ * Set up the splat sorting web worker.
1657
+ * @param {SplatMesh} splatMesh SplatMesh instance that contains the splats to be sorted
1658
+ * @return {Promise}
1659
+ */
1660
+ async setupSortWorker(splatMesh) {
1661
+ if (this.isDisposingOrDisposed()) return
1662
+ const DistancesArrayType = this.integerBasedSort
1663
+ ? Int32Array
1664
+ : Float32Array;
1665
+ const splatCount = splatMesh.getSplatCount();
1666
+ const maxSplatCount = splatMesh.getMaxSplatCount();
1667
+ this.sortWorker = await createSortWorker(
1668
+ maxSplatCount,
1669
+ this.sharedMemoryForWorkers,
1670
+ this.enableSIMDInSort,
1671
+ this.integerBasedSort,
1672
+ this.splatMesh.dynamicMode,
1673
+ this.splatSortDistanceMapPrecision
1674
+ );
1675
+ return new Promise((resolve) => {
1676
+ this.sortWorker.onmessage = (e) => {
1677
+ if (e.data.sortDone) {
1678
+ this.sortRunning = false;
1679
+ let numbersArray = Array.from(
1680
+ { length: this.gaussianSplatCount },
1681
+ (_, i) => i
1682
+ );
1683
+ if (this.sharedMemoryForWorkers) {
1684
+ this.splatMesh.updateRenderIndexes(
1685
+ this.sortWorkerSortedIndexes,
1686
+ e.data.splatRenderCount
1687
+ );
1688
+ } else {
1689
+ const sortedIndexes = new Uint32Array(
1690
+ e.data.sortedIndexes.buffer,
1691
+ 0,
1692
+ e.data.splatRenderCount
1693
+ );
1694
+ // console.log(sortedIndexes);
1695
+ this.splatMesh.updateRenderIndexes(
1696
+ sortedIndexes,
1697
+ e.data.splatRenderCount
1698
+ );
1699
+ }
1700
+
1701
+ this.lastSplatSortCount = this.splatSortCount;
1702
+
1703
+ this.lastSortTime = e.data.sortTime;
1704
+ this.sortPromiseResolver();
1705
+ this.sortPromiseResolver = null;
1706
+ this.forceRenderNextFrame();
1707
+ if (this.runAfterNextSort.length > 0) {
1708
+ this.runAfterNextSort.forEach((func) => {
1709
+ func();
1710
+ });
1711
+ this.runAfterNextSort.length = 0;
1712
+ }
1713
+ } else if (e.data.sortCanceled) {
1714
+ this.sortRunning = false;
1715
+ } else if (e.data.sortSetupPhase1Complete) {
1716
+ if (this.logLevel >= LogLevel.Info)
1717
+ console.log('Sorting web worker WASM setup complete.');
1718
+ if (this.sharedMemoryForWorkers) {
1719
+ this.sortWorkerSortedIndexes = new Uint32Array(
1720
+ e.data.sortedIndexesBuffer,
1721
+ e.data.sortedIndexesOffset,
1722
+ maxSplatCount
1723
+ );
1724
+ this.sortWorkerIndexesToSort = new Uint32Array(
1725
+ e.data.indexesToSortBuffer,
1726
+ e.data.indexesToSortOffset,
1727
+ maxSplatCount
1728
+ );
1729
+ this.sortWorkerPrecomputedDistances = new DistancesArrayType(
1730
+ e.data.precomputedDistancesBuffer,
1731
+ e.data.precomputedDistancesOffset,
1732
+ maxSplatCount
1733
+ );
1734
+ this.sortWorkerTransforms = new Float32Array(
1735
+ e.data.transformsBuffer,
1736
+ e.data.transformsOffset,
1737
+ Constants.MaxScenes * 16
1738
+ );
1739
+ } else {
1740
+ this.sortWorkerIndexesToSort = new Uint32Array(maxSplatCount);
1741
+ this.sortWorkerPrecomputedDistances = new DistancesArrayType(
1742
+ maxSplatCount
1743
+ );
1744
+ this.sortWorkerTransforms = new Float32Array(
1745
+ Constants.MaxScenes * 16
1746
+ );
1747
+ }
1748
+ for (let i = 0; i < splatCount; i++)
1749
+ this.sortWorkerIndexesToSort[i] = i;
1750
+ this.sortWorker.maxSplatCount = maxSplatCount;
1751
+
1752
+ if (this.logLevel >= LogLevel.Info) {
1753
+ console.log('Sorting web worker ready.');
1754
+ const splatDataTextures = this.splatMesh.getSplatDataTextures();
1755
+ const covariancesTextureSize = splatDataTextures.covariances.size;
1756
+ const centersColorsTextureSize = splatDataTextures.centerColors.size;
1757
+ console.log(
1758
+ 'Covariances texture size: ' +
1759
+ covariancesTextureSize.x +
1760
+ ' x ' +
1761
+ covariancesTextureSize.y
1762
+ );
1763
+ console.log(
1764
+ 'Centers/colors texture size: ' +
1765
+ centersColorsTextureSize.x +
1766
+ ' x ' +
1767
+ centersColorsTextureSize.y
1768
+ );
1769
+ }
1770
+
1771
+ resolve();
1772
+ }
1773
+ };
1774
+ })
1775
+ }
1776
+
1777
+ disposeSortWorker() {
1778
+ if (this.sortWorker) this.sortWorker.terminate();
1779
+ this.sortWorker = null;
1780
+ this.sortPromise = null;
1781
+ if (this.sortPromiseResolver) {
1782
+ this.sortPromiseResolver();
1783
+ this.sortPromiseResolver = null;
1784
+ }
1785
+ this.preSortMessages = [];
1786
+ this.sortRunning = false;
1787
+ }
1788
+
1789
+ removeSplatScene(indexToRemove, showLoadingUI = true) {
1790
+ return this.removeSplatScenes([indexToRemove], showLoadingUI)
1791
+ }
1792
+
1793
+ removeSplatScenes(indexesToRemove, showLoadingUI = true) {
1794
+ if (this.isLoadingOrUnloading()) {
1795
+ throw new Error(
1796
+ 'Cannot remove splat scene while another load or unload is already in progress.'
1797
+ )
1798
+ }
1799
+
1800
+ if (this.isDisposingOrDisposed()) {
1801
+ throw new Error('Cannot remove splat scene after dispose() is called.')
1802
+ }
1803
+
1804
+ let sortPromise;
1805
+
1806
+ this.splatSceneRemovalPromise = new Promise((resolve, reject) => {
1807
+ let revmovalTaskId;
1808
+
1809
+ if (showLoadingUI) {
1810
+ this.loadingSpinner.removeAllTasks();
1811
+ this.loadingSpinner.show();
1812
+ revmovalTaskId = this.loadingSpinner.addTask('Removing splat scene...');
1813
+ }
1814
+
1815
+ const checkAndHideLoadingUI = () => {
1816
+ if (showLoadingUI) {
1817
+ this.loadingSpinner.hide();
1818
+ this.loadingSpinner.removeTask(revmovalTaskId);
1819
+ }
1820
+ };
1821
+
1822
+ const onDone = (error) => {
1823
+ checkAndHideLoadingUI();
1824
+ this.splatSceneRemovalPromise = null;
1825
+ if (!error) resolve();
1826
+ else reject(error);
1827
+ };
1828
+
1829
+ const checkForEarlyExit = () => {
1830
+ if (this.isDisposingOrDisposed()) {
1831
+ onDone();
1832
+ return true
1833
+ }
1834
+ return false
1835
+ };
1836
+
1837
+ sortPromise = this.sortPromise || Promise.resolve();
1838
+ sortPromise.then(() => {
1839
+ if (checkForEarlyExit()) return
1840
+ const savedSplatBuffers = [];
1841
+ const savedSceneOptions = [];
1842
+ const savedSceneTransformComponents = [];
1843
+ for (let i = 0; i < this.splatMesh.scenes.length; i++) {
1844
+ let shouldRemove = false;
1845
+ for (let indexToRemove of indexesToRemove) {
1846
+ if (indexToRemove === i) {
1847
+ shouldRemove = true;
1848
+ break
1849
+ }
1850
+ }
1851
+ if (!shouldRemove) {
1852
+ const scene = this.splatMesh.scenes[i];
1853
+ savedSplatBuffers.push(scene.splatBuffer);
1854
+ savedSceneOptions.push(this.splatMesh.sceneOptions[i]);
1855
+ savedSceneTransformComponents.push({
1856
+ position: scene.position.clone(),
1857
+ quaternion: scene.quaternion.clone(),
1858
+ scale: scene.scale.clone()
1859
+ });
1860
+ }
1861
+ }
1862
+ this.disposeSortWorker();
1863
+ this.splatMesh.dispose();
1864
+ this.sceneRevealMode = SceneRevealMode.Instant;
1865
+ this.createSplatMesh();
1866
+ this.addSplatBuffers(
1867
+ savedSplatBuffers,
1868
+ savedSceneOptions,
1869
+ true,
1870
+ false,
1871
+ true
1872
+ )
1873
+ .then(() => {
1874
+ if (checkForEarlyExit()) return
1875
+ checkAndHideLoadingUI();
1876
+ this.splatMesh.scenes.forEach((scene, index) => {
1877
+ scene.position.copy(savedSceneTransformComponents[index].position);
1878
+ scene.quaternion.copy(
1879
+ savedSceneTransformComponents[index].quaternion
1880
+ );
1881
+ scene.scale.copy(savedSceneTransformComponents[index].scale);
1882
+ });
1883
+ this.splatMesh.updateTransforms();
1884
+ this.splatRenderReady = false;
1885
+
1886
+ this.runSplatSort(true).then(() => {
1887
+ if (checkForEarlyExit()) {
1888
+ this.splatRenderReady = true;
1889
+ return
1890
+ }
1891
+ sortPromise = this.sortPromise || Promise.resolve();
1892
+ sortPromise.then(() => {
1893
+ this.splatRenderReady = true;
1894
+ onDone();
1895
+ });
1896
+ });
1897
+ })
1898
+ .catch((e) => {
1899
+ onDone(e);
1900
+ });
1901
+ });
1902
+ });
1903
+
1904
+ return this.splatSceneRemovalPromise
1905
+ }
1906
+
1907
+ /**
1908
+ * Start self-driven mode
1909
+ */
1910
+ start() {
1911
+ if (this.selfDrivenMode) {
1912
+ this.requestFrameId = requestAnimationFrame(this.selfDrivenUpdateFunc);
1913
+ this.selfDrivenModeRunning = true;
1914
+ } else {
1915
+ throw new Error('Cannot start viewer unless it is in self driven mode.')
1916
+ }
1917
+ }
1918
+
1919
+ /**
1920
+ * Stop self-driven mode
1921
+ */
1922
+ stop() {
1923
+ if (this.selfDrivenMode && this.selfDrivenModeRunning) {
1924
+ cancelAnimationFrame(this.requestFrameId);
1925
+ this.selfDrivenModeRunning = false;
1926
+ }
1927
+ }
1928
+
1929
+ /**
1930
+ * Dispose of all resources held directly and indirectly by this viewer.
1931
+ */
1932
+ async dispose() {
1933
+ if (this.isDisposingOrDisposed()) return this.disposePromise
1934
+
1935
+ for (let controller of this.splatSceneDownloadControllers) {
1936
+ controller.abort();
1937
+ }
1938
+
1939
+ let waitPromises = [];
1940
+ if (this.sortPromise) {
1941
+ waitPromises.push(this.sortPromise);
1942
+ }
1943
+
1944
+ this.disposing = true;
1945
+ this.disposePromise = Promise.all(waitPromises).finally(() => {
1946
+ this.stop();
1947
+ if (this.orthographicControls) {
1948
+ this.orthographicControls.dispose();
1949
+ this.orthographicControls = null;
1950
+ }
1951
+ if (this.perspectiveControls) {
1952
+ this.perspectiveControls.dispose();
1953
+ this.perspectiveControls = null;
1954
+ }
1955
+ this.controls = null;
1956
+ if (this.splatMesh) {
1957
+ this.splatMesh.dispose();
1958
+ this.splatMesh = null;
1959
+ }
1960
+ if (this.avatarMesh) {
1961
+ disposeAllMeshes(this.avatarMesh);
1962
+ this.avatarMesh = null;
1963
+ }
1964
+ if (this.sceneHelper) {
1965
+ this.sceneHelper.dispose();
1966
+ this.sceneHelper = null;
1967
+ }
1968
+ if (this.resizeObserver) {
1969
+ this.resizeObserver.unobserve(this.rootElement);
1970
+ this.resizeObserver = null;
1971
+ }
1972
+ this.disposeSortWorker();
1973
+ this.removeEventHandlers();
1974
+
1975
+ this.loadingSpinner.removeAllTasks();
1976
+ this.loadingSpinner.setContainer(null);
1977
+ this.loadingProgressBar.hide();
1978
+ this.loadingProgressBar.setContainer(null);
1979
+ // this.infoPanel.setContainer(null)
1980
+
1981
+ this.camera = null;
1982
+ this.threeScene = null;
1983
+ this.splatRenderReady = false;
1984
+ this.initialized = false;
1985
+ if (this.renderer) {
1986
+ if (!this.usingExternalRenderer) {
1987
+ this.rootElement.removeChild(this.renderer.domElement);
1988
+ this.renderer.dispose();
1989
+ }
1990
+ this.renderer = null;
1991
+ }
1992
+
1993
+ if (!this.usingExternalRenderer) {
1994
+ // document.body.removeChild(this.rootElement);
1995
+ }
1996
+
1997
+ this.sortWorkerSortedIndexes = null;
1998
+ this.sortWorkerIndexesToSort = null;
1999
+ this.sortWorkerPrecomputedDistances = null;
2000
+ this.sortWorkerTransforms = null;
2001
+ this.disposed = true;
2002
+ this.disposing = false;
2003
+ this.disposePromise = null;
2004
+ });
2005
+ return this.disposePromise
2006
+ }
2007
+ vsyncNum = 4
2008
+ selfDrivenUpdate() {
2009
+ if (this.selfDrivenMode) {
2010
+ this.requestFrameId = requestAnimationFrame(this.selfDrivenUpdateFunc);
2011
+
2012
+ // const currentTime = getCurrentTime();
2013
+ // const calcDelta = currentTime - this.lastTime;
2014
+ // if (calcDelta >= 1.0 / 30.0) {
2015
+ // this.lastTime = currentTime;
2016
+ // } else {
2017
+ // return
2018
+ // }
2019
+ }
2020
+ this.vsyncCount++;
2021
+
2022
+ if (this.vsyncCount < this.vsyncNum) {
2023
+ return
2024
+ }
2025
+
2026
+ this.vsyncCount = 0;
2027
+
2028
+ this.update();
2029
+ if (this.shouldRender()) {
2030
+ //add expression
2031
+
2032
+ this.render();
2033
+ this.consecutiveRenderFrames++;
2034
+ } else {
2035
+ this.consecutiveRenderFrames = 0;
2036
+ }
2037
+ this.renderNextFrame = false;
2038
+ }
2039
+
2040
+ forceRenderNextFrame() {
2041
+ this.renderNextFrame = true;
2042
+ }
2043
+
2044
+ shouldRender = (function () {
2045
+ let renderCount = 0;
2046
+ const lastCameraPosition = new Vector3();
2047
+ const lastCameraOrientation = new Quaternion();
2048
+ const changeEpsilon = 0.0001;
2049
+
2050
+ return function () {
2051
+ if (
2052
+ !this.initialized ||
2053
+ !this.splatRenderReady ||
2054
+ this.isDisposingOrDisposed()
2055
+ )
2056
+ return false
2057
+
2058
+ let shouldRender = false;
2059
+ let cameraChanged = false;
2060
+ if (this.camera) {
2061
+ const cp = this.camera.position;
2062
+ const co = this.camera.quaternion;
2063
+ cameraChanged =
2064
+ Math.abs(cp.x - lastCameraPosition.x) > changeEpsilon ||
2065
+ Math.abs(cp.y - lastCameraPosition.y) > changeEpsilon ||
2066
+ Math.abs(cp.z - lastCameraPosition.z) > changeEpsilon ||
2067
+ Math.abs(co.x - lastCameraOrientation.x) > changeEpsilon ||
2068
+ Math.abs(co.y - lastCameraOrientation.y) > changeEpsilon ||
2069
+ Math.abs(co.z - lastCameraOrientation.z) > changeEpsilon ||
2070
+ Math.abs(co.w - lastCameraOrientation.w) > changeEpsilon;
2071
+ }
2072
+
2073
+ shouldRender =
2074
+ this.renderMode !== RenderMode.Never &&
2075
+ (renderCount === 0 ||
2076
+ this.splatMesh.visibleRegionChanging ||
2077
+ cameraChanged ||
2078
+ this.renderMode === RenderMode.Always ||
2079
+ this.dynamicMode === true ||
2080
+ this.renderNextFrame);
2081
+
2082
+ if (this.camera) {
2083
+ lastCameraPosition.copy(this.camera.position);
2084
+ lastCameraOrientation.copy(this.camera.quaternion);
2085
+ }
2086
+
2087
+ renderCount++;
2088
+ return shouldRender
2089
+ }
2090
+ })()
2091
+
2092
+ render = (function () {
2093
+ return function () {
2094
+ if (
2095
+ !this.initialized ||
2096
+ !this.splatRenderReady ||
2097
+ this.isDisposingOrDisposed()
2098
+ )
2099
+ return
2100
+
2101
+ const hasRenderables = (threeScene) => {
2102
+ for (let child of threeScene.children) {
2103
+ if (child.visible) return true
2104
+ }
2105
+ return false
2106
+ };
2107
+
2108
+ const savedAuoClear = this.renderer.autoClear;
2109
+ if (hasRenderables(this.threeScene)) {
2110
+ this.renderer.render(this.threeScene, this.camera);
2111
+ this.renderer.autoClear = false;
2112
+ }
2113
+ this.renderer.render(this.splatMesh, this.camera);
2114
+ this.renderer.autoClear = false;
2115
+ if (this.sceneHelper.getFocusMarkerOpacity() > 0.0)
2116
+ this.renderer.render(this.sceneHelper.focusMarker, this.camera);
2117
+ if (this.showControlPlane)
2118
+ this.renderer.render(this.sceneHelper.controlPlane, this.camera);
2119
+ this.renderer.autoClear = savedAuoClear;
2120
+ }
2121
+ })()
2122
+
2123
+
2124
+ update(renderer, camera) {
2125
+ // this.frame++
2126
+ const fpsDiv = document.getElementById('fps');
2127
+ if (fpsDiv) {
2128
+ fpsDiv.textContent = `FPS: ${this.currentFPS}`;
2129
+ }
2130
+
2131
+ if(this.frame >= this.totalFrames)
2132
+ this.frame = 0;
2133
+
2134
+ if (this.dropInMode) this.updateForDropInMode(renderer, camera);
2135
+
2136
+ if (
2137
+ !this.initialized ||
2138
+ !this.splatRenderReady ||
2139
+ this.isDisposingOrDisposed()
2140
+ )
2141
+ return
2142
+
2143
+ if (this.controls) {
2144
+ this.controls.update();
2145
+ if (this.camera.isOrthographicCamera && !this.usingExternalCamera) {
2146
+ Viewer.setCameraPositionFromZoom(
2147
+ this.camera,
2148
+ this.camera,
2149
+ this.controls
2150
+ );
2151
+ }
2152
+ }
2153
+ this.runMorphUpdate();
2154
+ this.runSplatSort(true,true);
2155
+
2156
+ const replaceIndexes = false;
2157
+ if (replaceIndexes) {
2158
+ this.splatMesh.updateRenderIndexes(
2159
+ this.sortedIndexes,
2160
+ this.sortedIndexes.length
2161
+ );
2162
+ }
2163
+
2164
+ this.updateForRendererSizeChanges();
2165
+ this.updateSplatMesh();
2166
+ this.updateMeshCursor();
2167
+ this.updateFPS();
2168
+ this.timingSensitiveUpdates();
2169
+ // this.updateInfoPanel()
2170
+ this.updateControlPlane();
2171
+ }
2172
+
2173
+ sortedIndexes
2174
+ updateForDropInMode(renderer, camera) {
2175
+ this.renderer = renderer;
2176
+ if (this.splatMesh) this.splatMesh.setRenderer(this.renderer);
2177
+ this.camera = camera;
2178
+ if (this.controls) this.controls.object = camera;
2179
+ this.init();
2180
+ }
2181
+
2182
+ lastCalcTime = getCurrentTime()
2183
+ fpsFrameCount = 0
2184
+ updateFPS = () => {
2185
+ if (
2186
+ this.consecutiveRenderFrames >
2187
+ CONSECUTIVE_RENDERED_FRAMES_FOR_FPS_CALCULATION
2188
+ ) {
2189
+ const currentTime = getCurrentTime();
2190
+ const calcDelta = currentTime - this.lastCalcTime;
2191
+ if (calcDelta >= 1.0) {
2192
+ this.currentFPS = this.fpsFrameCount;
2193
+ this.fpsFrameCount = 0;
2194
+ this.lastCalcTime = currentTime;
2195
+ } else {
2196
+ this.fpsFrameCount++;
2197
+ }
2198
+ } else {
2199
+ this.currentFPS = null;
2200
+ }
2201
+ }
2202
+
2203
+ updateForRendererSizeChanges = (function () {
2204
+ const lastRendererSize = new Vector2();
2205
+ const currentRendererSize = new Vector2();
2206
+ let lastCameraOrthographic;
2207
+
2208
+ return function () {
2209
+ if (!this.usingExternalCamera) {
2210
+ this.renderer.getSize(currentRendererSize);
2211
+ if (
2212
+ lastCameraOrthographic === undefined ||
2213
+ lastCameraOrthographic !== this.camera.isOrthographicCamera ||
2214
+ currentRendererSize.x !== lastRendererSize.x ||
2215
+ currentRendererSize.y !== lastRendererSize.y
2216
+ ) {
2217
+ if (this.camera.isOrthographicCamera) {
2218
+ this.camera.left = -currentRendererSize.x / 2.0;
2219
+ this.camera.right = currentRendererSize.x / 2.0;
2220
+ this.camera.top = currentRendererSize.y / 2.0;
2221
+ this.camera.bottom = -currentRendererSize.y / 2.0;
2222
+ } else {
2223
+ this.camera.aspect = currentRendererSize.x / currentRendererSize.y;
2224
+ }
2225
+ this.camera.updateProjectionMatrix();
2226
+ lastRendererSize.copy(currentRendererSize);
2227
+ lastCameraOrthographic = this.camera.isOrthographicCamera;
2228
+ }
2229
+ }
2230
+ }
2231
+ })()
2232
+
2233
+ timingSensitiveUpdates = (function () {
2234
+ let lastUpdateTime;
2235
+
2236
+ return function () {
2237
+ const currentTime = getCurrentTime();
2238
+ if (!lastUpdateTime) lastUpdateTime = currentTime;
2239
+ const timeDelta = currentTime - lastUpdateTime;
2240
+
2241
+ this.updateCameraTransition(currentTime);
2242
+ this.updateFocusMarker(timeDelta);
2243
+
2244
+ lastUpdateTime = currentTime;
2245
+ }
2246
+ })()
2247
+
2248
+ tempCameraTarget = new Vector3()
2249
+ toPreviousTarget = new Vector3()
2250
+ toNextTarget = new Vector3()
2251
+ updateCameraTransition = (currentTime) => {
2252
+ if (this.transitioningCameraTarget) {
2253
+ this.toPreviousTarget
2254
+ .copy(this.previousCameraTarget)
2255
+ .sub(this.camera.position)
2256
+ .normalize();
2257
+ this.toNextTarget
2258
+ .copy(this.nextCameraTarget)
2259
+ .sub(this.camera.position)
2260
+ .normalize();
2261
+ const rotationAngle = Math.acos(
2262
+ this.toPreviousTarget.dot(this.toNextTarget)
2263
+ );
2264
+ const rotationSpeed = (rotationAngle / (Math.PI / 3)) * 0.65 + 0.3;
2265
+ const t =
2266
+ (rotationSpeed / rotationAngle) *
2267
+ (currentTime - this.transitioningCameraTargetStartTime);
2268
+ this.tempCameraTarget
2269
+ .copy(this.previousCameraTarget)
2270
+ .lerp(this.nextCameraTarget, t);
2271
+ this.camera.lookAt(this.tempCameraTarget);
2272
+ this.controls.target.copy(this.tempCameraTarget);
2273
+ if (t >= 1.0) {
2274
+ this.transitioningCameraTarget = false;
2275
+ }
2276
+ }
2277
+ }
2278
+
2279
+ updateFocusMarker = (function () {
2280
+ const renderDimensions = new Vector2();
2281
+ let wasTransitioning = false;
2282
+
2283
+ return function (timeDelta) {
2284
+ this.getRenderDimensions(renderDimensions);
2285
+ if (this.transitioningCameraTarget) {
2286
+ this.sceneHelper.setFocusMarkerVisibility(true);
2287
+ const currentFocusMarkerOpacity = Math.max(
2288
+ this.sceneHelper.getFocusMarkerOpacity(),
2289
+ 0.0
2290
+ );
2291
+ let newFocusMarkerOpacity = Math.min(
2292
+ currentFocusMarkerOpacity + FOCUS_MARKER_FADE_IN_SPEED * timeDelta,
2293
+ 1.0
2294
+ );
2295
+ this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity);
2296
+ this.sceneHelper.updateFocusMarker(
2297
+ this.nextCameraTarget,
2298
+ this.camera,
2299
+ renderDimensions
2300
+ );
2301
+ wasTransitioning = true;
2302
+ this.forceRenderNextFrame();
2303
+ } else {
2304
+ let currentFocusMarkerOpacity;
2305
+ if (wasTransitioning) currentFocusMarkerOpacity = 1.0;
2306
+ else
2307
+ currentFocusMarkerOpacity = Math.min(
2308
+ this.sceneHelper.getFocusMarkerOpacity(),
2309
+ 1.0
2310
+ );
2311
+ if (currentFocusMarkerOpacity > 0) {
2312
+ this.sceneHelper.updateFocusMarker(
2313
+ this.nextCameraTarget,
2314
+ this.camera,
2315
+ renderDimensions
2316
+ );
2317
+ let newFocusMarkerOpacity = Math.max(
2318
+ currentFocusMarkerOpacity - FOCUS_MARKER_FADE_OUT_SPEED * timeDelta,
2319
+ 0.0
2320
+ );
2321
+ this.sceneHelper.setFocusMarkerOpacity(newFocusMarkerOpacity);
2322
+ if (newFocusMarkerOpacity === 0.0)
2323
+ this.sceneHelper.setFocusMarkerVisibility(false);
2324
+ }
2325
+ if (currentFocusMarkerOpacity > 0.0) this.forceRenderNextFrame();
2326
+ wasTransitioning = false;
2327
+ }
2328
+ }
2329
+ })()
2330
+
2331
+ updateMeshCursor = (function () {
2332
+ const outHits = [];
2333
+ const renderDimensions = new Vector2();
2334
+
2335
+ return function () {
2336
+ if (this.showMeshCursor) {
2337
+ this.forceRenderNextFrame();
2338
+ this.getRenderDimensions(renderDimensions);
2339
+ outHits.length = 0;
2340
+ this.raycaster.setFromCameraAndScreenPosition(
2341
+ this.camera,
2342
+ this.mousePosition,
2343
+ renderDimensions
2344
+ );
2345
+ this.raycaster.intersectSplatMesh(this.splatMesh, outHits);
2346
+ if (outHits.length > 0) {
2347
+ this.sceneHelper.setMeshCursorVisibility(true);
2348
+ this.sceneHelper.positionAndOrientMeshCursor(
2349
+ outHits[0].origin,
2350
+ this.camera
2351
+ );
2352
+ } else {
2353
+ this.sceneHelper.setMeshCursorVisibility(false);
2354
+ }
2355
+ } else {
2356
+ if (this.sceneHelper.getMeschCursorVisibility())
2357
+ this.forceRenderNextFrame();
2358
+ this.sceneHelper.setMeshCursorVisibility(false);
2359
+ }
2360
+ }
2361
+ })()
2362
+
2363
+ updateInfoPanel = (function () {
2364
+ const renderDimensions = new Vector2();
2365
+
2366
+ return function () {
2367
+ if (!this.showInfo) return
2368
+ const splatCount = this.splatMesh.getSplatCount();
2369
+ this.getRenderDimensions(renderDimensions);
2370
+ const cameraLookAtPosition = this.controls ? this.controls.target : null;
2371
+ const meshCursorPosition = this.showMeshCursor
2372
+ ? this.sceneHelper.meshCursor.position
2373
+ : null;
2374
+ const splatRenderCountPct =
2375
+ splatCount > 0 ? (this.splatRenderCount / splatCount) * 100 : 0;
2376
+ // this.infoPanel.update(
2377
+ // renderDimensions,
2378
+ // this.camera.position,
2379
+ // cameraLookAtPosition,
2380
+ // this.camera.up,
2381
+ // this.camera.isOrthographicCamera,
2382
+ // meshCursorPosition,
2383
+ // this.currentFPS || 'N/A',
2384
+ // splatCount,
2385
+ // this.splatRenderCount,
2386
+ // splatRenderCountPct,
2387
+ // this.lastSortTime,
2388
+ // this.focalAdjustment,
2389
+ // this.splatMesh.getSplatScale(),
2390
+ // this.splatMesh.getPointCloudModeEnabled()
2391
+ // )
2392
+ }
2393
+ })()
2394
+
2395
+ updateControlPlane() {
2396
+ if (this.showControlPlane) {
2397
+ this.sceneHelper.setControlPlaneVisibility(true);
2398
+ this.sceneHelper.positionAndOrientControlPlane(
2399
+ this.controls.target,
2400
+ this.camera.up
2401
+ );
2402
+ } else {
2403
+ this.sceneHelper.setControlPlaneVisibility(false);
2404
+ }
2405
+ }
2406
+
2407
+ mvpMatrix = new Matrix4()
2408
+ cameraPositionArray = []
2409
+ lastSortViewDir = new Vector3(0, 0, -1)
2410
+ sortViewDir = new Vector3(0, 0, -1)
2411
+ lastSortViewPos = new Vector3()
2412
+ sortViewOffset = new Vector3()
2413
+ queuedSorts = []
2414
+
2415
+ partialSorts = [
2416
+ {
2417
+ angleThreshold: 0.55,
2418
+ sortFractions: [0.125, 0.33333, 0.75]
2419
+ },
2420
+ {
2421
+ angleThreshold: 0.65,
2422
+ sortFractions: [0.33333, 0.66667]
2423
+ },
2424
+ {
2425
+ angleThreshold: 0.8,
2426
+ sortFractions: [0.5]
2427
+ }
2428
+ ]
2429
+ runSplatSort = (force = false, forceSortAll = false) => {
2430
+ if (!this.initialized) return Promise.resolve(false)
2431
+ if (this.sortRunning) return Promise.resolve(true)
2432
+ if (this.splatMesh.getSplatCount() <= 0) {
2433
+ this.splatRenderCount = 0;
2434
+ return Promise.resolve(false)
2435
+ }
2436
+
2437
+ let angleDiff = 0;
2438
+ let positionDiff = 0;
2439
+ let needsRefreshForRotation = false;
2440
+ let needsRefreshForPosition = false;
2441
+
2442
+ this.sortViewDir.set(0, 0, -1).applyQuaternion(this.camera.quaternion);
2443
+ angleDiff = this.sortViewDir.dot(this.lastSortViewDir);
2444
+ positionDiff = this.sortViewOffset
2445
+ .copy(this.camera.position)
2446
+ .sub(this.lastSortViewPos)
2447
+ .length();
2448
+
2449
+ if (!force) {
2450
+ if (!this.splatMesh.dynamicMode && this.queuedSorts.length === 0) {
2451
+ if (angleDiff <= 0.99) needsRefreshForRotation = true;
2452
+ if (positionDiff >= 1.0) needsRefreshForPosition = true;
2453
+ if (!needsRefreshForRotation && !needsRefreshForPosition)
2454
+ return Promise.resolve(false)
2455
+ }
2456
+ }
2457
+
2458
+ this.sortRunning = true;
2459
+ let { splatRenderCount, shouldSortAll } = this.gatherSceneNodesForSort();
2460
+ shouldSortAll = shouldSortAll || forceSortAll;
2461
+ this.splatRenderCount = splatRenderCount;
2462
+
2463
+ this.mvpMatrix.copy(this.camera.matrixWorld).invert();
2464
+ const mvpCamera = this.perspectiveCamera || this.camera;
2465
+ this.mvpMatrix.premultiply(mvpCamera.projectionMatrix);
2466
+ if (!this.splatMesh.dynamicMode)
2467
+ this.mvpMatrix.multiply(this.splatMesh.matrixWorld);
2468
+
2469
+ let gpuAcceleratedSortPromise = Promise.resolve(true);
2470
+ if (
2471
+ this.gpuAcceleratedSort &&
2472
+ (this.queuedSorts.length <= 1 || this.queuedSorts.length % 2 === 0)
2473
+ ) {
2474
+ gpuAcceleratedSortPromise = this.splatMesh.computeDistancesOnGPU(
2475
+ this.mvpMatrix,
2476
+ this.sortWorkerPrecomputedDistances
2477
+ );
2478
+ }
2479
+
2480
+ gpuAcceleratedSortPromise.then(() => {
2481
+ if (this.queuedSorts.length === 0) {
2482
+ if (this.splatMesh.dynamicMode || shouldSortAll) {
2483
+ this.queuedSorts.push(this.splatRenderCount);
2484
+ } else {
2485
+ for (let partialSort of this.partialSorts) {
2486
+ if (angleDiff < partialSort.angleThreshold) {
2487
+ for (let sortFraction of partialSort.sortFractions) {
2488
+ this.queuedSorts.push(
2489
+ Math.floor(this.splatRenderCount * sortFraction)
2490
+ );
2491
+ }
2492
+ break
2493
+ }
2494
+ }
2495
+ this.queuedSorts.push(this.splatRenderCount);
2496
+ }
2497
+ }
2498
+ let sortCount = Math.min(this.queuedSorts.shift(), this.splatRenderCount);
2499
+ this.splatSortCount = sortCount;
2500
+
2501
+ this.cameraPositionArray[0] = this.camera.position.x;
2502
+ this.cameraPositionArray[1] = this.camera.position.y;
2503
+ this.cameraPositionArray[2] = this.camera.position.z;
2504
+
2505
+ const sortMessage = {
2506
+ modelViewProj: this.mvpMatrix.elements,
2507
+ cameraPosition: this.cameraPositionArray,
2508
+ splatRenderCount: this.splatRenderCount,
2509
+ splatSortCount: sortCount,
2510
+ usePrecomputedDistances: this.gpuAcceleratedSort
2511
+ };
2512
+ if (this.splatMesh.dynamicMode) {
2513
+ this.splatMesh.fillTransformsArray(this.sortWorkerTransforms);
2514
+ }
2515
+ if (!this.sharedMemoryForWorkers) {
2516
+ sortMessage.indexesToSort = this.sortWorkerIndexesToSort;
2517
+ sortMessage.transforms = this.sortWorkerTransforms;
2518
+ if (this.gpuAcceleratedSort) {
2519
+ sortMessage.precomputedDistances = this.sortWorkerPrecomputedDistances;
2520
+ }
2521
+ }
2522
+
2523
+ this.sortPromise = new Promise((resolve) => {
2524
+ this.sortPromiseResolver = resolve;
2525
+ });
2526
+
2527
+ if (this.preSortMessages.length > 0) {
2528
+ this.preSortMessages.forEach((message) => {
2529
+ this.sortWorker.postMessage(message);
2530
+ });
2531
+ this.preSortMessages = [];
2532
+ }
2533
+ this.sortWorker.postMessage({
2534
+ sort: sortMessage
2535
+ });
2536
+
2537
+ if (this.queuedSorts.length === 0) {
2538
+ this.lastSortViewPos.copy(this.camera.position);
2539
+ this.lastSortViewDir.copy(this.sortViewDir);
2540
+ }
2541
+
2542
+ return true
2543
+ });
2544
+
2545
+ return gpuAcceleratedSortPromise
2546
+ }
2547
+
2548
+ /**
2549
+ * Determine which splats to render by checking which are inside or close to the view frustum
2550
+ */
2551
+ gatherSceneNodesForSort = (function () {
2552
+ const nodeRenderList = [];
2553
+ let allSplatsSortBuffer = null;
2554
+ const tempVectorYZ = new Vector3();
2555
+ const tempVectorXZ = new Vector3();
2556
+ const tempVector = new Vector3();
2557
+ const modelView = new Matrix4();
2558
+ const baseModelView = new Matrix4();
2559
+ const sceneTransform = new Matrix4();
2560
+ const renderDimensions = new Vector3();
2561
+ const forward = new Vector3(0, 0, -1);
2562
+
2563
+ const tempMax = new Vector3();
2564
+ const nodeSize = (node) => {
2565
+ return tempMax.copy(node.max).sub(node.min).length()
2566
+ };
2567
+
2568
+ return function (gatherAllNodes = false) {
2569
+ this.getRenderDimensions(renderDimensions);
2570
+ const cameraFocalLength =
2571
+ renderDimensions.y /
2572
+ 2.0 /
2573
+ Math.tan((this.camera.fov / 2.0) * MathUtils.DEG2RAD);
2574
+ const fovXOver2 = Math.atan(renderDimensions.x / 2.0 / cameraFocalLength);
2575
+ const fovYOver2 = Math.atan(renderDimensions.y / 2.0 / cameraFocalLength);
2576
+ const cosFovXOver2 = Math.cos(fovXOver2);
2577
+ const cosFovYOver2 = Math.cos(fovYOver2);
2578
+
2579
+ const splatTree = this.splatMesh.getSplatTree();
2580
+
2581
+ if (splatTree) {
2582
+ baseModelView.copy(this.camera.matrixWorld).invert();
2583
+ if (!this.splatMesh.dynamicMode)
2584
+ baseModelView.multiply(this.splatMesh.matrixWorld);
2585
+
2586
+ let nodeRenderCount = 0;
2587
+ let splatRenderCount = 0;
2588
+
2589
+ for (let s = 0; s < splatTree.subTrees.length; s++) {
2590
+ const subTree = splatTree.subTrees[s];
2591
+ modelView.copy(baseModelView);
2592
+ if (this.splatMesh.dynamicMode) {
2593
+ this.splatMesh.getSceneTransform(s, sceneTransform);
2594
+ modelView.multiply(sceneTransform);
2595
+ }
2596
+ const nodeCount = subTree.nodesWithIndexes.length;
2597
+ for (let i = 0; i < nodeCount; i++) {
2598
+ const node = subTree.nodesWithIndexes[i];
2599
+ if (
2600
+ !node.data ||
2601
+ !node.data.indexes ||
2602
+ node.data.indexes.length === 0
2603
+ )
2604
+ continue
2605
+ tempVector.copy(node.center).applyMatrix4(modelView);
2606
+
2607
+ const distanceToNode = tempVector.length();
2608
+ tempVector.normalize();
2609
+
2610
+ tempVectorYZ.copy(tempVector).setX(0).normalize();
2611
+ tempVectorXZ.copy(tempVector).setY(0).normalize();
2612
+
2613
+ const cameraAngleXZDot = forward.dot(tempVectorXZ);
2614
+ const cameraAngleYZDot = forward.dot(tempVectorYZ);
2615
+
2616
+ const ns = nodeSize(node);
2617
+ const outOfFovY = cameraAngleYZDot < cosFovYOver2 - 0.6;
2618
+ const outOfFovX = cameraAngleXZDot < cosFovXOver2 - 0.6;
2619
+ if (
2620
+ !gatherAllNodes &&
2621
+ (outOfFovX || outOfFovY) &&
2622
+ distanceToNode > ns
2623
+ ) {
2624
+ continue
2625
+ }
2626
+ splatRenderCount += node.data.indexes.length;
2627
+ nodeRenderList[nodeRenderCount] = node;
2628
+ node.data.distanceToNode = distanceToNode;
2629
+ nodeRenderCount++;
2630
+ }
2631
+ }
2632
+
2633
+ nodeRenderList.length = nodeRenderCount;
2634
+ nodeRenderList.sort((a, b) => {
2635
+ if (a.data.distanceToNode < b.data.distanceToNode) return -1
2636
+ else return 1
2637
+ });
2638
+
2639
+ let currentByteOffset = splatRenderCount * Constants.BytesPerInt;
2640
+ for (let i = 0; i < nodeRenderCount; i++) {
2641
+ const node = nodeRenderList[i];
2642
+ const windowSizeInts = node.data.indexes.length;
2643
+ const windowSizeBytes = windowSizeInts * Constants.BytesPerInt;
2644
+ let destView = new Uint32Array(
2645
+ this.sortWorkerIndexesToSort.buffer,
2646
+ currentByteOffset - windowSizeBytes,
2647
+ windowSizeInts
2648
+ );
2649
+ destView.set(node.data.indexes);
2650
+ currentByteOffset -= windowSizeBytes;
2651
+ }
2652
+
2653
+ return {
2654
+ splatRenderCount: splatRenderCount,
2655
+ shouldSortAll: false
2656
+ }
2657
+ } else {
2658
+ const totalSplatCount = this.splatMesh.getSplatCount();
2659
+ if (
2660
+ !allSplatsSortBuffer ||
2661
+ allSplatsSortBuffer.length !== totalSplatCount
2662
+ ) {
2663
+ allSplatsSortBuffer = new Uint32Array(totalSplatCount);
2664
+ for (let i = 0; i < totalSplatCount; i++) {
2665
+ allSplatsSortBuffer[i] = i;
2666
+ }
2667
+ }
2668
+ this.sortWorkerIndexesToSort.set(allSplatsSortBuffer);
2669
+ return {
2670
+ splatRenderCount: totalSplatCount,
2671
+ shouldSortAll: true
2672
+ }
2673
+ }
2674
+ }
2675
+ })()
2676
+
2677
+ getSplatMesh() {
2678
+ return this.splatMesh
2679
+ }
2680
+
2681
+ /**
2682
+ * Get a reference to a splat scene.
2683
+ * @param {number} sceneIndex The index of the scene to which the reference will be returned
2684
+ * @return {SplatScene}
2685
+ */
2686
+ getSplatScene(sceneIndex) {
2687
+ return this.splatMesh.getScene(sceneIndex)
2688
+ }
2689
+
2690
+ getSceneCount() {
2691
+ return this.splatMesh.getSceneCount()
2692
+ }
2693
+
2694
+ isMobile() {
2695
+ return navigator.userAgent.includes('Mobi')
2696
+ }
2697
+
2698
+ createBonesFromJson(bonesJson) {
2699
+ const bones = [];
2700
+
2701
+ function createBoneRecursive(jsonBone, parent = null) {
2702
+ const bone = new Bone();
2703
+ bone.name = jsonBone.name;
2704
+ if (parent) {
2705
+ parent.add(bone);
2706
+ }
2707
+ bone.position.set(...jsonBone.position);
2708
+ bones.push(bone);
2709
+
2710
+ if (jsonBone.children) {
2711
+ jsonBone.children.forEach((childJsonBone) =>
2712
+ createBoneRecursive(childJsonBone, bone)
2713
+ );
2714
+ }
2715
+ return bone
2716
+ }
2717
+
2718
+ bonesJson.forEach((boneJson) => createBoneRecursive(boneJson));
2719
+
2720
+ return bones
2721
+ }
2722
+
2723
+ updateMorphTarget(inputMesh) {
2724
+ this.avatarMesh = inputMesh;
2725
+ this.splatMesh.flameModel = inputMesh;
2726
+ this.splatMesh.useFlameModel = this.useFlame;
2727
+ if(this.useFlame == true) {
2728
+ // const skinData = {
2729
+ // bones: [
2730
+ // {
2731
+ // "name": "root",
2732
+ // "position": [-1.7149e-04, -1.4252e-01, -8.2541e-02],
2733
+ // "children": [
2734
+ // {
2735
+ // "name": "neck",
2736
+ // "position": [-5.6988e-05, -1.6069e-02, -5.7859e-02],
2737
+
2738
+ // "children": [
2739
+ // {
2740
+ // "name": "jaw",
2741
+ // "position": [7.4429e-04, -8.7249e-03, -5.3760e-02]
2742
+
2743
+ // },
2744
+ // {
2745
+ // "name": "leftEye",
2746
+ // "position": [ 3.0240e-02, 2.3092e-02, 2.2900e-02]
2747
+ // },
2748
+ // {
2749
+ // "name": "rightEye",
2750
+ // "position": [-3.0296e-02, 2.3675e-02, 2.1837e-02]
2751
+ // }
2752
+ // ]
2753
+ // }
2754
+ // ]
2755
+ // }
2756
+ // ]
2757
+ // };
2758
+
2759
+ // this.bones = this.createBonesFromJson(skinData.bones);
2760
+ this.bones = this.createBonesFromJson(this.bone_tree["bones"]);
2761
+
2762
+ const bonesPosReserve = [new Vector3(this.bones[0].position.x, this.bones[0].position.y, this.bones[0].position.z),
2763
+ new Vector3(this.bones[1].position.x, this.bones[1].position.y, this.bones[1].position.z),
2764
+ new Vector3(this.bones[2].position.x, this.bones[2].position.y, this.bones[2].position.z),
2765
+ new Vector3(this.bones[3].position.x, this.bones[3].position.y, this.bones[3].position.z),
2766
+ new Vector3(this.bones[4].position.x, this.bones[4].position.y, this.bones[4].position.z)
2767
+ ];
2768
+ this.bones[1].position.copy(new Vector3(bonesPosReserve[1].x - bonesPosReserve[0].x, bonesPosReserve[1].y - bonesPosReserve[0].y, bonesPosReserve[1].z - bonesPosReserve[0].z));
2769
+ this.bones[2].position.copy(new Vector3(bonesPosReserve[2].x - bonesPosReserve[1].x, bonesPosReserve[2].y - bonesPosReserve[1].y, bonesPosReserve[2].z - bonesPosReserve[1].z));
2770
+ this.bones[3].position.copy(new Vector3(bonesPosReserve[3].x - bonesPosReserve[1].x, bonesPosReserve[3].y - bonesPosReserve[1].y, bonesPosReserve[3].z - bonesPosReserve[1].z));
2771
+ this.bones[4].position.copy(new Vector3(bonesPosReserve[4].x - bonesPosReserve[1].x, bonesPosReserve[4].y - bonesPosReserve[1].y, bonesPosReserve[4].z - bonesPosReserve[1].z));
2772
+
2773
+ this.bones[0].updateMatrixWorld(true);
2774
+ const boneInverses = [this.bones[0].matrixWorld.clone().invert(),
2775
+ this.bones[1].matrixWorld.clone().invert(),
2776
+ this.bones[2].matrixWorld.clone().invert(),
2777
+ this.bones[3].matrixWorld.clone().invert(),
2778
+ this.bones[4].matrixWorld.clone().invert()];
2779
+
2780
+ this.skeleton = new Skeleton(this.bones, boneInverses);
2781
+ }
2782
+
2783
+ this.runMorphUpdate();
2784
+ this.splatMesh.gaussianSplatCount = this.gaussianSplatCount;
2785
+ }
2786
+
2787
+ updatedBoneMatrices(boneNum){
2788
+ let updatedBoneMatrices = [];
2789
+ for (let j = 0; j < boneNum; j++) {
2790
+ let boneMatrix;
2791
+ boneMatrix = this.skeleton.bones[j].matrixWorld.clone().multiply(this.skeleton.boneInverses[j].clone());
2792
+
2793
+ function addMatrixToArray(matrix, array) {
2794
+ let elements = matrix.elements;
2795
+ for (let i = 0; i < elements.length; i++) {
2796
+ array.push(elements[i]);
2797
+ }
2798
+ }
2799
+
2800
+ addMatrixToArray(boneMatrix, updatedBoneMatrices);
2801
+ }
2802
+ let bonesMatrix = new Float32Array(updatedBoneMatrices);
2803
+ return bonesMatrix;
2804
+ }
2805
+ runMorphUpdate() {
2806
+ this.gaussianSplatCount = this.avatarMesh.geometry.attributes.position.count;
2807
+ var morphedMesh = new Float32Array(
2808
+ this.avatarMesh.geometry.attributes.position.array
2809
+ );
2810
+ const numBones = 5;
2811
+ this.splatMesh.bonesNum = numBones;
2812
+ if (this.useFlame == false)
2813
+ {
2814
+ this.skinModel.skeleton.update();
2815
+ this.boneRoot.updateMatrixWorld(true);
2816
+ if (this.splatMesh.geometry.getAttribute('splatIndex') && this.setSkinAttibutes === false) {
2817
+
2818
+ this.setSkinAttibutes = true;
2819
+ const geometry = this.splatMesh.geometry;
2820
+
2821
+ const skinIndexSource = this.skinModel.geometry.attributes.skinIndex;
2822
+ const skinWeightSource = this.skinModel.geometry.attributes.skinWeight;
2823
+
2824
+ const newSkinIndex = new InstancedBufferAttribute(
2825
+ new skinIndexSource.array.constructor(skinIndexSource.array),
2826
+ 4,
2827
+ skinIndexSource.normalized,
2828
+ 1
2829
+ );
2830
+
2831
+ const newSkinWeight = new InstancedBufferAttribute(
2832
+ new skinWeightSource.array.constructor(skinWeightSource.array),
2833
+ 4,
2834
+ skinWeightSource.normalized,
2835
+ 1
2836
+ );
2837
+ newSkinIndex.setUsage(DynamicDrawUsage);
2838
+ newSkinWeight.setUsage(DynamicDrawUsage);
2839
+ geometry.setAttribute('skinIndex', newSkinIndex);
2840
+ geometry.setAttribute('skinWeight', newSkinWeight);
2841
+ }
2842
+ } else {
2843
+ this.updateFlameBones();
2844
+ }
2845
+
2846
+ this.splatMesh.morphedMesh = morphedMesh;
2847
+
2848
+ let splatNum = this.splatMesh.morphedMesh.length / 3;
2849
+ if (this.splatMesh.splatDataTextures['flameModel'] != undefined) {
2850
+ this.splatMesh.updateTetureAfterBSAndSkeleton(0, splatNum - 1, this.useFlame);
2851
+ }
2852
+ }
2853
+
2854
+ updateFlameBones(){
2855
+ this.splatMesh.bsWeight = this.flame_params['expr'][this.frame];
2856
+
2857
+ function setBoneRotationAndMatrix(bone, angles, isQuat = false) {
2858
+ let quaternion;
2859
+ if(isQuat == true) {
2860
+ quaternion = new Quaternion(angles[0], angles[1], angles[2], angles[3]);
2861
+ } else {
2862
+ const value = new Vector3(angles[0], angles[1], angles[2]);
2863
+ const angleInRadians = value.length();
2864
+ const axis = value.normalize();
2865
+ quaternion = new Quaternion().setFromAxisAngle(axis, angleInRadians);
2866
+ }
2867
+ bone.quaternion.copy(quaternion);
2868
+ bone.updateMatrixWorld(true);
2869
+ }
2870
+
2871
+ let angle = this.flame_params['rotation'][this.frame];
2872
+
2873
+ setBoneRotationAndMatrix(this.skeleton.bones[0], angle);
2874
+
2875
+ angle = this.flame_params['neck_pose'][this.frame];
2876
+ setBoneRotationAndMatrix(this.skeleton.bones[1], angle);
2877
+
2878
+ angle = this.flame_params['jaw_pose'][this.frame];
2879
+ setBoneRotationAndMatrix(this.skeleton.bones[2], angle);
2880
+
2881
+ angle = this.flame_params['eyes_pose'][this.frame];
2882
+ setBoneRotationAndMatrix(this.skeleton.bones[3], angle);
2883
+
2884
+ setBoneRotationAndMatrix(this.skeleton.bones[4], [angle[3], angle[4], angle[5]]);
2885
+
2886
+ // update skeleton
2887
+ this.skeleton.update();
2888
+
2889
+ const numBones = 5;
2890
+ const bonesMatrix = this.updatedBoneMatrices(numBones);
2891
+ this.splatMesh.bonesMatrix = bonesMatrix;
2892
+ this.splatMesh.bonesNum = numBones;
2893
+ this.splatMesh.bonesWeight = this.lbs_weight_80k;
2894
+ }
2895
+ }