@myned-ai/gsplat-flame-avatar-renderer 1.0.6 → 1.0.7

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