@myned-ai/gsplat-flame-avatar-renderer 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/gsplat-flame-avatar-renderer.cjs.js +38 -33
- package/dist/gsplat-flame-avatar-renderer.cjs.min.js +1 -1
- package/dist/gsplat-flame-avatar-renderer.cjs.min.js.map +1 -1
- package/dist/gsplat-flame-avatar-renderer.esm.js +38 -33
- package/dist/gsplat-flame-avatar-renderer.esm.min.js +1 -1
- package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +1 -1
- package/package.json +5 -2
- 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 -2627
- package/src/core/SplatScene.js +0 -43
- package/src/core/SplatTree.js +0 -200
- package/src/core/Viewer.js +0 -2746
- 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/errors/ApplicationError.js +0 -185
- package/src/errors/index.js +0 -17
- package/src/flame/FlameAnimator.js +0 -496
- 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 -519
- 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 -1068
- package/src/materials/SplatMaterial2D.js +0 -358
- package/src/materials/SplatMaterial3D.js +0 -323
- 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 -577
- package/src/renderer/AppConstants.js +0 -101
- package/src/renderer/GaussianSplatRenderer.js +0 -1146
- package/src/renderer/index.js +0 -24
- package/src/utils/BlobUrlManager.js +0 -294
- package/src/utils/EventEmitter.js +0 -349
- package/src/utils/LoaderUtils.js +0 -66
- package/src/utils/Logger.js +0 -171
- package/src/utils/ObjectPool.js +0 -248
- package/src/utils/RenderLoop.js +0 -306
- package/src/utils/Util.js +0 -416
- package/src/utils/ValidationUtils.js +0 -331
- package/src/utils/index.js +0 -18
- package/src/worker/SortWorker.js +0 -284
- package/src/worker/index.js +0 -8
package/src/utils/RenderLoop.js
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RenderLoop - Frame-independent animation loop with budget management
|
|
3
|
-
*
|
|
4
|
-
* Provides a robust requestAnimationFrame loop with:
|
|
5
|
-
* - Delta time calculation for frame-independent updates
|
|
6
|
-
* - Frame budget management to prevent frame drops
|
|
7
|
-
* - Deferred task execution
|
|
8
|
-
* - Performance monitoring
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { getLogger } from './Logger.js';
|
|
12
|
-
|
|
13
|
-
const logger = getLogger('RenderLoop');
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* RenderLoop - Manages animation frame loop
|
|
17
|
-
*/
|
|
18
|
-
export class RenderLoop {
|
|
19
|
-
/**
|
|
20
|
-
* Create a RenderLoop
|
|
21
|
-
* @param {Function} updateFn - Update function called each frame with deltaTime
|
|
22
|
-
* @param {Function} renderFn - Render function called each frame
|
|
23
|
-
* @param {object} [options] - Configuration options
|
|
24
|
-
* @param {number} [options.targetFps=60] - Target frames per second
|
|
25
|
-
* @param {number} [options.maxDeltaTime=0.1] - Maximum delta time in seconds (prevents spiral of death)
|
|
26
|
-
*/
|
|
27
|
-
constructor(updateFn, renderFn, options = {}) {
|
|
28
|
-
this._update = updateFn;
|
|
29
|
-
this._render = renderFn;
|
|
30
|
-
|
|
31
|
-
this._targetFps = options.targetFps || 60;
|
|
32
|
-
this._maxDeltaTime = options.maxDeltaTime || 0.1; // 100ms max
|
|
33
|
-
this._frameBudget = 1000 / this._targetFps; // ms per frame
|
|
34
|
-
|
|
35
|
-
this._running = false;
|
|
36
|
-
this._rafId = null;
|
|
37
|
-
this._lastTime = 0;
|
|
38
|
-
this._frameCount = 0;
|
|
39
|
-
this._deferredTasks = [];
|
|
40
|
-
|
|
41
|
-
// Performance tracking
|
|
42
|
-
this._fpsHistory = [];
|
|
43
|
-
this._fpsHistorySize = 60; // Track last 60 frames
|
|
44
|
-
this._lastFpsUpdate = 0;
|
|
45
|
-
this._currentFps = 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Start the render loop
|
|
50
|
-
*/
|
|
51
|
-
start() {
|
|
52
|
-
if (this._running) {
|
|
53
|
-
logger.warn('RenderLoop already running');
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
this._running = true;
|
|
58
|
-
this._lastTime = performance.now();
|
|
59
|
-
this._frameCount = 0;
|
|
60
|
-
logger.info('RenderLoop started');
|
|
61
|
-
|
|
62
|
-
this._tick();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Stop the render loop
|
|
67
|
-
*/
|
|
68
|
-
stop() {
|
|
69
|
-
if (!this._running) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this._running = false;
|
|
74
|
-
|
|
75
|
-
if (this._rafId !== null) {
|
|
76
|
-
cancelAnimationFrame(this._rafId);
|
|
77
|
-
this._rafId = null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
logger.info(`RenderLoop stopped after ${this._frameCount} frames`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Main loop tick
|
|
85
|
-
* @private
|
|
86
|
-
*/
|
|
87
|
-
_tick = () => {
|
|
88
|
-
if (!this._running) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const frameStart = performance.now();
|
|
93
|
-
const rawDeltaTime = (frameStart - this._lastTime) / 1000; // Convert to seconds
|
|
94
|
-
|
|
95
|
-
// Clamp delta time to prevent spiral of death
|
|
96
|
-
const deltaTime = Math.min(rawDeltaTime, this._maxDeltaTime);
|
|
97
|
-
|
|
98
|
-
this._lastTime = frameStart;
|
|
99
|
-
this._frameCount++;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
// Update logic
|
|
103
|
-
this._update(deltaTime);
|
|
104
|
-
|
|
105
|
-
// Render
|
|
106
|
-
this._render();
|
|
107
|
-
|
|
108
|
-
// Process deferred tasks if time permits
|
|
109
|
-
const frameElapsed = performance.now() - frameStart;
|
|
110
|
-
const remainingTime = this._frameBudget - frameElapsed;
|
|
111
|
-
|
|
112
|
-
if (remainingTime > 1 && this._deferredTasks.length > 0) {
|
|
113
|
-
this._processDeferredTasks(remainingTime - 1); // Leave 1ms margin
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Update FPS tracking
|
|
117
|
-
this._updateFpsTracking(performance.now() - frameStart);
|
|
118
|
-
|
|
119
|
-
} catch (error) {
|
|
120
|
-
logger.error('Error in render loop:', error);
|
|
121
|
-
// Continue loop despite error
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Schedule next frame
|
|
125
|
-
this._rafId = requestAnimationFrame(this._tick);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Update FPS tracking
|
|
130
|
-
* @private
|
|
131
|
-
* @param {number} frameTime - Time taken for this frame in ms
|
|
132
|
-
*/
|
|
133
|
-
_updateFpsTracking(frameTime) {
|
|
134
|
-
this._fpsHistory.push(1000 / frameTime);
|
|
135
|
-
|
|
136
|
-
if (this._fpsHistory.length > this._fpsHistorySize) {
|
|
137
|
-
this._fpsHistory.shift();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Update FPS every second
|
|
141
|
-
const now = performance.now();
|
|
142
|
-
if (now - this._lastFpsUpdate > 1000) {
|
|
143
|
-
this._currentFps = this._fpsHistory.reduce((a, b) => a + b, 0) / this._fpsHistory.length;
|
|
144
|
-
this._lastFpsUpdate = now;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Process deferred tasks within time budget
|
|
150
|
-
* @private
|
|
151
|
-
* @param {number} maxTime - Maximum time in ms to spend on tasks
|
|
152
|
-
*/
|
|
153
|
-
_processDeferredTasks(maxTime) {
|
|
154
|
-
const startTime = performance.now();
|
|
155
|
-
|
|
156
|
-
while (this._deferredTasks.length > 0) {
|
|
157
|
-
if (performance.now() - startTime >= maxTime) {
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const task = this._deferredTasks.shift();
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
task.fn();
|
|
165
|
-
} catch (error) {
|
|
166
|
-
logger.error(`Error in deferred task: ${task.label}`, error);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Execute task if within frame budget, otherwise defer
|
|
173
|
-
*
|
|
174
|
-
* @param {Function} task - Task function to execute
|
|
175
|
-
* @param {number} [priority=0] - Task priority (higher = more important)
|
|
176
|
-
* @param {string} [label=''] - Task label for debugging
|
|
177
|
-
*/
|
|
178
|
-
executeOrDefer(task, priority = 0, label = '') {
|
|
179
|
-
const frameElapsed = performance.now() - this._lastTime;
|
|
180
|
-
|
|
181
|
-
if (frameElapsed < this._frameBudget * 0.8) {
|
|
182
|
-
// Within budget, execute now
|
|
183
|
-
task();
|
|
184
|
-
} else {
|
|
185
|
-
// Over budget, defer
|
|
186
|
-
this._deferredTasks.push({ fn: task, priority, label });
|
|
187
|
-
|
|
188
|
-
// Sort by priority (higher first)
|
|
189
|
-
this._deferredTasks.sort((a, b) => b.priority - a.priority);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Get current FPS
|
|
195
|
-
* @returns {number} Average FPS over recent frames
|
|
196
|
-
*/
|
|
197
|
-
getFps() {
|
|
198
|
-
return Math.round(this._currentFps);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Get performance stats
|
|
203
|
-
* @returns {object} Performance statistics
|
|
204
|
-
*/
|
|
205
|
-
getStats() {
|
|
206
|
-
return {
|
|
207
|
-
fps: this.getFps(),
|
|
208
|
-
frameCount: this._frameCount,
|
|
209
|
-
deferredTaskCount: this._deferredTasks.length,
|
|
210
|
-
running: this._running
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Check if loop is running
|
|
216
|
-
* @returns {boolean} True if running
|
|
217
|
-
*/
|
|
218
|
-
isRunning() {
|
|
219
|
-
return this._running;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Get frame count
|
|
224
|
-
* @returns {number} Total frames rendered
|
|
225
|
-
*/
|
|
226
|
-
getFrameCount() {
|
|
227
|
-
return this._frameCount;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Clear all deferred tasks
|
|
232
|
-
*/
|
|
233
|
-
clearDeferredTasks() {
|
|
234
|
-
this._deferredTasks.length = 0;
|
|
235
|
-
logger.debug('Cleared all deferred tasks');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* FrameBudgetMonitor - Monitors and alerts on frame budget violations
|
|
241
|
-
*/
|
|
242
|
-
export class FrameBudgetMonitor {
|
|
243
|
-
/**
|
|
244
|
-
* Create a FrameBudgetMonitor
|
|
245
|
-
* @param {number} [targetFps=60] - Target FPS
|
|
246
|
-
* @param {Function} [onViolation] - Callback when budget is violated
|
|
247
|
-
*/
|
|
248
|
-
constructor(targetFps = 60, onViolation = null) {
|
|
249
|
-
this._targetFps = targetFps;
|
|
250
|
-
this._frameBudget = 1000 / targetFps;
|
|
251
|
-
this._onViolation = onViolation;
|
|
252
|
-
this._violations = 0;
|
|
253
|
-
this._frameStart = 0;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Mark start of frame
|
|
258
|
-
*/
|
|
259
|
-
startFrame() {
|
|
260
|
-
this._frameStart = performance.now();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Check if frame is within budget
|
|
265
|
-
* @param {string} [location] - Location identifier for debugging
|
|
266
|
-
* @returns {boolean} True if within budget
|
|
267
|
-
*/
|
|
268
|
-
checkBudget(location = '') {
|
|
269
|
-
const elapsed = performance.now() - this._frameStart;
|
|
270
|
-
const withinBudget = elapsed < this._frameBudget;
|
|
271
|
-
|
|
272
|
-
if (!withinBudget) {
|
|
273
|
-
this._violations++;
|
|
274
|
-
|
|
275
|
-
if (this._onViolation) {
|
|
276
|
-
this._onViolation({
|
|
277
|
-
location,
|
|
278
|
-
elapsed,
|
|
279
|
-
budget: this._frameBudget,
|
|
280
|
-
overrun: elapsed - this._frameBudget
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
logger.warn(`Frame budget violation at ${location}: ${elapsed.toFixed(2)}ms / ${this._frameBudget.toFixed(2)}ms`);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return withinBudget;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Get violation count
|
|
292
|
-
* @returns {number} Total violations
|
|
293
|
-
*/
|
|
294
|
-
getViolationCount() {
|
|
295
|
-
return this._violations;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Reset violation count
|
|
300
|
-
*/
|
|
301
|
-
resetViolations() {
|
|
302
|
-
this._violations = 0;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export default RenderLoop;
|
package/src/utils/Util.js
DELETED
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core utility functions for Gaussian Splat rendering
|
|
3
|
-
*
|
|
4
|
-
* Derived from @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
-
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
-
*
|
|
7
|
-
* Import paths adjusted for gsplat-flame-avatar package structure.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { DataUtils } from 'three';
|
|
11
|
-
import { NetworkError } from '../errors/index.js';
|
|
12
|
-
import { getLogger } from './Logger.js';
|
|
13
|
-
|
|
14
|
-
const logger = getLogger('Util');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Custom error for aborted operations
|
|
18
|
-
* @deprecated Use NetworkError instead for consistency
|
|
19
|
-
*/
|
|
20
|
-
export class AbortedPromiseError extends Error {
|
|
21
|
-
constructor(msg) {
|
|
22
|
-
super(msg);
|
|
23
|
-
this.name = 'AbortedPromiseError';
|
|
24
|
-
this.code = 'ABORTED';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Fetch with progress tracking using standard AbortController
|
|
30
|
-
*
|
|
31
|
-
* @param {string} path - URL to fetch
|
|
32
|
-
* @param {Function} [onProgress] - Progress callback (percent, percentLabel, chunk, fileSize)
|
|
33
|
-
* @param {boolean} [saveChunks=true] - Whether to save and return downloaded chunks
|
|
34
|
-
* @param {object} [headers] - Optional HTTP headers
|
|
35
|
-
* @returns {Promise<ArrayBuffer>} Promise that resolves with downloaded data
|
|
36
|
-
* @throws {NetworkError} If fetch fails or is aborted
|
|
37
|
-
*/
|
|
38
|
-
export const fetchWithProgress = function(path, onProgress, saveChunks = true, headers) {
|
|
39
|
-
|
|
40
|
-
const abortController = new AbortController();
|
|
41
|
-
const signal = abortController.signal;
|
|
42
|
-
let aborted = false;
|
|
43
|
-
|
|
44
|
-
let onProgressCalledAtComplete = false;
|
|
45
|
-
const localOnProgress = (percent, percentLabel, chunk, fileSize) => {
|
|
46
|
-
if (onProgress && !onProgressCalledAtComplete) {
|
|
47
|
-
try {
|
|
48
|
-
onProgress(percent, percentLabel, chunk, fileSize);
|
|
49
|
-
if (percent === 100) {
|
|
50
|
-
onProgressCalledAtComplete = true;
|
|
51
|
-
}
|
|
52
|
-
} catch (error) {
|
|
53
|
-
// Don't let progress callback errors break the download
|
|
54
|
-
logger.error('Error in progress callback:', error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const promise = new Promise((resolve, reject) => {
|
|
60
|
-
const fetchOptions = { signal };
|
|
61
|
-
if (headers) fetchOptions.headers = headers;
|
|
62
|
-
|
|
63
|
-
fetch(path, fetchOptions)
|
|
64
|
-
.then(async (data) => {
|
|
65
|
-
// Handle HTTP error responses
|
|
66
|
-
if (!data.ok) {
|
|
67
|
-
let errorText = '';
|
|
68
|
-
try {
|
|
69
|
-
errorText = await data.text();
|
|
70
|
-
} catch {
|
|
71
|
-
// Ignore if we can't read error text
|
|
72
|
-
}
|
|
73
|
-
reject(new NetworkError(
|
|
74
|
-
`Fetch failed: ${data.statusText}${errorText ? ' - ' + errorText : ''}`,
|
|
75
|
-
data.status
|
|
76
|
-
));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const reader = data.body?.getReader();
|
|
81
|
-
if (!reader) {
|
|
82
|
-
reject(new NetworkError('Response body is not readable', 0));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let bytesDownloaded = 0;
|
|
87
|
-
const _fileSize = data.headers.get('Content-Length');
|
|
88
|
-
const fileSize = _fileSize ? parseInt(_fileSize, 10) : undefined;
|
|
89
|
-
|
|
90
|
-
const chunks = [];
|
|
91
|
-
|
|
92
|
-
while (!aborted) {
|
|
93
|
-
try {
|
|
94
|
-
const { value: chunk, done } = await reader.read();
|
|
95
|
-
if (done) {
|
|
96
|
-
localOnProgress(100, '100%', chunk, fileSize);
|
|
97
|
-
if (saveChunks) {
|
|
98
|
-
const buffer = new Blob(chunks).arrayBuffer();
|
|
99
|
-
resolve(buffer);
|
|
100
|
-
} else {
|
|
101
|
-
resolve();
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
bytesDownloaded += chunk.length;
|
|
106
|
-
let percent;
|
|
107
|
-
let percentLabel;
|
|
108
|
-
if (fileSize !== undefined) {
|
|
109
|
-
percent = (bytesDownloaded / fileSize) * 100;
|
|
110
|
-
percentLabel = `${percent.toFixed(2)}%`;
|
|
111
|
-
}
|
|
112
|
-
if (saveChunks) {
|
|
113
|
-
chunks.push(chunk);
|
|
114
|
-
}
|
|
115
|
-
localOnProgress(percent, percentLabel, chunk, fileSize);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
reject(new NetworkError(
|
|
118
|
-
`Error reading response stream: ${error.message}`,
|
|
119
|
-
0,
|
|
120
|
-
error
|
|
121
|
-
));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
.catch((error) => {
|
|
127
|
-
// Don't wrap if already a NetworkError
|
|
128
|
-
if (error instanceof NetworkError) {
|
|
129
|
-
reject(error);
|
|
130
|
-
} else if (error.name === 'AbortError') {
|
|
131
|
-
reject(new NetworkError('Fetch aborted by user', 0, error));
|
|
132
|
-
} else {
|
|
133
|
-
reject(new NetworkError(
|
|
134
|
-
`Fetch failed: ${error.message || 'Unknown error'}`,
|
|
135
|
-
0,
|
|
136
|
-
error
|
|
137
|
-
));
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Attach abort functionality to the promise
|
|
143
|
-
promise.abort = (reason) => {
|
|
144
|
-
aborted = true;
|
|
145
|
-
abortController.abort(reason);
|
|
146
|
-
};
|
|
147
|
-
promise.abortController = abortController;
|
|
148
|
-
|
|
149
|
-
return promise;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// Clamp value between min and max
|
|
153
|
-
export const clamp = function(val, min, max) {
|
|
154
|
-
return Math.max(Math.min(val, max), min);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Get current time in seconds
|
|
158
|
-
export const getCurrentTime = function() {
|
|
159
|
-
return performance.now() / 1000;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// Dispose all meshes in a scene graph
|
|
163
|
-
export const disposeAllMeshes = (object3D) => {
|
|
164
|
-
if (object3D.geometry) {
|
|
165
|
-
object3D.geometry.dispose();
|
|
166
|
-
object3D.geometry = null;
|
|
167
|
-
}
|
|
168
|
-
if (object3D.material) {
|
|
169
|
-
object3D.material.dispose();
|
|
170
|
-
object3D.material = null;
|
|
171
|
-
}
|
|
172
|
-
if (object3D.children) {
|
|
173
|
-
for (let child of object3D.children) {
|
|
174
|
-
disposeAllMeshes(child);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Delayed execution helper
|
|
180
|
-
export const delayedExecute = (func, fast) => {
|
|
181
|
-
return new Promise((resolve) => {
|
|
182
|
-
window.setTimeout(() => {
|
|
183
|
-
resolve(func());
|
|
184
|
-
}, fast ? 1 : 50);
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// Get spherical harmonics component count for degree
|
|
189
|
-
export const getSphericalHarmonicsComponentCountForDegree = (sphericalHarmonicsDegree = 0) => {
|
|
190
|
-
switch (sphericalHarmonicsDegree) {
|
|
191
|
-
case 1:
|
|
192
|
-
return 9;
|
|
193
|
-
case 2:
|
|
194
|
-
return 24;
|
|
195
|
-
}
|
|
196
|
-
return 0;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Create native promise with extracted components
|
|
200
|
-
export const nativePromiseWithExtractedComponents = () => {
|
|
201
|
-
let resolver;
|
|
202
|
-
let rejecter;
|
|
203
|
-
const promise = new Promise((resolve, reject) => {
|
|
204
|
-
resolver = resolve;
|
|
205
|
-
rejecter = reject;
|
|
206
|
-
});
|
|
207
|
-
return {
|
|
208
|
-
'promise': promise,
|
|
209
|
-
'resolve': resolver,
|
|
210
|
-
'reject': rejecter
|
|
211
|
-
};
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Create a promise with extracted resolve/reject functions and optional abort capability
|
|
216
|
-
* Uses standard Promise with attached abort method
|
|
217
|
-
*/
|
|
218
|
-
export const abortablePromiseWithExtractedComponents = (abortHandler) => {
|
|
219
|
-
let resolver;
|
|
220
|
-
let rejecter;
|
|
221
|
-
const promise = new Promise((resolve, reject) => {
|
|
222
|
-
resolver = resolve;
|
|
223
|
-
rejecter = reject;
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Attach abort method to promise
|
|
227
|
-
promise.abort = abortHandler || (() => {});
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
'promise': promise,
|
|
231
|
-
'resolve': resolver,
|
|
232
|
-
'reject': rejecter
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Semver class for version handling
|
|
237
|
-
export class Semver {
|
|
238
|
-
constructor(major, minor, patch) {
|
|
239
|
-
this.major = major;
|
|
240
|
-
this.minor = minor;
|
|
241
|
-
this.patch = patch;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
toString() {
|
|
245
|
-
return `${this.major}_${this.minor}_${this.patch}`;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// iOS detection
|
|
250
|
-
export function isIOS() {
|
|
251
|
-
const ua = navigator.userAgent;
|
|
252
|
-
return ua.indexOf('iPhone') > 0 || ua.indexOf('iPad') > 0;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function getIOSSemever() {
|
|
256
|
-
if (isIOS()) {
|
|
257
|
-
const extract = navigator.userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/);
|
|
258
|
-
return new Semver(
|
|
259
|
-
parseInt(extract[1] || 0, 10),
|
|
260
|
-
parseInt(extract[2] || 0, 10),
|
|
261
|
-
parseInt(extract[3] || 0, 10)
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Half float conversion utilities
|
|
268
|
-
export const toHalfFloat = DataUtils.toHalfFloat.bind(DataUtils);
|
|
269
|
-
export const fromHalfFloat = DataUtils.fromHalfFloat.bind(DataUtils);
|
|
270
|
-
|
|
271
|
-
// Default spherical harmonics compression range (imported from enums)
|
|
272
|
-
import { DefaultSphericalHarmonics8BitCompressionRange } from '../enums/EngineConstants.js';
|
|
273
|
-
export { DefaultSphericalHarmonics8BitCompressionRange };
|
|
274
|
-
export const DefaultSphericalHarmonics8BitCompressionHalfRange = DefaultSphericalHarmonics8BitCompressionRange / 2.0;
|
|
275
|
-
|
|
276
|
-
// Uncompress float based on compression level
|
|
277
|
-
export const toUncompressedFloat = (f, compressionLevel, isSH = false, range8BitMin, range8BitMax) => {
|
|
278
|
-
if (compressionLevel === 0) {
|
|
279
|
-
return f;
|
|
280
|
-
} else if (compressionLevel === 1 || (compressionLevel === 2 && !isSH)) {
|
|
281
|
-
return DataUtils.fromHalfFloat(f);
|
|
282
|
-
} else if (compressionLevel === 2) {
|
|
283
|
-
return fromUint8(f, range8BitMin, range8BitMax);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// Convert to uint8
|
|
288
|
-
export const toUint8 = (v, rangeMin, rangeMax) => {
|
|
289
|
-
v = clamp(v, rangeMin, rangeMax);
|
|
290
|
-
const range = (rangeMax - rangeMin);
|
|
291
|
-
return clamp(Math.floor((v - rangeMin) / range * 255), 0, 255);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Convert from uint8
|
|
295
|
-
export const fromUint8 = (v, rangeMin, rangeMax) => {
|
|
296
|
-
const range = (rangeMax - rangeMin);
|
|
297
|
-
return (v / 255 * range + rangeMin);
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
// Half float to uint8
|
|
301
|
-
export const fromHalfFloatToUint8 = (v, rangeMin, rangeMax) => {
|
|
302
|
-
return toUint8(fromHalfFloat(v), rangeMin, rangeMax);
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// Uint8 to half float
|
|
306
|
-
export const fromUint8ToHalfFloat = (v, rangeMin, rangeMax) => {
|
|
307
|
-
return toHalfFloat(fromUint8(v, rangeMin, rangeMax));
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// Read float from DataView based on compression level
|
|
311
|
-
export const dataViewFloatForCompressionLevel = (dataView, floatIndex, compressionLevel, isSH = false) => {
|
|
312
|
-
if (compressionLevel === 0) {
|
|
313
|
-
return dataView.getFloat32(floatIndex * 4, true);
|
|
314
|
-
} else if (compressionLevel === 1 || (compressionLevel === 2 && !isSH)) {
|
|
315
|
-
return dataView.getUint16(floatIndex * 2, true);
|
|
316
|
-
} else {
|
|
317
|
-
return dataView.getUint8(floatIndex, true);
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
// Convert between compression levels
|
|
322
|
-
export const convertBetweenCompressionLevels = function() {
|
|
323
|
-
const noop = (v) => v;
|
|
324
|
-
|
|
325
|
-
return function(val, fromLevel, toLevel, isSH = false) {
|
|
326
|
-
if (fromLevel === toLevel) return val;
|
|
327
|
-
let outputConversionFunc = noop;
|
|
328
|
-
|
|
329
|
-
if (fromLevel === 2 && isSH) {
|
|
330
|
-
if (toLevel === 1) outputConversionFunc = fromUint8ToHalfFloat;
|
|
331
|
-
else if (toLevel === 0) {
|
|
332
|
-
outputConversionFunc = fromUint8;
|
|
333
|
-
}
|
|
334
|
-
} else if (fromLevel === 2 || fromLevel === 1) {
|
|
335
|
-
if (toLevel === 0) outputConversionFunc = fromHalfFloat;
|
|
336
|
-
else if (toLevel === 2) {
|
|
337
|
-
if (!isSH) outputConversionFunc = noop;
|
|
338
|
-
else outputConversionFunc = fromHalfFloatToUint8;
|
|
339
|
-
}
|
|
340
|
-
} else if (fromLevel === 0) {
|
|
341
|
-
if (toLevel === 1) outputConversionFunc = toHalfFloat;
|
|
342
|
-
else if (toLevel === 2) {
|
|
343
|
-
if (!isSH) outputConversionFunc = toHalfFloat;
|
|
344
|
-
else outputConversionFunc = toUint8;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return outputConversionFunc(val);
|
|
349
|
-
};
|
|
350
|
-
}();
|
|
351
|
-
|
|
352
|
-
// Copy between buffers
|
|
353
|
-
export const copyBetweenBuffers = (srcBuffer, srcOffset, destBuffer, destOffset, byteCount = 0) => {
|
|
354
|
-
const src = new Uint8Array(srcBuffer, srcOffset);
|
|
355
|
-
const dest = new Uint8Array(destBuffer, destOffset);
|
|
356
|
-
for (let i = 0; i < byteCount; i++) {
|
|
357
|
-
dest[i] = src[i];
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
// Float to half float conversion
|
|
362
|
-
export const floatToHalf = function() {
|
|
363
|
-
const floatView = new Float32Array(1);
|
|
364
|
-
const int32View = new Int32Array(floatView.buffer);
|
|
365
|
-
|
|
366
|
-
return function(val) {
|
|
367
|
-
floatView[0] = val;
|
|
368
|
-
const x = int32View[0];
|
|
369
|
-
|
|
370
|
-
let bits = (x >> 16) & 0x8000;
|
|
371
|
-
let m = (x >> 12) & 0x07ff;
|
|
372
|
-
const e = (x >> 23) & 0xff;
|
|
373
|
-
|
|
374
|
-
if (e < 103) return bits;
|
|
375
|
-
|
|
376
|
-
if (e > 142) {
|
|
377
|
-
bits |= 0x7c00;
|
|
378
|
-
bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff);
|
|
379
|
-
return bits;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (e < 113) {
|
|
383
|
-
m |= 0x0800;
|
|
384
|
-
bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
|
|
385
|
-
return bits;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
bits |= ((e - 112) << 10) | (m >> 1);
|
|
389
|
-
bits += m & 1;
|
|
390
|
-
return bits;
|
|
391
|
-
};
|
|
392
|
-
}();
|
|
393
|
-
|
|
394
|
-
// Encode float as uint
|
|
395
|
-
export const uintEncodedFloat = function() {
|
|
396
|
-
const floatView = new Float32Array(1);
|
|
397
|
-
const int32View = new Int32Array(floatView.buffer);
|
|
398
|
-
|
|
399
|
-
return function(f) {
|
|
400
|
-
floatView[0] = f;
|
|
401
|
-
return int32View[0];
|
|
402
|
-
};
|
|
403
|
-
}();
|
|
404
|
-
|
|
405
|
-
// RGBA to integer
|
|
406
|
-
export const rgbaToInteger = function(r, g, b, a) {
|
|
407
|
-
return r + (g << 8) + (b << 16) + (a << 24);
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// RGBA array to integer
|
|
411
|
-
export const rgbaArrayToInteger = function(arr, offset) {
|
|
412
|
-
return arr[offset] + (arr[offset + 1] << 8) + (arr[offset + 2] << 16) + (arr[offset + 3] << 24);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
// BASE_COMPONENT_COUNT for UncompressedSplatArray
|
|
416
|
-
export const BASE_COMPONENT_COUNT = 14;
|