sceneview-web 1.4.0 → 3.5.0
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 +52 -0
- package/build/dist/js/productionExecutable/sceneview-web.js +2 -0
- package/build/dist/js/productionExecutable/sceneview-web.js.map +1 -0
- package/package.json +25 -8
- package/filament/filament.js +0 -1411
- package/filament/filament.wasm +0 -0
- package/model-viewer.min.js +0 -1083
- package/sceneview-web.js +0 -2
- package/sceneview-web.js.map +0 -1
- package/sceneview.js +0 -505
- package/three/OrbitControls.js +0 -1417
- package/three/RGBELoader.js +0 -450
- package/three/RoomEnvironment.js +0 -148
- package/three/three.module.js +0 -53044
package/sceneview.js
DELETED
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SceneView Web — Simple 3D for the web.
|
|
3
|
-
*
|
|
4
|
-
* One line to render a 3D model:
|
|
5
|
-
* SceneView.modelViewer("canvas", "model.glb")
|
|
6
|
-
*
|
|
7
|
-
* Powered by Filament.js v1.70.1 (Google's PBR renderer, WASM).
|
|
8
|
-
* https://sceneview.github.io
|
|
9
|
-
*
|
|
10
|
-
* @version 1.4.0
|
|
11
|
-
* @license MIT
|
|
12
|
-
*/
|
|
13
|
-
(function(global) {
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
// Filament.js is loaded via <script> tag in HTML (js/filament/filament.js)
|
|
17
|
-
// This avoids dynamic script injection issues with WASM resolution.
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Wait for Filament to be available (loaded by the script tag).
|
|
21
|
-
*/
|
|
22
|
-
function _ensureFilament() {
|
|
23
|
-
return new Promise(function(resolve, reject) {
|
|
24
|
-
if (typeof Filament !== 'undefined') { resolve(); return; }
|
|
25
|
-
// Poll briefly in case the script tag hasn't finished loading
|
|
26
|
-
var attempts = 0;
|
|
27
|
-
var check = setInterval(function() {
|
|
28
|
-
if (typeof Filament !== 'undefined') { clearInterval(check); resolve(); }
|
|
29
|
-
if (++attempts > 100) { clearInterval(check); reject(new Error('SceneView: Filament.js not loaded')); }
|
|
30
|
-
}, 50);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* SceneView instance — wraps Filament engine, scene, camera, renderer.
|
|
36
|
-
*/
|
|
37
|
-
class SceneViewInstance {
|
|
38
|
-
constructor(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader) {
|
|
39
|
-
this._canvas = canvas;
|
|
40
|
-
this._engine = engine;
|
|
41
|
-
this._scene = scene;
|
|
42
|
-
this._renderer = renderer;
|
|
43
|
-
this._view = view;
|
|
44
|
-
this._swapChain = swapChain;
|
|
45
|
-
this._camera = camera;
|
|
46
|
-
this._cameraEntity = cameraEntity;
|
|
47
|
-
this._loader = loader;
|
|
48
|
-
this._asset = null;
|
|
49
|
-
this._angle = 0.785; // Start at ~45° like model-viewer
|
|
50
|
-
this._autoRotate = true;
|
|
51
|
-
this._orbitRadius = 3.5;
|
|
52
|
-
this._orbitHeight = 0.8;
|
|
53
|
-
this._orbitTarget = [0, 0, 0];
|
|
54
|
-
this._running = true;
|
|
55
|
-
this._isDragging = false;
|
|
56
|
-
this._lastMouse = { x: 0, y: 0 };
|
|
57
|
-
// Inertia for smooth orbit deceleration
|
|
58
|
-
this._velocityAngle = 0;
|
|
59
|
-
this._velocityHeight = 0;
|
|
60
|
-
this._dampingFactor = 0.95;
|
|
61
|
-
this._wantsAutoRotate = true; // Remember initial preference for resume after drag
|
|
62
|
-
this._autoRotateTimer = null;
|
|
63
|
-
this._setupControls();
|
|
64
|
-
this._setupResizeObserver();
|
|
65
|
-
this._startRenderLoop();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Load a glTF/GLB model from URL */
|
|
69
|
-
loadModel(url) {
|
|
70
|
-
var self = this;
|
|
71
|
-
return new Promise(function(resolve, reject) {
|
|
72
|
-
fetch(url)
|
|
73
|
-
.then(function(resp) { return resp.arrayBuffer(); })
|
|
74
|
-
.then(function(buffer) {
|
|
75
|
-
Filament.assets = Filament.assets || {};
|
|
76
|
-
Filament.assets[url] = new Uint8Array(buffer);
|
|
77
|
-
try {
|
|
78
|
-
self._showModel(url);
|
|
79
|
-
resolve(self);
|
|
80
|
-
} catch (e) {
|
|
81
|
-
reject(e);
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
.catch(reject);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
_showModel(url) {
|
|
89
|
-
// Remove previous model
|
|
90
|
-
if (this._asset) {
|
|
91
|
-
try {
|
|
92
|
-
this._asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
|
|
93
|
-
this._scene.remove(this._asset.getRoot());
|
|
94
|
-
} catch (e) { /* ignore cleanup errors */ }
|
|
95
|
-
this._asset = null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
var data = Filament.assets[url];
|
|
99
|
-
if (!data) throw new Error('Failed to fetch model: ' + url);
|
|
100
|
-
|
|
101
|
-
var asset = this._loader.createAsset(data);
|
|
102
|
-
if (!asset) throw new Error('Failed to parse model: ' + url);
|
|
103
|
-
|
|
104
|
-
asset.loadResources();
|
|
105
|
-
this._scene.addEntity(asset.getRoot());
|
|
106
|
-
this._scene.addEntities(asset.getRenderableEntities());
|
|
107
|
-
this._asset = asset;
|
|
108
|
-
|
|
109
|
-
// Auto-frame the model
|
|
110
|
-
try {
|
|
111
|
-
var bbox = asset.getBoundingBox();
|
|
112
|
-
var cx = (bbox.min[0] + bbox.max[0]) / 2;
|
|
113
|
-
var cy = (bbox.min[1] + bbox.max[1]) / 2;
|
|
114
|
-
var cz = (bbox.min[2] + bbox.max[2]) / 2;
|
|
115
|
-
var sx = bbox.max[0] - bbox.min[0];
|
|
116
|
-
var sy = bbox.max[1] - bbox.min[1];
|
|
117
|
-
var sz = bbox.max[2] - bbox.min[2];
|
|
118
|
-
var maxDim = Math.max(sx, sy, sz);
|
|
119
|
-
if (maxDim > 0) {
|
|
120
|
-
this._orbitTarget = [cx, cy, cz];
|
|
121
|
-
// Tighter framing than before (1.8x instead of 2.5x)
|
|
122
|
-
this._orbitRadius = maxDim * 1.8;
|
|
123
|
-
this._orbitHeight = cy;
|
|
124
|
-
}
|
|
125
|
-
} catch (e) { /* use defaults */ }
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
setAutoRotate(enabled) { this._autoRotate = enabled; this._wantsAutoRotate = enabled; return this; }
|
|
129
|
-
setCameraDistance(d) { this._orbitRadius = d; return this; }
|
|
130
|
-
|
|
131
|
-
setBackgroundColor(r, g, b, a) {
|
|
132
|
-
this._renderer.setClearOptions({ clearColor: [r, g, b, a !== undefined ? a : 1], clear: true });
|
|
133
|
-
return this;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Add a model to the scene (without removing existing ones) */
|
|
137
|
-
addModel(url) {
|
|
138
|
-
var self = this;
|
|
139
|
-
return new Promise(function(resolve, reject) {
|
|
140
|
-
fetch(url)
|
|
141
|
-
.then(function(resp) { return resp.arrayBuffer(); })
|
|
142
|
-
.then(function(buffer) {
|
|
143
|
-
var data = new Uint8Array(buffer);
|
|
144
|
-
try {
|
|
145
|
-
var asset = self._loader.createAsset(data);
|
|
146
|
-
if (!asset) { reject(new Error('Failed to parse: ' + url)); return; }
|
|
147
|
-
asset.loadResources();
|
|
148
|
-
self._scene.addEntity(asset.getRoot());
|
|
149
|
-
self._scene.addEntities(asset.getRenderableEntities());
|
|
150
|
-
resolve(asset);
|
|
151
|
-
} catch (e) { reject(e); }
|
|
152
|
-
})
|
|
153
|
-
.catch(reject);
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Load a GLB from a Uint8Array buffer directly */
|
|
158
|
-
loadGLBBuffer(buffer, key) {
|
|
159
|
-
var asset = this._loader.createAsset(buffer);
|
|
160
|
-
if (!asset) return null;
|
|
161
|
-
asset.loadResources();
|
|
162
|
-
this._scene.addEntity(asset.getRoot());
|
|
163
|
-
this._scene.addEntities(asset.getRenderableEntities());
|
|
164
|
-
return asset;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** Remove an asset from the scene */
|
|
168
|
-
removeAsset(asset) {
|
|
169
|
-
if (!asset) return;
|
|
170
|
-
try {
|
|
171
|
-
asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
|
|
172
|
-
this._scene.remove(asset.getRoot());
|
|
173
|
-
} catch (e) { /* ignore cleanup errors */ }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Access engine for advanced Filament operations */
|
|
177
|
-
get engine() { return this._engine; }
|
|
178
|
-
get scene() { return this._scene; }
|
|
179
|
-
|
|
180
|
-
dispose() {
|
|
181
|
-
this._running = false;
|
|
182
|
-
if (this._resizeObserver) this._resizeObserver.disconnect();
|
|
183
|
-
try { Filament.Engine.destroy(this._engine); } catch (e) { /* already destroyed */ }
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
_setupControls() {
|
|
187
|
-
var canvas = this._canvas;
|
|
188
|
-
var self = this;
|
|
189
|
-
|
|
190
|
-
canvas.addEventListener('mousedown', function(e) {
|
|
191
|
-
self._isDragging = true;
|
|
192
|
-
self._lastMouse = { x: e.clientX, y: e.clientY };
|
|
193
|
-
self._autoRotate = false;
|
|
194
|
-
self._velocityAngle = 0;
|
|
195
|
-
self._velocityHeight = 0;
|
|
196
|
-
if (self._autoRotateTimer) { clearTimeout(self._autoRotateTimer); self._autoRotateTimer = null; }
|
|
197
|
-
});
|
|
198
|
-
canvas.addEventListener('mousemove', function(e) {
|
|
199
|
-
if (!self._isDragging) return;
|
|
200
|
-
var dx = (e.clientX - self._lastMouse.x) * 0.005;
|
|
201
|
-
var dy = (e.clientY - self._lastMouse.y) * 0.01;
|
|
202
|
-
self._velocityAngle = -dx;
|
|
203
|
-
self._velocityHeight = dy;
|
|
204
|
-
self._angle -= dx;
|
|
205
|
-
self._orbitHeight += dy;
|
|
206
|
-
self._lastMouse = { x: e.clientX, y: e.clientY };
|
|
207
|
-
});
|
|
208
|
-
canvas.addEventListener('mouseup', function() {
|
|
209
|
-
self._isDragging = false;
|
|
210
|
-
// Resume auto-rotate after 3s idle (like model-viewer)
|
|
211
|
-
if (self._wantsAutoRotate) {
|
|
212
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
canvas.addEventListener('mouseleave', function() {
|
|
216
|
-
self._isDragging = false;
|
|
217
|
-
if (self._wantsAutoRotate) {
|
|
218
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
canvas.addEventListener('wheel', function(e) {
|
|
223
|
-
e.preventDefault();
|
|
224
|
-
self._orbitRadius *= (1 + e.deltaY * 0.001);
|
|
225
|
-
self._orbitRadius = Math.max(0.5, Math.min(50, self._orbitRadius));
|
|
226
|
-
}, { passive: false });
|
|
227
|
-
|
|
228
|
-
canvas.addEventListener('touchstart', function(e) {
|
|
229
|
-
if (e.touches.length === 1) {
|
|
230
|
-
self._isDragging = true;
|
|
231
|
-
self._lastMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
232
|
-
self._autoRotate = false;
|
|
233
|
-
self._velocityAngle = 0;
|
|
234
|
-
self._velocityHeight = 0;
|
|
235
|
-
if (self._autoRotateTimer) { clearTimeout(self._autoRotateTimer); self._autoRotateTimer = null; }
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
canvas.addEventListener('touchmove', function(e) {
|
|
239
|
-
if (!self._isDragging || e.touches.length !== 1) return;
|
|
240
|
-
e.preventDefault();
|
|
241
|
-
var dx = (e.touches[0].clientX - self._lastMouse.x) * 0.005;
|
|
242
|
-
var dy = (e.touches[0].clientY - self._lastMouse.y) * 0.01;
|
|
243
|
-
self._velocityAngle = -dx;
|
|
244
|
-
self._velocityHeight = dy;
|
|
245
|
-
self._angle -= dx;
|
|
246
|
-
self._orbitHeight += dy;
|
|
247
|
-
self._lastMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
248
|
-
}, { passive: false });
|
|
249
|
-
canvas.addEventListener('touchend', function() {
|
|
250
|
-
self._isDragging = false;
|
|
251
|
-
if (self._wantsAutoRotate) {
|
|
252
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
_setupResizeObserver() {
|
|
258
|
-
var self = this;
|
|
259
|
-
this._resizeObserver = new ResizeObserver(function() {
|
|
260
|
-
var canvas = self._canvas;
|
|
261
|
-
var dpr = Math.min(devicePixelRatio, 2); // Cap at 2x for performance
|
|
262
|
-
canvas.width = canvas.clientWidth * dpr;
|
|
263
|
-
canvas.height = canvas.clientHeight * dpr;
|
|
264
|
-
self._view.setViewport([0, 0, canvas.width, canvas.height]);
|
|
265
|
-
self._camera.setProjectionFov(
|
|
266
|
-
self._fov || 45, canvas.width / canvas.height, 0.1, 1000,
|
|
267
|
-
Filament.Camera$Fov.VERTICAL
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
this._resizeObserver.observe(this._canvas);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
_startRenderLoop() {
|
|
274
|
-
var self = this;
|
|
275
|
-
function render() {
|
|
276
|
-
if (!self._running) return;
|
|
277
|
-
|
|
278
|
-
// Auto-rotate: 30°/sec ÷ 60fps (matches model-viewer)
|
|
279
|
-
if (self._autoRotate) self._angle += 0.00873;
|
|
280
|
-
|
|
281
|
-
// Inertia damping after drag release
|
|
282
|
-
if (!self._isDragging) {
|
|
283
|
-
self._angle += self._velocityAngle;
|
|
284
|
-
self._orbitHeight += self._velocityHeight;
|
|
285
|
-
self._velocityAngle *= self._dampingFactor;
|
|
286
|
-
self._velocityHeight *= self._dampingFactor;
|
|
287
|
-
if (Math.abs(self._velocityAngle) < 0.00005) self._velocityAngle = 0;
|
|
288
|
-
if (Math.abs(self._velocityHeight) < 0.00005) self._velocityHeight = 0;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
var t = self._orbitTarget;
|
|
292
|
-
var r = self._orbitRadius;
|
|
293
|
-
var h = self._orbitHeight;
|
|
294
|
-
self._camera.lookAt(
|
|
295
|
-
[t[0] + Math.sin(self._angle) * r, h, t[2] + Math.cos(self._angle) * r],
|
|
296
|
-
t,
|
|
297
|
-
[0, 1, 0]
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
self._engine.execute();
|
|
301
|
-
try {
|
|
302
|
-
if (self._renderer.beginFrame(self._swapChain)) {
|
|
303
|
-
self._renderer.renderView(self._view);
|
|
304
|
-
self._renderer.endFrame();
|
|
305
|
-
}
|
|
306
|
-
} catch (e) {
|
|
307
|
-
// Filament 1.70 may need different render call
|
|
308
|
-
console.error('SceneView render error:', e.message);
|
|
309
|
-
self._running = false;
|
|
310
|
-
}
|
|
311
|
-
requestAnimationFrame(render);
|
|
312
|
-
}
|
|
313
|
-
render();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Singleton guard — prevent multiple engine creations on same canvas
|
|
318
|
-
var _activeCanvases = new Set();
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Set up Filament engine, scene, lights on a canvas.
|
|
322
|
-
*/
|
|
323
|
-
function _createEngine(canvasOrId, options) {
|
|
324
|
-
options = options || {};
|
|
325
|
-
|
|
326
|
-
var canvas = typeof canvasOrId === 'string'
|
|
327
|
-
? document.getElementById(canvasOrId)
|
|
328
|
-
: canvasOrId;
|
|
329
|
-
if (!canvas) throw new Error('Canvas not found: ' + canvasOrId);
|
|
330
|
-
|
|
331
|
-
// Prevent double initialization on the same canvas
|
|
332
|
-
if (_activeCanvases.has(canvas)) {
|
|
333
|
-
console.warn('SceneView: Canvas already initialized, skipping');
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
_activeCanvases.add(canvas);
|
|
337
|
-
|
|
338
|
-
var dpr = Math.min(devicePixelRatio, 2);
|
|
339
|
-
// Ensure canvas has actual layout dimensions (not default 300x150)
|
|
340
|
-
var cssW = canvas.clientWidth || canvas.offsetWidth || 500;
|
|
341
|
-
var cssH = canvas.clientHeight || canvas.offsetHeight || 500;
|
|
342
|
-
canvas.width = cssW * dpr;
|
|
343
|
-
canvas.height = cssH * dpr;
|
|
344
|
-
|
|
345
|
-
var engine = Filament.Engine.create(canvas);
|
|
346
|
-
var scene = engine.createScene();
|
|
347
|
-
var renderer = engine.createRenderer();
|
|
348
|
-
var cameraEntity = Filament.EntityManager.get().create();
|
|
349
|
-
var camera = engine.createCamera(cameraEntity);
|
|
350
|
-
var view = engine.createView();
|
|
351
|
-
var swapChain = engine.createSwapChain();
|
|
352
|
-
|
|
353
|
-
view.setCamera(camera);
|
|
354
|
-
view.setScene(scene);
|
|
355
|
-
view.setViewport([0, 0, canvas.width, canvas.height]);
|
|
356
|
-
|
|
357
|
-
var bg = options.backgroundColor || [0.05, 0.06, 0.1, 1.0];
|
|
358
|
-
renderer.setClearOptions({ clearColor: bg, clear: true });
|
|
359
|
-
|
|
360
|
-
var fov = options.fov || 45;
|
|
361
|
-
camera.setProjectionFov(fov, canvas.width / canvas.height, 0.1, 1000, Filament.Camera$Fov.VERTICAL);
|
|
362
|
-
camera.lookAt([0, 1, 5], [0, 0, 0], [0, 1, 0]);
|
|
363
|
-
|
|
364
|
-
// --- Post-processing quality ---
|
|
365
|
-
try {
|
|
366
|
-
view.setAmbientOcclusionOptions({
|
|
367
|
-
enabled: true, radius: 0.3, bias: 0.0005, intensity: 1.0, quality: 1
|
|
368
|
-
});
|
|
369
|
-
} catch (e) { /* skip */ }
|
|
370
|
-
|
|
371
|
-
// --- 3-point studio lighting ---
|
|
372
|
-
// Sun/key light — warm, strong
|
|
373
|
-
var sun = Filament.EntityManager.get().create();
|
|
374
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.SUN)
|
|
375
|
-
.color([0.98, 0.92, 0.89])
|
|
376
|
-
.intensity(options.lightIntensity || 110000)
|
|
377
|
-
.direction([0.6, -1.0, -0.8])
|
|
378
|
-
.sunAngularRadius(1.9)
|
|
379
|
-
.sunHaloSize(10.0)
|
|
380
|
-
.sunHaloFalloff(80.0)
|
|
381
|
-
.build(engine, sun);
|
|
382
|
-
scene.addEntity(sun);
|
|
383
|
-
|
|
384
|
-
// Fill light — cool, softer
|
|
385
|
-
var fill = Filament.EntityManager.get().create();
|
|
386
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.DIRECTIONAL)
|
|
387
|
-
.color([0.7, 0.75, 0.9])
|
|
388
|
-
.intensity(60000)
|
|
389
|
-
.direction([-0.5, 0.5, 1.0])
|
|
390
|
-
.build(engine, fill);
|
|
391
|
-
scene.addEntity(fill);
|
|
392
|
-
|
|
393
|
-
// Back/rim light — edge highlight
|
|
394
|
-
var back = Filament.EntityManager.get().create();
|
|
395
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.DIRECTIONAL)
|
|
396
|
-
.color([0.5, 0.6, 0.9])
|
|
397
|
-
.intensity(50000)
|
|
398
|
-
.direction([0, 0.3, 1.0])
|
|
399
|
-
.build(engine, back);
|
|
400
|
-
scene.addEntity(back);
|
|
401
|
-
|
|
402
|
-
// --- IBL: load real KTX if available, fallback to synthetic SH ---
|
|
403
|
-
var iblUrl = options.iblUrl || 'environments/neutral_ibl.ktx';
|
|
404
|
-
fetch(iblUrl)
|
|
405
|
-
.then(function(r) {
|
|
406
|
-
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
407
|
-
return r.arrayBuffer().then(function(ab) { return new Uint8Array(ab); });
|
|
408
|
-
})
|
|
409
|
-
.then(function(buffer) {
|
|
410
|
-
try {
|
|
411
|
-
var ibl = engine.createIblFromKtx1(buffer);
|
|
412
|
-
ibl.setIntensity(options.iblIntensity || 40000);
|
|
413
|
-
scene.setIndirectLight(ibl);
|
|
414
|
-
// Create skybox from IBL reflection cubemap if skybox enabled
|
|
415
|
-
if (options.skybox !== false) {
|
|
416
|
-
try {
|
|
417
|
-
var reflections = ibl.getReflectionsTexture();
|
|
418
|
-
if (reflections) {
|
|
419
|
-
var skybox = Filament.Skybox.Builder()
|
|
420
|
-
.environment(reflections)
|
|
421
|
-
.build(engine);
|
|
422
|
-
scene.setSkybox(skybox);
|
|
423
|
-
console.log('SceneView: Skybox created from IBL cubemap');
|
|
424
|
-
}
|
|
425
|
-
} catch (skyErr) {
|
|
426
|
-
// Skybox not supported in this build — that's OK
|
|
427
|
-
console.log('SceneView: Skybox not available (IBL-only mode)');
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
console.log('SceneView: KTX IBL loaded (' + Math.round(buffer.length / 1024) + 'KB)');
|
|
431
|
-
} catch (e) {
|
|
432
|
-
console.warn('SceneView: createIblFromKtx1 failed, using SH fallback', e);
|
|
433
|
-
_applySyntheticIBL(engine, scene);
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
.catch(function() {
|
|
437
|
-
_applySyntheticIBL(engine, scene);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
var loader = engine.createAssetLoader();
|
|
441
|
-
var instance = new SceneViewInstance(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader);
|
|
442
|
-
instance._fov = fov;
|
|
443
|
-
|
|
444
|
-
if (options.autoRotate === false) instance.setAutoRotate(false);
|
|
445
|
-
|
|
446
|
-
return instance;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/** Fallback IBL from spherical harmonics when KTX not available */
|
|
450
|
-
function _applySyntheticIBL(engine, scene) {
|
|
451
|
-
try {
|
|
452
|
-
var ibl = Filament.IndirectLight.Builder()
|
|
453
|
-
.irradiance(3, [
|
|
454
|
-
0.65, 0.65, 0.70,
|
|
455
|
-
0.10, 0.10, 0.12,
|
|
456
|
-
0.15, 0.15, 0.18,
|
|
457
|
-
-0.02, -0.02, -0.01,
|
|
458
|
-
0.04, 0.04, 0.05,
|
|
459
|
-
0.08, 0.08, 0.10,
|
|
460
|
-
0.01, 0.01, 0.01,
|
|
461
|
-
-0.02, -0.02, -0.02,
|
|
462
|
-
0.03, 0.03, 0.03
|
|
463
|
-
])
|
|
464
|
-
.intensity(35000)
|
|
465
|
-
.build(engine);
|
|
466
|
-
scene.setIndirectLight(ibl);
|
|
467
|
-
console.log('SceneView: Using synthetic SH IBL');
|
|
468
|
-
} catch (e) { /* skip */ }
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function create(canvasOrId, options) {
|
|
472
|
-
return _ensureFilament().then(function() {
|
|
473
|
-
return new Promise(function(resolve, reject) {
|
|
474
|
-
if (typeof Filament.Engine !== 'undefined') {
|
|
475
|
-
try {
|
|
476
|
-
var instance = _createEngine(canvasOrId, options);
|
|
477
|
-
if (instance) resolve(instance);
|
|
478
|
-
else reject(new Error('SceneView: Canvas already initialized'));
|
|
479
|
-
} catch (e) { reject(e); }
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
Filament.init([], function() {
|
|
483
|
-
try {
|
|
484
|
-
var instance = _createEngine(canvasOrId, options);
|
|
485
|
-
if (instance) resolve(instance);
|
|
486
|
-
else reject(new Error('SceneView: Canvas already initialized'));
|
|
487
|
-
} catch (e) { reject(e); }
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function modelViewer(canvasOrId, modelUrl, options) {
|
|
494
|
-
return create(canvasOrId, options).then(function(instance) {
|
|
495
|
-
return instance.loadModel(modelUrl);
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
global.SceneView = {
|
|
500
|
-
version: '1.4.0',
|
|
501
|
-
create: create,
|
|
502
|
-
modelViewer: modelViewer
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
})(typeof globalThis !== 'undefined' ? globalThis : window);
|