sceneview-web 1.5.0 → 3.5.1
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/sceneview-web.js +0 -2
- package/sceneview-web.js.map +0 -1
- package/sceneview.js +0 -591
package/sceneview.js
DELETED
|
@@ -1,591 +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.5.0
|
|
11
|
-
* @license MIT
|
|
12
|
-
*/
|
|
13
|
-
(function(global) {
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
function _ensureFilament() {
|
|
17
|
-
return new Promise(function(resolve, reject) {
|
|
18
|
-
if (typeof Filament !== 'undefined') { resolve(); return; }
|
|
19
|
-
var attempts = 0;
|
|
20
|
-
var check = setInterval(function() {
|
|
21
|
-
if (typeof Filament !== 'undefined') { clearInterval(check); resolve(); }
|
|
22
|
-
if (++attempts > 100) { clearInterval(check); reject(new Error('SceneView: Filament.js not loaded')); }
|
|
23
|
-
}, 50);
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class SceneViewInstance {
|
|
28
|
-
constructor(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader) {
|
|
29
|
-
this._canvas = canvas;
|
|
30
|
-
this._engine = engine;
|
|
31
|
-
this._scene = scene;
|
|
32
|
-
this._renderer = renderer;
|
|
33
|
-
this._view = view;
|
|
34
|
-
this._swapChain = swapChain;
|
|
35
|
-
this._camera = camera;
|
|
36
|
-
this._cameraEntity = cameraEntity;
|
|
37
|
-
this._loader = loader;
|
|
38
|
-
this._asset = null;
|
|
39
|
-
this._angle = 0.785;
|
|
40
|
-
this._autoRotate = true;
|
|
41
|
-
this._orbitRadius = 3.5;
|
|
42
|
-
this._orbitHeight = 0.8;
|
|
43
|
-
this._orbitTarget = [0, 0, 0];
|
|
44
|
-
this._running = true;
|
|
45
|
-
this._isDragging = false;
|
|
46
|
-
this._lastMouse = { x: 0, y: 0 };
|
|
47
|
-
this._velocityAngle = 0;
|
|
48
|
-
this._velocityHeight = 0;
|
|
49
|
-
this._dampingFactor = 0.95;
|
|
50
|
-
this._wantsAutoRotate = true;
|
|
51
|
-
this._autoRotateTimer = null;
|
|
52
|
-
this._userLights = [];
|
|
53
|
-
this._setupControls();
|
|
54
|
-
this._setupResizeObserver();
|
|
55
|
-
this._startRenderLoop();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ── Model loading ──
|
|
59
|
-
|
|
60
|
-
loadModel(url) {
|
|
61
|
-
var self = this;
|
|
62
|
-
return new Promise(function(resolve, reject) {
|
|
63
|
-
fetch(url)
|
|
64
|
-
.then(function(resp) { return resp.arrayBuffer(); })
|
|
65
|
-
.then(function(buffer) {
|
|
66
|
-
Filament.assets = Filament.assets || {};
|
|
67
|
-
Filament.assets[url] = new Uint8Array(buffer);
|
|
68
|
-
try { self._showModel(url); resolve(self); } catch (e) { reject(e); }
|
|
69
|
-
})
|
|
70
|
-
.catch(reject);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
_showModel(url) {
|
|
75
|
-
if (this._asset) {
|
|
76
|
-
try {
|
|
77
|
-
this._asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
|
|
78
|
-
this._scene.remove(this._asset.getRoot());
|
|
79
|
-
} catch (e) {}
|
|
80
|
-
this._asset = null;
|
|
81
|
-
}
|
|
82
|
-
var data = Filament.assets[url];
|
|
83
|
-
if (!data) throw new Error('Failed to fetch model: ' + url);
|
|
84
|
-
var asset = this._loader.createAsset(data);
|
|
85
|
-
if (!asset) throw new Error('Failed to parse model: ' + url);
|
|
86
|
-
asset.loadResources();
|
|
87
|
-
this._scene.addEntity(asset.getRoot());
|
|
88
|
-
this._scene.addEntities(asset.getRenderableEntities());
|
|
89
|
-
this._asset = asset;
|
|
90
|
-
this._autoFrame(asset);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
_autoFrame(asset) {
|
|
94
|
-
try {
|
|
95
|
-
var bbox = asset.getBoundingBox();
|
|
96
|
-
var cx = (bbox.min[0] + bbox.max[0]) / 2;
|
|
97
|
-
var cy = (bbox.min[1] + bbox.max[1]) / 2;
|
|
98
|
-
var cz = (bbox.min[2] + bbox.max[2]) / 2;
|
|
99
|
-
var maxDim = Math.max(bbox.max[0] - bbox.min[0], bbox.max[1] - bbox.min[1], bbox.max[2] - bbox.min[2]);
|
|
100
|
-
if (maxDim > 0) {
|
|
101
|
-
this._orbitTarget = [cx, cy, cz];
|
|
102
|
-
this._orbitRadius = maxDim * 1.8;
|
|
103
|
-
this._orbitHeight = cy;
|
|
104
|
-
}
|
|
105
|
-
} catch (e) {}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
addModel(url) {
|
|
109
|
-
var self = this;
|
|
110
|
-
return new Promise(function(resolve, reject) {
|
|
111
|
-
fetch(url)
|
|
112
|
-
.then(function(resp) { return resp.arrayBuffer(); })
|
|
113
|
-
.then(function(buffer) {
|
|
114
|
-
try {
|
|
115
|
-
var asset = self._loader.createAsset(new Uint8Array(buffer));
|
|
116
|
-
if (!asset) { reject(new Error('Failed to parse: ' + url)); return; }
|
|
117
|
-
asset.loadResources();
|
|
118
|
-
self._scene.addEntity(asset.getRoot());
|
|
119
|
-
self._scene.addEntities(asset.getRenderableEntities());
|
|
120
|
-
resolve(asset);
|
|
121
|
-
} catch (e) { reject(e); }
|
|
122
|
-
})
|
|
123
|
-
.catch(reject);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
loadGLBBuffer(buffer) {
|
|
128
|
-
var asset = this._loader.createAsset(buffer);
|
|
129
|
-
if (!asset) return null;
|
|
130
|
-
asset.loadResources();
|
|
131
|
-
this._scene.addEntity(asset.getRoot());
|
|
132
|
-
this._scene.addEntities(asset.getRenderableEntities());
|
|
133
|
-
return asset;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
removeAsset(asset) {
|
|
137
|
-
if (!asset) return;
|
|
138
|
-
try {
|
|
139
|
-
asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
|
|
140
|
-
this._scene.remove(asset.getRoot());
|
|
141
|
-
} catch (e) {}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
get engine() { return this._engine; }
|
|
145
|
-
get scene() { return this._scene; }
|
|
146
|
-
get view() { return this._view; }
|
|
147
|
-
get camera() { return this._camera; }
|
|
148
|
-
|
|
149
|
-
// ── Basic setters ──
|
|
150
|
-
|
|
151
|
-
setAutoRotate(enabled) { this._autoRotate = enabled; this._wantsAutoRotate = enabled; return this; }
|
|
152
|
-
setCameraDistance(d) { this._orbitRadius = d; return this; }
|
|
153
|
-
|
|
154
|
-
setBackgroundColor(r, g, b, a) {
|
|
155
|
-
this._renderer.setClearOptions({ clearColor: [r, g, b, a !== undefined ? a : 1], clear: true });
|
|
156
|
-
return this;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ── Post-processing ──
|
|
160
|
-
|
|
161
|
-
setBloom(opts) {
|
|
162
|
-
try { this._view.setBloomOptions(Object.assign({ enabled: true, strength: 0.1, resolution: 360, levels: 6, blendMode: 0 }, opts)); } catch (e) {}
|
|
163
|
-
return this;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
setFog(opts) {
|
|
167
|
-
try { this._view.setFogOptions(Object.assign({ enabled: true, distance: 10, maximumOpacity: 0.8, color: [0.5, 0.55, 0.65] }, opts)); } catch (e) {}
|
|
168
|
-
return this;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
setVignette(opts) {
|
|
172
|
-
try { this._view.setVignetteOptions(Object.assign({ enabled: true, midPoint: 0.5, roundness: 0.5, feather: 0.5 }, opts)); } catch (e) {}
|
|
173
|
-
return this;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setDOF(opts) {
|
|
177
|
-
try { this._view.setDepthOfFieldOptions(Object.assign({ enabled: true, cocScale: 1.0 }, opts)); } catch (e) {}
|
|
178
|
-
return this;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
setMSAA(opts) {
|
|
182
|
-
try { this._view.setMultiSampleAntiAliasingOptions(Object.assign({ enabled: true, sampleCount: 4 }, opts)); } catch (e) {}
|
|
183
|
-
return this;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
setTAA(opts) {
|
|
187
|
-
try { this._view.setTemporalAntiAliasingOptions(Object.assign({ enabled: true }, opts)); } catch (e) {}
|
|
188
|
-
return this;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
setSSR(opts) {
|
|
192
|
-
try { this._view.setScreenSpaceReflectionsOptions(Object.assign({ enabled: true }, opts)); } catch (e) {}
|
|
193
|
-
return this;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
setQuality(level) {
|
|
197
|
-
if (level === 'low') {
|
|
198
|
-
this.setBloom({ enabled: false }); this.setMSAA({ enabled: false });
|
|
199
|
-
} else if (level === 'medium') {
|
|
200
|
-
this.setBloom({ enabled: true, strength: 0.05 }); this.setMSAA({ enabled: true, sampleCount: 2 });
|
|
201
|
-
} else if (level === 'high') {
|
|
202
|
-
this.setBloom({ enabled: true, strength: 0.1 }); this.setMSAA({ enabled: true, sampleCount: 4 }); this.setVignette({ enabled: true });
|
|
203
|
-
} else if (level === 'ultra') {
|
|
204
|
-
this.setBloom({ enabled: true, strength: 0.15 }); this.setMSAA({ enabled: true, sampleCount: 4 });
|
|
205
|
-
this.setTAA({ enabled: true }); this.setSSR({ enabled: true }); this.setVignette({ enabled: true });
|
|
206
|
-
}
|
|
207
|
-
return this;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── Camera helpers ──
|
|
211
|
-
|
|
212
|
-
setCameraPosition(x, y, z) {
|
|
213
|
-
this._orbitRadius = Math.sqrt(x * x + z * z);
|
|
214
|
-
this._orbitHeight = y;
|
|
215
|
-
this._angle = Math.atan2(x, z);
|
|
216
|
-
return this;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
setCameraTarget(x, y, z) { this._orbitTarget = [x, y, z]; return this; }
|
|
220
|
-
|
|
221
|
-
setCameraFOV(degrees) {
|
|
222
|
-
this._fov = degrees;
|
|
223
|
-
var c = this._canvas;
|
|
224
|
-
this._camera.setProjectionFov(degrees, c.width / c.height, 0.1, 1000, Filament.Camera$Fov.VERTICAL);
|
|
225
|
-
return this;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
setOrbitSpeed(speed) { this._orbitSpeed = speed; return this; }
|
|
229
|
-
|
|
230
|
-
setOrbitLimits(opts) {
|
|
231
|
-
if (opts.minDistance !== undefined) this._minRadius = opts.minDistance;
|
|
232
|
-
if (opts.maxDistance !== undefined) this._maxRadius = opts.maxDistance;
|
|
233
|
-
return this;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
animateCamera(opts) {
|
|
237
|
-
var self = this;
|
|
238
|
-
var duration = opts.duration || 1000;
|
|
239
|
-
var startTime = performance.now();
|
|
240
|
-
var sA = this._angle, sR = this._orbitRadius, sH = this._orbitHeight;
|
|
241
|
-
var sT = this._orbitTarget.slice();
|
|
242
|
-
var eT = opts.target || sT;
|
|
243
|
-
var eR = opts.distance !== undefined ? opts.distance : sR;
|
|
244
|
-
var eH = opts.height !== undefined ? opts.height : sH;
|
|
245
|
-
var eA = opts.angle !== undefined ? opts.angle : sA;
|
|
246
|
-
var wasAuto = this._autoRotate;
|
|
247
|
-
this._autoRotate = false;
|
|
248
|
-
this._cameraAnimating = true;
|
|
249
|
-
|
|
250
|
-
function ease(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
|
|
251
|
-
function lerp(a, b, t) { return a + (b - a) * t; }
|
|
252
|
-
|
|
253
|
-
function step(now) {
|
|
254
|
-
if (!self._running || !self._cameraAnimating) return;
|
|
255
|
-
var t = ease(Math.min((now - startTime) / duration, 1));
|
|
256
|
-
self._angle = lerp(sA, eA, t);
|
|
257
|
-
self._orbitRadius = lerp(sR, eR, t);
|
|
258
|
-
self._orbitHeight = lerp(sH, eH, t);
|
|
259
|
-
self._orbitTarget = [lerp(sT[0], eT[0], t), lerp(sT[1], eT[1], t), lerp(sT[2], eT[2], t)];
|
|
260
|
-
if (t < 1) { requestAnimationFrame(step); }
|
|
261
|
-
else { self._cameraAnimating = false; if (wasAuto) self._autoRotate = true; if (opts.onComplete) opts.onComplete(); }
|
|
262
|
-
}
|
|
263
|
-
requestAnimationFrame(step);
|
|
264
|
-
return this;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ── Dynamic lights ──
|
|
268
|
-
|
|
269
|
-
addLight(type, opts) {
|
|
270
|
-
opts = opts || {};
|
|
271
|
-
var entity = Filament.EntityManager.get().create();
|
|
272
|
-
var typeMap = { sun: Filament.LightManager$Type.SUN, directional: Filament.LightManager$Type.DIRECTIONAL, point: Filament.LightManager$Type.POINT, spot: Filament.LightManager$Type.SPOT };
|
|
273
|
-
var builder = Filament.LightManager.Builder(typeMap[type] || Filament.LightManager$Type.POINT)
|
|
274
|
-
.color(opts.color || [1, 1, 1]).intensity(opts.intensity || 100000);
|
|
275
|
-
if (opts.direction) builder.direction(opts.direction);
|
|
276
|
-
if (opts.position) builder.position(opts.position);
|
|
277
|
-
if (opts.falloff) builder.falloff(opts.falloff);
|
|
278
|
-
if (opts.castShadows) builder.castShadows(true);
|
|
279
|
-
if (type === 'spot') builder.spotLightCone(opts.innerCone || 0.5, opts.outerCone || 0.7);
|
|
280
|
-
if (type === 'sun') { builder.sunAngularRadius(opts.angularRadius || 1.9); builder.sunHaloSize(opts.haloSize || 10.0); builder.sunHaloFalloff(opts.haloFalloff || 80.0); }
|
|
281
|
-
builder.build(this._engine, entity);
|
|
282
|
-
this._scene.addEntity(entity);
|
|
283
|
-
this._userLights.push(entity);
|
|
284
|
-
return entity;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
removeLight(entity) {
|
|
288
|
-
this._scene.remove(entity);
|
|
289
|
-
var i = this._userLights.indexOf(entity);
|
|
290
|
-
if (i >= 0) this._userLights.splice(i, 1);
|
|
291
|
-
return this;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
clearLights() {
|
|
295
|
-
this._userLights.forEach(function(e) { this._scene.remove(e); }.bind(this));
|
|
296
|
-
this._userLights = [];
|
|
297
|
-
return this;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// ── Skybox control ──
|
|
301
|
-
|
|
302
|
-
setSkyboxColor(r, g, b) {
|
|
303
|
-
try { this._scene.setSkybox(Filament.Skybox.Builder().color([r, g, b, 1]).build(this._engine)); } catch (e) {}
|
|
304
|
-
return this;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
removeSkybox() { try { this._scene.setSkybox(null); } catch (e) {} return this; }
|
|
308
|
-
|
|
309
|
-
// ── Asset querying ──
|
|
310
|
-
|
|
311
|
-
getEntitiesByName(name) { if (!this._asset) return []; try { return this._asset.getEntitiesByName(name); } catch (e) { return []; } }
|
|
312
|
-
getEntitiesByPrefix(prefix) { if (!this._asset) return []; try { return this._asset.getEntitiesByPrefix(prefix); } catch (e) { return []; } }
|
|
313
|
-
|
|
314
|
-
// ── Lifecycle ──
|
|
315
|
-
|
|
316
|
-
dispose() {
|
|
317
|
-
this._running = false;
|
|
318
|
-
if (this._resizeObserver) this._resizeObserver.disconnect();
|
|
319
|
-
_activeCanvases.delete(this._canvas);
|
|
320
|
-
try { Filament.Engine.destroy(this._engine); } catch (e) {}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
_setupControls() {
|
|
324
|
-
var canvas = this._canvas;
|
|
325
|
-
var self = this;
|
|
326
|
-
|
|
327
|
-
canvas.addEventListener('mousedown', function(e) {
|
|
328
|
-
self._isDragging = true;
|
|
329
|
-
self._lastMouse = { x: e.clientX, y: e.clientY };
|
|
330
|
-
self._autoRotate = false;
|
|
331
|
-
self._velocityAngle = 0;
|
|
332
|
-
self._velocityHeight = 0;
|
|
333
|
-
if (self._autoRotateTimer) { clearTimeout(self._autoRotateTimer); self._autoRotateTimer = null; }
|
|
334
|
-
});
|
|
335
|
-
canvas.addEventListener('mousemove', function(e) {
|
|
336
|
-
if (!self._isDragging) return;
|
|
337
|
-
var dx = (e.clientX - self._lastMouse.x) * 0.005;
|
|
338
|
-
var dy = (e.clientY - self._lastMouse.y) * 0.01;
|
|
339
|
-
self._velocityAngle = -dx;
|
|
340
|
-
self._velocityHeight = dy;
|
|
341
|
-
self._angle -= dx;
|
|
342
|
-
self._orbitHeight += dy;
|
|
343
|
-
self._lastMouse = { x: e.clientX, y: e.clientY };
|
|
344
|
-
});
|
|
345
|
-
canvas.addEventListener('mouseup', function() {
|
|
346
|
-
self._isDragging = false;
|
|
347
|
-
if (self._wantsAutoRotate) {
|
|
348
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
canvas.addEventListener('mouseleave', function() {
|
|
352
|
-
self._isDragging = false;
|
|
353
|
-
if (self._wantsAutoRotate) {
|
|
354
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
canvas.addEventListener('wheel', function(e) {
|
|
359
|
-
e.preventDefault();
|
|
360
|
-
self._orbitRadius *= (1 + e.deltaY * 0.001);
|
|
361
|
-
var minR = self._minRadius || 0.5;
|
|
362
|
-
var maxR = self._maxRadius || 50;
|
|
363
|
-
self._orbitRadius = Math.max(minR, Math.min(maxR, self._orbitRadius));
|
|
364
|
-
}, { passive: false });
|
|
365
|
-
|
|
366
|
-
canvas.addEventListener('touchstart', function(e) {
|
|
367
|
-
if (e.touches.length === 1) {
|
|
368
|
-
self._isDragging = true;
|
|
369
|
-
self._lastMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
370
|
-
self._autoRotate = false;
|
|
371
|
-
self._velocityAngle = 0;
|
|
372
|
-
self._velocityHeight = 0;
|
|
373
|
-
if (self._autoRotateTimer) { clearTimeout(self._autoRotateTimer); self._autoRotateTimer = null; }
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
canvas.addEventListener('touchmove', function(e) {
|
|
377
|
-
if (!self._isDragging || e.touches.length !== 1) return;
|
|
378
|
-
e.preventDefault();
|
|
379
|
-
var dx = (e.touches[0].clientX - self._lastMouse.x) * 0.005;
|
|
380
|
-
var dy = (e.touches[0].clientY - self._lastMouse.y) * 0.01;
|
|
381
|
-
self._velocityAngle = -dx;
|
|
382
|
-
self._velocityHeight = dy;
|
|
383
|
-
self._angle -= dx;
|
|
384
|
-
self._orbitHeight += dy;
|
|
385
|
-
self._lastMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
386
|
-
}, { passive: false });
|
|
387
|
-
canvas.addEventListener('touchend', function() {
|
|
388
|
-
self._isDragging = false;
|
|
389
|
-
if (self._wantsAutoRotate) {
|
|
390
|
-
self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
_setupResizeObserver() {
|
|
396
|
-
var self = this;
|
|
397
|
-
this._resizeObserver = new ResizeObserver(function() {
|
|
398
|
-
var canvas = self._canvas;
|
|
399
|
-
var dpr = Math.min(devicePixelRatio, 2);
|
|
400
|
-
canvas.width = canvas.clientWidth * dpr;
|
|
401
|
-
canvas.height = canvas.clientHeight * dpr;
|
|
402
|
-
self._view.setViewport([0, 0, canvas.width, canvas.height]);
|
|
403
|
-
self._camera.setProjectionFov(
|
|
404
|
-
self._fov || 45, canvas.width / canvas.height, 0.1, 1000,
|
|
405
|
-
Filament.Camera$Fov.VERTICAL
|
|
406
|
-
);
|
|
407
|
-
});
|
|
408
|
-
this._resizeObserver.observe(this._canvas);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
_startRenderLoop() {
|
|
412
|
-
var self = this;
|
|
413
|
-
function render() {
|
|
414
|
-
if (!self._running) return;
|
|
415
|
-
if (self._autoRotate) self._angle += (self._orbitSpeed || 0.00873);
|
|
416
|
-
if (!self._isDragging && !self._cameraAnimating) {
|
|
417
|
-
self._angle += self._velocityAngle;
|
|
418
|
-
self._orbitHeight += self._velocityHeight;
|
|
419
|
-
self._velocityAngle *= self._dampingFactor;
|
|
420
|
-
self._velocityHeight *= self._dampingFactor;
|
|
421
|
-
if (Math.abs(self._velocityAngle) < 0.00005) self._velocityAngle = 0;
|
|
422
|
-
if (Math.abs(self._velocityHeight) < 0.00005) self._velocityHeight = 0;
|
|
423
|
-
}
|
|
424
|
-
var t = self._orbitTarget;
|
|
425
|
-
var r = self._orbitRadius;
|
|
426
|
-
var h = self._orbitHeight;
|
|
427
|
-
self._camera.lookAt(
|
|
428
|
-
[t[0] + Math.sin(self._angle) * r, h, t[2] + Math.cos(self._angle) * r],
|
|
429
|
-
t, [0, 1, 0]
|
|
430
|
-
);
|
|
431
|
-
self._engine.execute();
|
|
432
|
-
try {
|
|
433
|
-
if (self._renderer.beginFrame(self._swapChain)) {
|
|
434
|
-
self._renderer.renderView(self._view);
|
|
435
|
-
self._renderer.endFrame();
|
|
436
|
-
}
|
|
437
|
-
} catch (e) {
|
|
438
|
-
console.error('SceneView render error:', e.message);
|
|
439
|
-
self._running = false;
|
|
440
|
-
}
|
|
441
|
-
requestAnimationFrame(render);
|
|
442
|
-
}
|
|
443
|
-
render();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
var _activeCanvases = new Set();
|
|
448
|
-
|
|
449
|
-
function _createEngine(canvasOrId, options) {
|
|
450
|
-
options = options || {};
|
|
451
|
-
var canvas = typeof canvasOrId === 'string' ? document.getElementById(canvasOrId) : canvasOrId;
|
|
452
|
-
if (!canvas) throw new Error('Canvas not found: ' + canvasOrId);
|
|
453
|
-
if (_activeCanvases.has(canvas)) { console.warn('SceneView: Canvas already initialized, skipping'); return null; }
|
|
454
|
-
_activeCanvases.add(canvas);
|
|
455
|
-
|
|
456
|
-
var dpr = Math.min(devicePixelRatio, 2);
|
|
457
|
-
var cssW = canvas.clientWidth || canvas.offsetWidth || 500;
|
|
458
|
-
var cssH = canvas.clientHeight || canvas.offsetHeight || 500;
|
|
459
|
-
canvas.width = cssW * dpr;
|
|
460
|
-
canvas.height = cssH * dpr;
|
|
461
|
-
|
|
462
|
-
var engine = Filament.Engine.create(canvas);
|
|
463
|
-
var scene = engine.createScene();
|
|
464
|
-
var renderer = engine.createRenderer();
|
|
465
|
-
var cameraEntity = Filament.EntityManager.get().create();
|
|
466
|
-
var camera = engine.createCamera(cameraEntity);
|
|
467
|
-
var view = engine.createView();
|
|
468
|
-
var swapChain = engine.createSwapChain();
|
|
469
|
-
|
|
470
|
-
view.setCamera(camera);
|
|
471
|
-
view.setScene(scene);
|
|
472
|
-
view.setViewport([0, 0, canvas.width, canvas.height]);
|
|
473
|
-
|
|
474
|
-
var bg = options.backgroundColor || [0.05, 0.06, 0.1, 1.0];
|
|
475
|
-
renderer.setClearOptions({ clearColor: bg, clear: true });
|
|
476
|
-
|
|
477
|
-
var fov = options.fov || 45;
|
|
478
|
-
camera.setProjectionFov(fov, canvas.width / canvas.height, 0.1, 1000, Filament.Camera$Fov.VERTICAL);
|
|
479
|
-
camera.lookAt([0, 1, 5], [0, 0, 0], [0, 1, 0]);
|
|
480
|
-
|
|
481
|
-
try { view.setAmbientOcclusionOptions({ enabled: true, radius: 0.3, bias: 0.0005, intensity: 1.0, quality: 1 }); } catch (e) {}
|
|
482
|
-
|
|
483
|
-
var sun = Filament.EntityManager.get().create();
|
|
484
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.SUN)
|
|
485
|
-
.color([0.98, 0.92, 0.89]).intensity(options.lightIntensity || 110000)
|
|
486
|
-
.direction([0.6, -1.0, -0.8]).sunAngularRadius(1.9).sunHaloSize(10.0).sunHaloFalloff(80.0)
|
|
487
|
-
.build(engine, sun);
|
|
488
|
-
scene.addEntity(sun);
|
|
489
|
-
|
|
490
|
-
var fill = Filament.EntityManager.get().create();
|
|
491
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.DIRECTIONAL)
|
|
492
|
-
.color([0.7, 0.75, 0.9]).intensity(60000).direction([-0.5, 0.5, 1.0])
|
|
493
|
-
.build(engine, fill);
|
|
494
|
-
scene.addEntity(fill);
|
|
495
|
-
|
|
496
|
-
var back = Filament.EntityManager.get().create();
|
|
497
|
-
Filament.LightManager.Builder(Filament.LightManager$Type.DIRECTIONAL)
|
|
498
|
-
.color([0.5, 0.6, 0.9]).intensity(50000).direction([0, 0.3, 1.0])
|
|
499
|
-
.build(engine, back);
|
|
500
|
-
scene.addEntity(back);
|
|
501
|
-
|
|
502
|
-
var iblUrl = options.iblUrl || 'environments/neutral_ibl.ktx';
|
|
503
|
-
fetch(iblUrl)
|
|
504
|
-
.then(function(r) {
|
|
505
|
-
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
506
|
-
return r.arrayBuffer().then(function(ab) { return new Uint8Array(ab); });
|
|
507
|
-
})
|
|
508
|
-
.then(function(buffer) {
|
|
509
|
-
try {
|
|
510
|
-
var ibl = engine.createIblFromKtx1(buffer);
|
|
511
|
-
ibl.setIntensity(options.iblIntensity || 40000);
|
|
512
|
-
scene.setIndirectLight(ibl);
|
|
513
|
-
if (options.skybox !== false) {
|
|
514
|
-
try {
|
|
515
|
-
var reflections = ibl.getReflectionsTexture();
|
|
516
|
-
if (reflections) {
|
|
517
|
-
scene.setSkybox(Filament.Skybox.Builder().environment(reflections).build(engine));
|
|
518
|
-
console.log('SceneView: Skybox created from IBL cubemap');
|
|
519
|
-
}
|
|
520
|
-
} catch (skyErr) { console.log('SceneView: Skybox not available (IBL-only mode)'); }
|
|
521
|
-
}
|
|
522
|
-
console.log('SceneView: KTX IBL loaded (' + Math.round(buffer.length / 1024) + 'KB)');
|
|
523
|
-
} catch (e) {
|
|
524
|
-
console.warn('SceneView: createIblFromKtx1 failed, using SH fallback', e);
|
|
525
|
-
_applySyntheticIBL(engine, scene);
|
|
526
|
-
}
|
|
527
|
-
})
|
|
528
|
-
.catch(function() { _applySyntheticIBL(engine, scene); });
|
|
529
|
-
|
|
530
|
-
var loader = engine.createAssetLoader();
|
|
531
|
-
var instance = new SceneViewInstance(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader);
|
|
532
|
-
instance._fov = fov;
|
|
533
|
-
|
|
534
|
-
if (options.autoRotate === false) instance.setAutoRotate(false);
|
|
535
|
-
if (options.quality) instance.setQuality(options.quality);
|
|
536
|
-
if (options.bloom) instance.setBloom(typeof options.bloom === 'object' ? options.bloom : {});
|
|
537
|
-
if (options.fog) instance.setFog(typeof options.fog === 'object' ? options.fog : {});
|
|
538
|
-
if (options.vignette) instance.setVignette(typeof options.vignette === 'object' ? options.vignette : {});
|
|
539
|
-
if (options.dof) instance.setDOF(typeof options.dof === 'object' ? options.dof : {});
|
|
540
|
-
if (options.msaa) instance.setMSAA(typeof options.msaa === 'object' ? options.msaa : {});
|
|
541
|
-
|
|
542
|
-
return instance;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function _applySyntheticIBL(engine, scene) {
|
|
546
|
-
try {
|
|
547
|
-
var ibl = Filament.IndirectLight.Builder()
|
|
548
|
-
.irradiance(3, [
|
|
549
|
-
0.65, 0.65, 0.70, 0.10, 0.10, 0.12, 0.15, 0.15, 0.18,
|
|
550
|
-
-0.02, -0.02, -0.01, 0.04, 0.04, 0.05, 0.08, 0.08, 0.10,
|
|
551
|
-
0.01, 0.01, 0.01, -0.02, -0.02, -0.02, 0.03, 0.03, 0.03
|
|
552
|
-
])
|
|
553
|
-
.intensity(35000).build(engine);
|
|
554
|
-
scene.setIndirectLight(ibl);
|
|
555
|
-
console.log('SceneView: Using synthetic SH IBL');
|
|
556
|
-
} catch (e) {}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function create(canvasOrId, options) {
|
|
560
|
-
return _ensureFilament().then(function() {
|
|
561
|
-
return new Promise(function(resolve, reject) {
|
|
562
|
-
if (typeof Filament.Engine !== 'undefined') {
|
|
563
|
-
try {
|
|
564
|
-
var instance = _createEngine(canvasOrId, options);
|
|
565
|
-
if (instance) resolve(instance); else reject(new Error('SceneView: Canvas already initialized'));
|
|
566
|
-
} catch (e) { reject(e); }
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
Filament.init([], function() {
|
|
570
|
-
try {
|
|
571
|
-
var instance = _createEngine(canvasOrId, options);
|
|
572
|
-
if (instance) resolve(instance); else reject(new Error('SceneView: Canvas already initialized'));
|
|
573
|
-
} catch (e) { reject(e); }
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
function modelViewer(canvasOrId, modelUrl, options) {
|
|
580
|
-
return create(canvasOrId, options).then(function(instance) {
|
|
581
|
-
return instance.loadModel(modelUrl);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
global.SceneView = {
|
|
586
|
-
version: '1.5.0',
|
|
587
|
-
create: create,
|
|
588
|
-
modelViewer: modelViewer
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
})(typeof globalThis !== 'undefined' ? globalThis : window);
|