@scenography/engine 0.5.0 → 0.7.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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { MeshStandardMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
1
+ import { CompressedTexture, MeshStandardMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
2
2
  import { DataPlugin, Scene as Scene$1, SurfaceTexture, Vec2 } from "@scenography/scene";
3
3
 
4
4
  //#region src/camera/api.d.ts
@@ -111,6 +111,15 @@ interface SkyApi {
111
111
  getAzimuth(): number;
112
112
  }
113
113
  //#endregion
114
+ //#region src/loading.d.ts
115
+ interface LoadingState {
116
+ loaded: number;
117
+ total: number;
118
+ done: boolean;
119
+ }
120
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
121
+ declare const trackAsset: (url: string) => void;
122
+ //#endregion
114
123
  //#region src/input/InputBus.d.ts
115
124
  /**
116
125
  * The shared analog-input channel: the current movement and look intent, each
@@ -249,6 +258,10 @@ interface BuiltElement {
249
258
  /** What the kernel hands a plugin while building. */
250
259
  interface EngineContext {
251
260
  capabilities: CapabilityRegistry;
261
+ /** Live camera, for plugins that choose detail by distance. */
262
+ camera: PerspectiveCamera;
263
+ /** Live renderer, for plugins that need GPU capabilities (e.g. KTX2). */
264
+ renderer: WebGLRenderer;
252
265
  }
253
266
  /**
254
267
  * An aggregate controller over all of an element plugin's live objects, exposing
@@ -483,6 +496,11 @@ interface GalleryOptions extends RenderOptions {
483
496
  canvas: HTMLCanvasElement;
484
497
  /** Background color. Defaults to a soft gray. */
485
498
  background?: string;
499
+ /**
500
+ * Base path of the KTX2 transcoder files (`basis_transcoder.js` and
501
+ * `basis_transcoder.wasm`). Without it, `.ktx2` textures are skipped.
502
+ */
503
+ ktx2Path?: string;
486
504
  /**
487
505
  * Runtime plugins: element plugins materialize a scene key; system plugins
488
506
  * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells
@@ -573,6 +591,11 @@ interface Gallery {
573
591
  resize(width: number, height: number): void;
574
592
  /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */
575
593
  setRender(options: RenderOptions): void;
594
+ /** Initial visual asset loading progress. */
595
+ readonly loading: {
596
+ get(): LoadingState;
597
+ subscribe(listener: (state: LoadingState) => void): () => void;
598
+ };
576
599
  readonly ctx: GalleryContext;
577
600
  }
578
601
  /**
@@ -594,9 +617,15 @@ declare class SceneManager {
594
617
  private readonly root;
595
618
  private readonly plugins;
596
619
  private readonly capabilities;
620
+ private readonly view;
597
621
  /** Mounted plugin elements, per kind, keyed by element id ("" for singletons). */
598
622
  private readonly pluginElements;
599
- constructor(root: Scene, plugins?: EnginePlugin[], capabilities?: CapabilityRegistry);
623
+ constructor(root: Scene, plugins?: EnginePlugin[], capabilities?: CapabilityRegistry, view?: {
624
+ camera: PerspectiveCamera;
625
+ renderer: WebGLRenderer;
626
+ });
627
+ /** The build context handed to every plugin. */
628
+ private context;
600
629
  reconcile(scene: Scene$1): void;
601
630
  /**
602
631
  * Reconcile every registered plugin's elements by content: an element
@@ -624,6 +653,14 @@ declare class SceneManager {
624
653
  */
625
654
  declare const applyTexture: (material: MeshStandardMaterial, texture: SurfaceTexture) => boolean;
626
655
  //#endregion
656
+ //#region src/builders/ktx2.d.ts
657
+ /** The part of three's KTX2Loader the engine and plugins use. */
658
+ interface Ktx2Loader {
659
+ load(url: string, onLoad: (texture: CompressedTexture) => void, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): void;
660
+ }
661
+ /** The configured loader, or null when the gallery did not set `ktx2Path`. */
662
+ declare const getKtx2Loader: () => Promise<Ktx2Loader> | null;
663
+ //#endregion
627
664
  //#region src/interaction/InteractionController.d.ts
628
665
  /**
629
666
  * Pointer picking for artworks: hovering a piece's click target (its label, or
@@ -677,5 +714,5 @@ declare class InteractionController {
677
714
  private buildHighlight;
678
715
  }
679
716
  //#endregion
680
- export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, isSystemPlugin, modeOf };
717
+ export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, type LoadingState, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf, trackAsset };
681
718
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,4 +1,47 @@
1
1
  import { BoxGeometry, Clock, EquirectangularReflectionMapping, Group, HalfFloatType, Mesh, MeshBasicMaterial, NoColorSpace, PCFShadowMap, PerspectiveCamera, Quaternion, Raycaster, RepeatWrapping, SRGBColorSpace, Scene, StereoCamera, TextureLoader, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
2
+ //#region src/loading.ts
3
+ const pending = /* @__PURE__ */ new Set();
4
+ const listeners = /* @__PURE__ */ new Set();
5
+ let state = {
6
+ loaded: 0,
7
+ total: 0,
8
+ done: true
9
+ };
10
+ const notify = () => {
11
+ for (const listener of listeners) listener(state);
12
+ };
13
+ /** Begin tracking a new initial asset set; resets the previous one. */
14
+ const startLoading = (urls) => {
15
+ pending.clear();
16
+ for (const url of urls) pending.add(url);
17
+ state = {
18
+ loaded: 0,
19
+ total: pending.size,
20
+ done: pending.size === 0
21
+ };
22
+ notify();
23
+ };
24
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
25
+ const trackAsset = (url) => {
26
+ if (!pending.delete(url)) return;
27
+ state = {
28
+ loaded: state.total - pending.size,
29
+ total: state.total,
30
+ done: pending.size === 0
31
+ };
32
+ notify();
33
+ };
34
+ /** Current loading progress. */
35
+ const loadingState = () => state;
36
+ /** Subscribe to progress; the listener runs immediately with the current state. */
37
+ const onLoading = (listener) => {
38
+ listeners.add(listener);
39
+ listener(state);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ };
43
+ };
44
+ //#endregion
2
45
  //#region src/environment/EnvironmentController.ts
