sceneview-web 1.4.0 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sceneview-web",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "One-liner 3D for the web — SceneView.modelViewer('canvas', 'model.glb'). Powered by Filament.js WASM.",
5
5
  "main": "sceneview.js",
6
6
  "author": "SceneView Tools",
package/sceneview.js CHANGED
@@ -7,22 +7,15 @@
7
7
  * Powered by Filament.js v1.70.1 (Google's PBR renderer, WASM).
8
8
  * https://sceneview.github.io
9
9
  *
10
- * @version 1.4.0
10
+ * @version 1.5.0
11
11
  * @license MIT
12
12
  */
13
13
  (function(global) {
14
14
  'use strict';
15
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
16
  function _ensureFilament() {
23
17
  return new Promise(function(resolve, reject) {
24
18
  if (typeof Filament !== 'undefined') { resolve(); return; }
25
- // Poll briefly in case the script tag hasn't finished loading
26
19
  var attempts = 0;
27
20
  var check = setInterval(function() {
28
21
  if (typeof Filament !== 'undefined') { clearInterval(check); resolve(); }
@@ -31,9 +24,6 @@
31
24
  });
32
25
  }
33
26
 
34
- /**
35
- * SceneView instance — wraps Filament engine, scene, camera, renderer.
36
- */
37
27
  class SceneViewInstance {
38
28
  constructor(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader) {
39
29
  this._canvas = canvas;
@@ -46,7 +36,7 @@
46
36
  this._cameraEntity = cameraEntity;
47
37
  this._loader = loader;
48
38
  this._asset = null;
49
- this._angle = 0.785; // Start at ~45° like model-viewer
39
+ this._angle = 0.785;
50
40
  this._autoRotate = true;
51
41
  this._orbitRadius = 3.5;
52
42
  this._orbitHeight = 0.8;
@@ -54,18 +44,19 @@
54
44
  this._running = true;
55
45
  this._isDragging = false;
56
46
  this._lastMouse = { x: 0, y: 0 };
57
- // Inertia for smooth orbit deceleration
58
47
  this._velocityAngle = 0;
59
48
  this._velocityHeight = 0;
60
49
  this._dampingFactor = 0.95;
61
- this._wantsAutoRotate = true; // Remember initial preference for resume after drag
50
+ this._wantsAutoRotate = true;
62
51
  this._autoRotateTimer = null;
52
+ this._userLights = [];
63
53
  this._setupControls();
64
54
  this._setupResizeObserver();
65
55
  this._startRenderLoop();
66
56
  }
67
57
 
68
- /** Load a glTF/GLB model from URL */
58
+ // ── Model loading ──
59
+
69
60
  loadModel(url) {
70
61
  var self = this;
71
62
  return new Promise(function(resolve, reject) {
@@ -74,75 +65,54 @@
74
65
  .then(function(buffer) {
75
66
  Filament.assets = Filament.assets || {};
76
67
  Filament.assets[url] = new Uint8Array(buffer);
77
- try {
78
- self._showModel(url);
79
- resolve(self);
80
- } catch (e) {
81
- reject(e);
82
- }
68
+ try { self._showModel(url); resolve(self); } catch (e) { reject(e); }
83
69
  })
84
70
  .catch(reject);
85
71
  });
86
72
  }
87
73
 
88
74
  _showModel(url) {
89
- // Remove previous model
90
75
  if (this._asset) {
91
76
  try {
92
77
  this._asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
93
78
  this._scene.remove(this._asset.getRoot());
94
- } catch (e) { /* ignore cleanup errors */ }
79
+ } catch (e) {}
95
80
  this._asset = null;
96
81
  }
97
-
98
82
  var data = Filament.assets[url];
99
83
  if (!data) throw new Error('Failed to fetch model: ' + url);
100
-
101
84
  var asset = this._loader.createAsset(data);
102
85
  if (!asset) throw new Error('Failed to parse model: ' + url);
103
-
104
86
  asset.loadResources();
105
87
  this._scene.addEntity(asset.getRoot());
106
88
  this._scene.addEntities(asset.getRenderableEntities());
107
89
  this._asset = asset;
90
+ this._autoFrame(asset);
91
+ }
108
92
 
109
- // Auto-frame the model
93
+ _autoFrame(asset) {
110
94
  try {
111
95
  var bbox = asset.getBoundingBox();
112
96
  var cx = (bbox.min[0] + bbox.max[0]) / 2;
113
97
  var cy = (bbox.min[1] + bbox.max[1]) / 2;
114
98
  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);
99
+ var maxDim = Math.max(bbox.max[0] - bbox.min[0], bbox.max[1] - bbox.min[1], bbox.max[2] - bbox.min[2]);
119
100
  if (maxDim > 0) {
120
101
  this._orbitTarget = [cx, cy, cz];
121
- // Tighter framing than before (1.8x instead of 2.5x)
122
102
  this._orbitRadius = maxDim * 1.8;
123
103
  this._orbitHeight = cy;
124
104
  }
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;
105
+ } catch (e) {}
134
106
  }
135
107
 
136
- /** Add a model to the scene (without removing existing ones) */
137
108
  addModel(url) {
138
109
  var self = this;
139
110
  return new Promise(function(resolve, reject) {
140
111
  fetch(url)
141
112
  .then(function(resp) { return resp.arrayBuffer(); })
142
113
  .then(function(buffer) {
143
- var data = new Uint8Array(buffer);
144
114
  try {
145
- var asset = self._loader.createAsset(data);
115
+ var asset = self._loader.createAsset(new Uint8Array(buffer));
146
116
  if (!asset) { reject(new Error('Failed to parse: ' + url)); return; }
147
117
  asset.loadResources();
148
118
  self._scene.addEntity(asset.getRoot());
@@ -154,8 +124,7 @@
154
124
  });
155
125
  }
156
126
 
157
- /** Load a GLB from a Uint8Array buffer directly */
158
- loadGLBBuffer(buffer, key) {
127
+ loadGLBBuffer(buffer) {
159
128
  var asset = this._loader.createAsset(buffer);
160
129
  if (!asset) return null;
161
130
  asset.loadResources();
@@ -164,23 +133,191 @@
164
133
  return asset;
165
134
  }
166
135
 
167
- /** Remove an asset from the scene */
168
136
  removeAsset(asset) {
169
137
  if (!asset) return;
170
138
  try {
171
139
  asset.getRenderableEntities().forEach(function(e) { this._scene.remove(e); }.bind(this));
172
140
  this._scene.remove(asset.getRoot());
173
- } catch (e) { /* ignore cleanup errors */ }
141
+ } catch (e) {}
174
142
  }
175
143
 
176
- /** Access engine for advanced Filament operations */
177
144
  get engine() { return this._engine; }
178
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 ──
179
315
 
180
316
  dispose() {
181
317
  this._running = false;
182
318
  if (this._resizeObserver) this._resizeObserver.disconnect();
183
- try { Filament.Engine.destroy(this._engine); } catch (e) { /* already destroyed */ }
319
+ _activeCanvases.delete(this._canvas);
320
+ try { Filament.Engine.destroy(this._engine); } catch (e) {}
184
321
  }
185
322
 
186
323
  _setupControls() {
@@ -207,7 +344,6 @@
207
344
  });
208
345
  canvas.addEventListener('mouseup', function() {
209
346
  self._isDragging = false;
210
- // Resume auto-rotate after 3s idle (like model-viewer)
211
347
  if (self._wantsAutoRotate) {
212
348
  self._autoRotateTimer = setTimeout(function() { self._autoRotate = true; }, 3000);
213
349
  }
@@ -222,7 +358,9 @@
222
358
  canvas.addEventListener('wheel', function(e) {
223
359
  e.preventDefault();
224
360
  self._orbitRadius *= (1 + e.deltaY * 0.001);
225
- self._orbitRadius = Math.max(0.5, Math.min(50, self._orbitRadius));
361
+ var minR = self._minRadius || 0.5;
362
+ var maxR = self._maxRadius || 50;
363
+ self._orbitRadius = Math.max(minR, Math.min(maxR, self._orbitRadius));
226
364
  }, { passive: false });
227
365
 
228
366
  canvas.addEventListener('touchstart', function(e) {
@@ -258,7 +396,7 @@
258
396
  var self = this;
259
397
  this._resizeObserver = new ResizeObserver(function() {
260
398
  var canvas = self._canvas;
261
- var dpr = Math.min(devicePixelRatio, 2); // Cap at 2x for performance
399
+ var dpr = Math.min(devicePixelRatio, 2);
262
400
  canvas.width = canvas.clientWidth * dpr;
263
401
  canvas.height = canvas.clientHeight * dpr;
264
402
  self._view.setViewport([0, 0, canvas.width, canvas.height]);
@@ -274,12 +412,8 @@
274
412
  var self = this;
275
413
  function render() {
276
414
  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) {
415
+ if (self._autoRotate) self._angle += (self._orbitSpeed || 0.00873);
416
+ if (!self._isDragging && !self._cameraAnimating) {
283
417
  self._angle += self._velocityAngle;
284
418
  self._orbitHeight += self._velocityHeight;
285
419
  self._velocityAngle *= self._dampingFactor;
@@ -287,16 +421,13 @@
287
421
  if (Math.abs(self._velocityAngle) < 0.00005) self._velocityAngle = 0;
288
422
  if (Math.abs(self._velocityHeight) < 0.00005) self._velocityHeight = 0;
289
423
  }
290
-
291
424
  var t = self._orbitTarget;
292
425
  var r = self._orbitRadius;
293
426
  var h = self._orbitHeight;
294
427
  self._camera.lookAt(
295
428
  [t[0] + Math.sin(self._angle) * r, h, t[2] + Math.cos(self._angle) * r],
296
- t,
297
- [0, 1, 0]
429
+ t, [0, 1, 0]
298
430
  );
299
-
300
431
  self._engine.execute();
301
432
  try {
302
433
  if (self._renderer.beginFrame(self._swapChain)) {
@@ -304,7 +435,6 @@
304
435
  self._renderer.endFrame();
305
436
  }
306
437
  } catch (e) {
307
- // Filament 1.70 may need different render call
308
438
  console.error('SceneView render error:', e.message);
309
439
  self._running = false;
310
440
  }
@@ -314,29 +444,16 @@
314
444
  }
315
445
  }
316
446
 
317
- // Singleton guard — prevent multiple engine creations on same canvas
318
447
  var _activeCanvases = new Set();
319
448
 
320
- /**
321
- * Set up Filament engine, scene, lights on a canvas.
322
- */
323
449
  function _createEngine(canvasOrId, options) {
324
450
  options = options || {};
325
-
326
- var canvas = typeof canvasOrId === 'string'
327
- ? document.getElementById(canvasOrId)
328
- : canvasOrId;
451
+ var canvas = typeof canvasOrId === 'string' ? document.getElementById(canvasOrId) : canvasOrId;
329
452
  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
- }
453
+ if (_activeCanvases.has(canvas)) { console.warn('SceneView: Canvas already initialized, skipping'); return null; }
336
454
  _activeCanvases.add(canvas);
337
455
 
338
456
  var dpr = Math.min(devicePixelRatio, 2);
339
- // Ensure canvas has actual layout dimensions (not default 300x150)
340
457
  var cssW = canvas.clientWidth || canvas.offsetWidth || 500;
341
458
  var cssH = canvas.clientHeight || canvas.offsetHeight || 500;
342
459
  canvas.width = cssW * dpr;
@@ -361,45 +478,27 @@
361
478
  camera.setProjectionFov(fov, canvas.width / canvas.height, 0.1, 1000, Filament.Camera$Fov.VERTICAL);
362
479
  camera.lookAt([0, 1, 5], [0, 0, 0], [0, 1, 0]);
363
480
 
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 */ }
481
+ try { view.setAmbientOcclusionOptions({ enabled: true, radius: 0.3, bias: 0.0005, intensity: 1.0, quality: 1 }); } catch (e) {}
370
482
 
371
- // --- 3-point studio lighting ---
372
- // Sun/key light — warm, strong
373
483
  var sun = Filament.EntityManager.get().create();
374
484
  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)
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)
381
487
  .build(engine, sun);
382
488
  scene.addEntity(sun);
383
489
 
384
- // Fill light — cool, softer
385
490
  var fill = Filament.EntityManager.get().create();
386
491
  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])
492
+ .color([0.7, 0.75, 0.9]).intensity(60000).direction([-0.5, 0.5, 1.0])
390
493
  .build(engine, fill);
391
494
  scene.addEntity(fill);
392
495
 
393
- // Back/rim light — edge highlight
394
496
  var back = Filament.EntityManager.get().create();
395
497
  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])
498
+ .color([0.5, 0.6, 0.9]).intensity(50000).direction([0, 0.3, 1.0])
399
499
  .build(engine, back);
400
500
  scene.addEntity(back);
401
501
 
402
- // --- IBL: load real KTX if available, fallback to synthetic SH ---
403
502
  var iblUrl = options.iblUrl || 'environments/neutral_ibl.ktx';
404
503
  fetch(iblUrl)
405
504
  .then(function(r) {
@@ -411,21 +510,14 @@
411
510
  var ibl = engine.createIblFromKtx1(buffer);
412
511
  ibl.setIntensity(options.iblIntensity || 40000);
413
512
  scene.setIndirectLight(ibl);
414
- // Create skybox from IBL reflection cubemap if skybox enabled
415
513
  if (options.skybox !== false) {
416
514
  try {
417
515
  var reflections = ibl.getReflectionsTexture();
418
516
  if (reflections) {
419
- var skybox = Filament.Skybox.Builder()
420
- .environment(reflections)
421
- .build(engine);
422
- scene.setSkybox(skybox);
517
+ scene.setSkybox(Filament.Skybox.Builder().environment(reflections).build(engine));
423
518
  console.log('SceneView: Skybox created from IBL cubemap');
424
519
  }
425
- } catch (skyErr) {
426
- // Skybox not supported in this build — that's OK
427
- console.log('SceneView: Skybox not available (IBL-only mode)');
428
- }
520
+ } catch (skyErr) { console.log('SceneView: Skybox not available (IBL-only mode)'); }
429
521
  }
430
522
  console.log('SceneView: KTX IBL loaded (' + Math.round(buffer.length / 1024) + 'KB)');
431
523
  } catch (e) {
@@ -433,39 +525,35 @@
433
525
  _applySyntheticIBL(engine, scene);
434
526
  }
435
527
  })
436
- .catch(function() {
437
- _applySyntheticIBL(engine, scene);
438
- });
528
+ .catch(function() { _applySyntheticIBL(engine, scene); });
439
529
 
440
530
  var loader = engine.createAssetLoader();
441
531
  var instance = new SceneViewInstance(canvas, engine, scene, renderer, view, swapChain, camera, cameraEntity, loader);
442
532
  instance._fov = fov;
443
533
 
444
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 : {});
445
541
 
446
542
  return instance;
447
543
  }
