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