3
46
  /** Load an equirectangular HDRI, picking the loader by extension. */
4
47
  const loadHDRI = (src, onLoad) => {
@@ -30,6 +73,7 @@ var EnvironmentController = class {
30
73
  this.clear();
31
74
  if (!env) return;
32
75
  loadHDRI(env.src, (tex) => {
76
+ trackAsset(env.src);
33
77
  if (this.hash !== hash) {
34
78
  tex.dispose();
35
79
  return;
@@ -59,6 +103,189 @@ var EnvironmentController = class {
59
103
  }
60
104
  };
61
105
  //#endregion
106
+ //#region src/builders/ktx2.ts
107
+ let loader = null;
108
+ /**
109
+ * Configure the shared KTX2 loader. Called by the kernel once the renderer
110
+ * exists; `.ktx2` textures and glTF models then decode through it.
111
+ */
112
+ const configureKtx2 = (renderer, path) => {
113
+ loader = import("three/examples/jsm/loaders/KTX2Loader.js").then(({ KTX2Loader }) => {
114
+ const instance = new KTX2Loader();
115
+ instance.setTranscoderPath(path);
116
+ instance.detectSupport(renderer);
117
+ return instance;
118
+ });
119
+ };
120
+ /** The configured loader, or null when the gallery did not set `ktx2Path`. */
121
+ const getKtx2Loader = () => loader;
122
+ //#endregion
123
+ //#region src/builders/textureCache.ts
124
+ /** Two surfaces can share a texture only at the same tiling and color space. */
125
+ const textureKey = (src, repeat, srgb) => `${src}|${repeat[0]}x${repeat[1]}|${srgb ? "srgb" : "linear"}`;
126
+ /** Tiling and color space of a texture; the image arrives later from the loader. */
127
+ const configureTexture = (texture, repeat, srgb) => {
128
+ texture.wrapS = RepeatWrapping;
129
+ texture.wrapT = RepeatWrapping;
130
+ texture.repeat.set(repeat[0], repeat[1]);
131
+ texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;
132
+ };
133
+ /**
134
+ * Shares textures between materials: the same source at the same tiling and
135
+ * color space becomes one texture, loaded once and uploaded to the GPU once.
136
+ * Reference counted, so releasing a material keeps the texture alive while
137
+ * another one still samples it.
138
+ */
139
+ var TextureCache = class {
140
+ source;
141
+ entries = /* @__PURE__ */ new Map();
142
+ owners = /* @__PURE__ */ new Map();
143
+ constructor(source) {
144
+ this.source = source;
145
+ }
146
+ /** The shared texture for a source, created on first use. */
147
+ acquire(src, repeat, srgb) {
148
+ const key = textureKey(src, repeat, srgb);
149
+ const existing = this.entries.get(key);
150
+ if (existing) {
151
+ existing.refs += 1;
152
+ return existing.texture;
153
+ }
154
+ const texture = this.source(src);
155
+ configureTexture(texture, repeat, srgb);
156
+ const entry = {
157
+ key,
158
+ texture,
159
+ refs: 1
160
+ };
161
+ this.entries.set(key, entry);
162
+ this.owners.set(texture, entry);
163
+ return texture;
164
+ }
165
+ /** Drop one reference; the texture is disposed when the last one goes. */
166
+ release(texture) {
167
+ const entry = this.owners.get(texture);
168
+ if (!entry) {
169
+ texture.dispose();
170
+ return;
171
+ }
172
+ entry.refs -= 1;
173
+ if (entry.refs > 0) return;
174
+ this.entries.delete(entry.key);
175
+ this.owners.delete(texture);
176
+ texture.dispose();
177
+ }
178
+ /** Textures currently shared. */
179
+ get size() {
180
+ return this.entries.size;
181
+ }
182
+ };
183
+ //#endregion
184
+ //#region src/builders/texture.ts
185
+ const textureLoader = new TextureLoader();
186
+ const cache = new TextureCache((src) => textureLoader.load(src, () => trackAsset(src)));
187
+ const ktx2Entries = /* @__PURE__ */ new Map();
188
+ const ktx2Owners = /* @__PURE__ */ new Map();
189
+ /**
190
+ * KTX2 textures resolve asynchronously, so the material slot is filled through
191
+ * a waiter list. One entry per source and tiling is shared by every material
192
+ * that samples it, just like the image cache.
193
+ */
194
+ const loadKtx2 = (material, slot, src, repeat, srgb) => {
195
+ const loader = getKtx2Loader();
196
+ if (!loader) {
197
+ console.warn(`KTX2 texture "${src}" needs the gallery's ktx2Path option.`);
198
+ return;
199
+ }
200
+ const key = textureKey(src, repeat, srgb);
201
+ let entry = ktx2Entries.get(key);
202
+ if (!entry) {
203
+ entry = {
204
+ key,
205
+ refs: 0,
206
+ waiters: []
207
+ };
208
+ ktx2Entries.set(key, entry);
209
+ const created = entry;
210
+ loader.then((ktx2) => ktx2.load(src, (texture) => {
211
+ configureTexture(texture, repeat, srgb);
212
+ texture.needsUpdate = true;
213
+ trackAsset(src);
214
+ created.texture = texture;
215
+ ktx2Owners.set(texture, created);
216
+ for (const assign of created.waiters) assign(texture);
217
+ created.waiters = [];
218
+ }, void 0, (error) => console.error(`Failed to load KTX2 texture "${src}".`, error)));
219
+ }
220
+ entry.refs += 1;
221
+ if (entry.texture) {
222
+ material[slot] = entry.texture;
223
+ material.needsUpdate = true;
224
+ } else entry.waiters.push((texture) => {
225
+ material[slot] = texture;
226
+ material.needsUpdate = true;
227
+ });
228
+ };
229
+ /**
230
+ * Load one map into `material[slot]`. Textures are shared: every material that
231
+ * samples the same source at the same tiling reuses one texture object. `.exr`
232
+ * maps go through EXRLoader, imported on demand, and are not shared because
233
+ * they resolve asynchronously; `.ktx2` maps share through their own cache.
234
+ */
235
+ const loadMap = (material, slot, src, repeat, srgb) => {
236
+ if (src.toLowerCase().endsWith(".ktx2")) {
237
+ loadKtx2(material, slot, src, repeat, srgb);
238
+ return;
239
+ }
240
+ if (src.toLowerCase().endsWith(".exr")) {
241
+ import("three/examples/jsm/loaders/EXRLoader.js").then(({ EXRLoader }) => {
242
+ new EXRLoader().load(src, (texture) => {
243
+ configureTexture(texture, repeat, srgb);
244
+ texture.needsUpdate = true;
245
+ trackAsset(src);
246
+ material[slot] = texture;
247
+ material.needsUpdate = true;
248
+ });
249
+ });
250
+ return;
251
+ }
252
+ material[slot] = cache.acquire(src, repeat, srgb);
253
+ };
254
+ /**
255
+ * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
256
+ * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
257
+ * provide one.
258
+ */
259
+ const applyTexture = (material, texture) => {
260
+ const repeat = texture.repeat ?? [1, 1];
261
+ loadMap(material, "map", texture.src, repeat, true);
262
+ if (texture.normal) {
263
+ loadMap(material, "normalMap", texture.normal, repeat, false);
264
+ const scale = texture.normalScale ?? 1;
265
+ material.normalScale = new Vector2(scale, scale);
266
+ }
267
+ if (texture.roughness) loadMap(material, "roughnessMap", texture.roughness, repeat, false);
268
+ if (texture.ao) loadMap(material, "aoMap", texture.ao, repeat, false);
269
+ material.needsUpdate = true;
270
+ return texture.ao !== void 0;
271
+ };
272
+ /**
273
+ * Release one reference to a texture assigned by `applyTexture`. Shared
274
+ * textures stay alive while another material still samples them.
275
+ */
276
+ const releaseTexture = (texture) => {
277
+ const ktx2 = ktx2Owners.get(texture);
278
+ if (!ktx2) {
279
+ cache.release(texture);
280
+ return;
281
+ }
282
+ ktx2.refs -= 1;
283
+ if (ktx2.refs > 0) return;
284
+ ktx2Owners.delete(texture);
285
+ ktx2Entries.delete(ktx2.key);
286
+ texture.dispose();
287
+ };
288
+ //#endregion
62
289
  //#region src/interaction/InteractionController.ts
63
290
  const HIGHLIGHT_COLOR = "#ef4444";
64
291
  const HIGHLIGHT_THICKNESS = 1.5;
@@ -502,116 +729,6 @@ const EDGES = "edges";
502
729
  */
503
730
  const EYE_HEIGHT = "eyeHeight";
504
731
  //#endregion
505
- //#region src/builders/textureCache.ts
506
- /** Two surfaces can share a texture only at the same tiling and color space. */
507
- const textureKey = (src, repeat, srgb) => `${src}|${repeat[0]}x${repeat[1]}|${srgb ? "srgb" : "linear"}`;
508
- /** Tiling and color space of a texture; the image arrives later from the loader. */
509
- const configureTexture = (texture, repeat, srgb) => {
510
- texture.wrapS = RepeatWrapping;
511
- texture.wrapT = RepeatWrapping;
512
- texture.repeat.set(repeat[0], repeat[1]);
513
- texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;
514
- };
515
- /**
516
- * Shares textures between materials: the same source at the same tiling and
517
- * color space becomes one texture, loaded once and uploaded to the GPU once.
518
- * Reference counted, so releasing a material keeps the texture alive while
519
- * another one still samples it.
520
- */
521
- var TextureCache = class {
522
- source;
523
- entries = /* @__PURE__ */ new Map();
524
- owners = /* @__PURE__ */ new Map();
525
- constructor(source) {
526
- this.source = source;
527
- }
528
- /** The shared texture for a source, created on first use. */
529
- acquire(src, repeat, srgb) {
530
- const key = textureKey(src, repeat, srgb);
531
- const existing = this.entries.get(key);
532
- if (existing) {
533
- existing.refs += 1;
534
- return existing.texture;
535
- }
536
- const texture = this.source(src);
537
- configureTexture(texture, repeat, srgb);
538
- const entry = {
539
- key,
540
- texture,
541
- refs: 1
542
- };
543
- this.entries.set(key, entry);
544
- this.owners.set(texture, entry);
545
- return texture;
546
- }
547
- /** Drop one reference; the texture is disposed when the last one goes. */
548
- release(texture) {
549
- const entry = this.owners.get(texture);
550
- if (!entry) {
551
- texture.dispose();
552
- return;
553
- }
554
- entry.refs -= 1;
555
- if (entry.refs > 0) return;
556
- this.entries.delete(entry.key);
557
- this.owners.delete(texture);
558
- texture.dispose();
559
- }
560
- /** Textures currently shared. */
561
- get size() {
562
- return this.entries.size;
563
- }
564
- };
565
- //#endregion
566
- //#region src/builders/texture.ts
567
- const textureLoader = new TextureLoader();
568
- const cache = new TextureCache((src) => textureLoader.load(src));
569
- /**
570
- * Load one map into `material[slot]`. Textures are shared: every material that
571
- * samples the same source at the same tiling reuses one texture object. `.exr`
572
- * maps go through EXRLoader, imported on demand, and are not shared because
573
- * they resolve asynchronously.
574
- */
575
- const loadMap = (material, slot, src, repeat, srgb) => {
576
- if (src.toLowerCase().endsWith(".exr")) {
577
- import("three/examples/jsm/loaders/EXRLoader.js").then(({ EXRLoader }) => {
578
- new EXRLoader().load(src, (texture) => {
579
- configureTexture(texture, repeat, srgb);
580
- texture.needsUpdate = true;
581
- material[slot] = texture;
582
- material.needsUpdate = true;
583
- });
584
- });
585
- return;
586
- }
587
- material[slot] = cache.acquire(src, repeat, srgb);
588
- };
589
- /**
590
- * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
591
- * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
592
- * provide one.
593
- */
594
- const applyTexture = (material, texture) => {
595
- const repeat = texture.repeat ?? [1, 1];
596
- loadMap(material, "map", texture.src, repeat, true);
597
- if (texture.normal) {
598
- loadMap(material, "normalMap", texture.normal, repeat, false);
599
- const scale = texture.normalScale ?? 1;
600
- material.normalScale = new Vector2(scale, scale);
601
- }
602
- if (texture.roughness) loadMap(material, "roughnessMap", texture.roughness, repeat, false);
603
- if (texture.ao) loadMap(material, "aoMap", texture.ao, repeat, false);
604
- material.needsUpdate = true;
605
- return texture.ao !== void 0;
606
- };
607
- /**
608
- * Release one reference to a texture assigned by `applyTexture`. Shared
609
- * textures stay alive while another material still samples them.
610
- */
611
- const releaseTexture = (texture) => {
612
- cache.release(texture);
613
- };
614
- //#endregion
615
732
  //#region src/SceneManager.ts
616
733
  const TEXTURE_SLOTS = [
617
734
  "map",
@@ -655,12 +772,24 @@ var SceneManager = class {
655
772
  root;
656
773
  plugins;
657
774
  capabilities;
775
+ view;
658
776
  /** Mounted plugin elements, per kind, keyed by element id ("" for singletons). */
659
777
  pluginElements = /* @__PURE__ */ new Map();
660
- constructor(root, plugins = [], capabilities = new CapabilityRegistry()) {
778
+ constructor(root, plugins = [], capabilities = new CapabilityRegistry(), view = {
779
+ camera: new PerspectiveCamera(),
780
+ renderer: {}
781
+ }) {
661
782
  this.root = root;
662
783
  this.plugins = plugins;
663
784
  this.capabilities = capabilities;
785
+ this.view = view;
786
+ }
787
+ /** The build context handed to every plugin. */
788
+ context() {
789
+ return {
790
+ capabilities: this.capabilities,
791
+ ...this.view
792
+ };
664
793
  }
665
794
  reconcile(scene) {
666
795
  const eye = scene.camera.start.position[1];
@@ -693,7 +822,7 @@ var SceneManager = class {
693
822
  const existing = mounted.get(key);
694
823
  if (existing?.hash === hash) continue;
695
824
  if (existing) this.removePluginElement(mounted, key, existing);
696
- const built = plugin.build(value, { capabilities: this.capabilities });
825
+ const built = plugin.build(value, this.context());
697
826
  if (built.object) this.root.add(built.object);
698
827
  mounted.set(key, {
699
828
  ...built,
@@ -701,7 +830,7 @@ var SceneManager = class {
701
830
  });
702
831
  }
703
832
  for (const [key, entry] of mounted) if (!seen.has(key)) this.removePluginElement(mounted, key, entry);
704
- plugin.provide?.(items.map(([, value]) => value), { capabilities: this.capabilities });
833
+ plugin.provide?.(items.map(([, value]) => value), this.context());
705
834
  }
706
835
  }
707
836
  removePluginElement(mounted, key, entry) {
@@ -792,7 +921,20 @@ function createGallery(scene, options) {
792
921
  const systemPlugins = allPlugins.filter(isSystemPlugin);
793
922
  const elementPlugins = allPlugins.filter((p) => !isSystemPlugin(p));
794
923
  const capabilities = new CapabilityRegistry();
795
- const manager = new SceneManager(threeScene, elementPlugins, capabilities);
924
+ if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);
925
+ const assets = /* @__PURE__ */ new Set();
926
+ for (const plugin of elementPlugins) {
927
+ const { kind, cardinality } = plugin.data;
928
+ const raw = scene[kind];
929
+ const values = cardinality === "many" ? Array.isArray(raw) ? raw : [] : raw !== void 0 ? [raw] : [];
930
+ for (const value of values) for (const url of plugin.data.assets?.(value) ?? []) assets.add(url);
931
+ }
932
+ if (scene.environment?.src) assets.add(scene.environment.src);
933
+ startLoading(assets);
934
+ const manager = new SceneManager(threeScene, elementPlugins, capabilities, {
935
+ camera,
936
+ renderer
937
+ });
796
938
  manager.reconcile(scene);
797
939
  const input = new InputBus();
798
940
  const controlSettings = new ControlSettings(loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}));
@@ -1011,6 +1153,10 @@ function createGallery(scene, options) {
1011
1153
  renderer
1012
1154
  }
1013
1155
  },
1156
+ loading: {
1157
+ get: loadingState,
1158
+ subscribe: onLoading
1159
+ },
1014
1160
  setScene(next) {
1015
1161
  manager.reconcile(next);
1016
1162
  interaction.setRadii(radiiOf(next));
@@ -1092,6 +1238,6 @@ function createGallery(scene, options) {
1092
1238
  };
1093
1239
  }
1094
1240
  //#endregion
1095
- export { COLLIDERS, CapabilityRegistry, ControlSettings, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, InputBus, InteractionController, MODE_PRESETS, ROAM_CONTROL, SceneManager, TERRAIN, applyMode, applyTexture, createGallery, isSystemPlugin, modeOf };
1241
+ export { COLLIDERS, CapabilityRegistry, ControlSettings, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, InputBus, InteractionController, MODE_PRESETS, ROAM_CONTROL, SceneManager, TERRAIN, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf, trackAsset };
1096
1242
 
1097
1243
  //# sourceMappingURL=index.mjs.map