448
544
 
449
- /** Fallback IBL from spherical harmonics when KTX not available */
450
545
  function _applySyntheticIBL(engine, scene) {
451
546
  try {
452
547
  var ibl = Filament.IndirectLight.Builder()
453
548
  .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
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
463
552
  ])
464
- .intensity(35000)
465
- .build(engine);
553
+ .intensity(35000).build(engine);
466
554
  scene.setIndirectLight(ibl);
467
555
  console.log('SceneView: Using synthetic SH IBL');
468
- } catch (e) { /* skip */ }
556
+ } catch (e) {}
469
557
  }
470
558
 
471
559
  function create(canvasOrId, options) {
@@ -474,16 +562,14 @@
474
562
  if (typeof Filament.Engine !== 'undefined') {
475
563
  try {
476
564
  var instance = _createEngine(canvasOrId, options);
477
- if (instance) resolve(instance);
478
- else reject(new Error('SceneView: Canvas already initialized'));
565
+ if (instance) resolve(instance); else reject(new Error('SceneView: Canvas already initialized'));
479
566
  } catch (e) { reject(e); }
480
567
  return;
481
568
  }
482
569
  Filament.init([], function() {
483
570
  try {
484
571
  var instance = _createEngine(canvasOrId, options);
485
- if (instance) resolve(instance);
486
- else reject(new Error('SceneView: Canvas already initialized'));
572
+ if (instance) resolve(instance); else reject(new Error('SceneView: Canvas already initialized'));
487
573
  } catch (e) { reject(e); }
488
574
  });
489
575
  });
@@ -497,7 +583,7 @@
497
583
  }
498
584
 
499
585
  global.SceneView = {
500
- version: '1.4.0',
586
+ version: '1.5.0',
501
587
  create: create,
502
588
  modelViewer: modelViewer
503
589
  };