@scenography/engine 0.5.0 → 0.6.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.mjs CHANGED
@@ -59,6 +59,187 @@ var EnvironmentController = class {
59
59
  }
60
60
  };
61
61
  //#endregion
62
+ //#region src/builders/ktx2.ts
63
+ let loader = null;
64
+ /**
65
+ * Configure the shared KTX2 loader. Called by the kernel once the renderer
66
+ * exists; `.ktx2` textures and glTF models then decode through it.
67
+ */
68
+ const configureKtx2 = (renderer, path) => {
69
+ loader = import("three/examples/jsm/loaders/KTX2Loader.js").then(({ KTX2Loader }) => {
70
+ const instance = new KTX2Loader();
71
+ instance.setTranscoderPath(path);
72
+ instance.detectSupport(renderer);
73
+ return instance;
74
+ });
75
+ };
76
+ /** The configured loader, or null when the gallery did not set `ktx2Path`. */
77
+ const getKtx2Loader = () => loader;
78
+ //#endregion
79
+ //#region src/builders/textureCache.ts
80
+ /** Two surfaces can share a texture only at the same tiling and color space. */
81
+ const textureKey = (src, repeat, srgb) => `${src}|${repeat[0]}x${repeat[1]}|${srgb ? "srgb" : "linear"}`;
82
+ /** Tiling and color space of a texture; the image arrives later from the loader. */
83
+ const configureTexture = (texture, repeat, srgb) => {
84
+ texture.wrapS = RepeatWrapping;
85
+ texture.wrapT = RepeatWrapping;
86
+ texture.repeat.set(repeat[0], repeat[1]);
87
+ texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;
88
+ };
89
+ /**
90
+ * Shares textures between materials: the same source at the same tiling and
91
+ * color space becomes one texture, loaded once and uploaded to the GPU once.
92
+ * Reference counted, so releasing a material keeps the texture alive while
93
+ * another one still samples it.
94
+ */
95
+ var TextureCache = class {
96
+ source;
97
+ entries = /* @__PURE__ */ new Map();
98
+ owners = /* @__PURE__ */ new Map();
99
+ constructor(source) {
100
+ this.source = source;
101
+ }
102
+ /** The shared texture for a source, created on first use. */
103
+ acquire(src, repeat, srgb) {
104
+ const key = textureKey(src, repeat, srgb);
105
+ const existing = this.entries.get(key);
106
+ if (existing) {
107
+ existing.refs += 1;
108
+ return existing.texture;
109
+ }
110
+ const texture = this.source(src);
111
+ configureTexture(texture, repeat, srgb);
112
+ const entry = {
113
+ key,
114
+ texture,
115
+ refs: 1
116
+ };
117
+ this.entries.set(key, entry);
118
+ this.owners.set(texture, entry);
119
+ return texture;
120
+ }
121
+ /** Drop one reference; the texture is disposed when the last one goes. */
122
+ release(texture) {
123
+ const entry = this.owners.get(texture);
124
+ if (!entry) {
125
+ texture.dispose();
126
+ return;
127
+ }
128
+ entry.refs -= 1;
129
+ if (entry.refs > 0) return;
130
+ this.entries.delete(entry.key);
131
+ this.owners.delete(texture);
132
+ texture.dispose();
133
+ }
134
+ /** Textures currently shared. */
135
+ get size() {
136
+ return this.entries.size;
137
+ }
138
+ };
139
+ //#endregion
140
+ //#region src/builders/texture.ts
141
+ const textureLoader = new TextureLoader();
142
+ const cache = new TextureCache((src) => textureLoader.load(src));
143
+ const ktx2Entries = /* @__PURE__ */ new Map();
144
+ const ktx2Owners = /* @__PURE__ */ new Map();
145
+ /**
146
+ * KTX2 textures resolve asynchronously, so the material slot is filled through
147
+ * a waiter list. One entry per source and tiling is shared by every material
148
+ * that samples it, just like the image cache.
149
+ */
150
+ const loadKtx2 = (material, slot, src, repeat, srgb) => {
151
+ const loader = getKtx2Loader();
152
+ if (!loader) {
153
+ console.warn(`KTX2 texture "${src}" needs the gallery's ktx2Path option.`);
154
+ return;
155
+ }
156
+ const key = textureKey(src, repeat, srgb);
157
+ let entry = ktx2Entries.get(key);
158
+ if (!entry) {
159
+ entry = {
160
+ key,
161
+ refs: 0,
162
+ waiters: []
163
+ };
164
+ ktx2Entries.set(key, entry);
165
+ const created = entry;
166
+ loader.then((ktx2) => ktx2.load(src, (texture) => {
167
+ configureTexture(texture, repeat, srgb);
168
+ texture.needsUpdate = true;
169
+ created.texture = texture;
170
+ ktx2Owners.set(texture, created);
171
+ for (const assign of created.waiters) assign(texture);
172
+ created.waiters = [];
173
+ }, void 0, (error) => console.error(`Failed to load KTX2 texture "${src}".`, error)));
174
+ }
175
+ entry.refs += 1;
176
+ if (entry.texture) {
177
+ material[slot] = entry.texture;
178
+ material.needsUpdate = true;
179
+ } else entry.waiters.push((texture) => {
180
+ material[slot] = texture;
181
+ material.needsUpdate = true;
182
+ });
183
+ };
184
+ /**
185
+ * Load one map into `material[slot]`. Textures are shared: every material that
186
+ * samples the same source at the same tiling reuses one texture object. `.exr`
187
+ * maps go through EXRLoader, imported on demand, and are not shared because
188
+ * they resolve asynchronously; `.ktx2` maps share through their own cache.
189
+ */
190
+ const loadMap = (material, slot, src, repeat, srgb) => {
191
+ if (src.toLowerCase().endsWith(".ktx2")) {
192
+ loadKtx2(material, slot, src, repeat, srgb);
193
+ return;
194
+ }
195
+ if (src.toLowerCase().endsWith(".exr")) {
196
+ import("three/examples/jsm/loaders/EXRLoader.js").then(({ EXRLoader }) => {
197
+ new EXRLoader().load(src, (texture) => {
198
+ configureTexture(texture, repeat, srgb);
199
+ texture.needsUpdate = true;
200
+ material[slot] = texture;
201
+ material.needsUpdate = true;
202
+ });
203
+ });
204
+ return;
205
+ }
206
+ material[slot] = cache.acquire(src, repeat, srgb);
207
+ };
208
+ /**
209
+ * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
210
+ * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
211
+ * provide one.
212
+ */
213
+ const applyTexture = (material, texture) => {
214
+ const repeat = texture.repeat ?? [1, 1];
215
+ loadMap(material, "map", texture.src, repeat, true);
216
+ if (texture.normal) {
217
+ loadMap(material, "normalMap", texture.normal, repeat, false);
218
+ const scale = texture.normalScale ?? 1;
219
+ material.normalScale = new Vector2(scale, scale);
220
+ }
221
+ if (texture.roughness) loadMap(material, "roughnessMap", texture.roughness, repeat, false);
222
+ if (texture.ao) loadMap(material, "aoMap", texture.ao, repeat, false);
223
+ material.needsUpdate = true;
224
+ return texture.ao !== void 0;
225
+ };
226
+ /**
227
+ * Release one reference to a texture assigned by `applyTexture`. Shared
228
+ * textures stay alive while another material still samples them.
229
+ */
230
+ const releaseTexture = (texture) => {
231
+ const ktx2 = ktx2Owners.get(texture);
232
+ if (!ktx2) {
233
+ cache.release(texture);
234
+ return;
235
+ }
236
+ ktx2.refs -= 1;
237
+ if (ktx2.refs > 0) return;
238
+ ktx2Owners.delete(texture);
239
+ ktx2Entries.delete(ktx2.key);
240
+ texture.dispose();
241
+ };
242
+ //#endregion
62
243
  //#region src/interaction/InteractionController.ts
63
244
  const HIGHLIGHT_COLOR = "#ef4444";
64
245
  const HIGHLIGHT_THICKNESS = 1.5;
@@ -502,116 +683,6 @@ const EDGES = "edges";
502
683
  */
503
684
  const EYE_HEIGHT = "eyeHeight";
504
685
  //#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
686
  //#region src/SceneManager.ts
616
687
  const TEXTURE_SLOTS = [
617
688
  "map",
@@ -655,12 +726,24 @@ var SceneManager = class {
655
726
  root;
656
727
  plugins;
657
728
  capabilities;
729
+ view;
658
730
  /** Mounted plugin elements, per kind, keyed by element id ("" for singletons). */
659
731
  pluginElements = /* @__PURE__ */ new Map();
660
- constructor(root, plugins = [], capabilities = new CapabilityRegistry()) {
732
+ constructor(root, plugins = [], capabilities = new CapabilityRegistry(), view = {
733
+ camera: new PerspectiveCamera(),
734
+ renderer: {}
735
+ }) {
661
736
  this.root = root;
662
737
  this.plugins = plugins;
663
738
  this.capabilities = capabilities;
739
+ this.view = view;
740
+ }
741
+ /** The build context handed to every plugin. */
742
+ context() {
743
+ return {
744
+ capabilities: this.capabilities,
745
+ ...this.view
746
+ };
664
747
  }
665
748
  reconcile(scene) {
666
749
  const eye = scene.camera.start.position[1];
@@ -693,7 +776,7 @@ var SceneManager = class {
693
776
  const existing = mounted.get(key);
694
777
  if (existing?.hash === hash) continue;
695
778
  if (existing) this.removePluginElement(mounted, key, existing);
696
- const built = plugin.build(value, { capabilities: this.capabilities });
779
+ const built = plugin.build(value, this.context());
697
780
  if (built.object) this.root.add(built.object);
698
781
  mounted.set(key, {
699
782
  ...built,
@@ -701,7 +784,7 @@ var SceneManager = class {
701
784
  });
702
785
  }
703
786
  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 });
787
+ plugin.provide?.(items.map(([, value]) => value), this.context());
705
788
  }
706
789
  }
707
790
  removePluginElement(mounted, key, entry) {
@@ -792,7 +875,11 @@ function createGallery(scene, options) {
792
875
  const systemPlugins = allPlugins.filter(isSystemPlugin);
793
876
  const elementPlugins = allPlugins.filter((p) => !isSystemPlugin(p));
794
877
  const capabilities = new CapabilityRegistry();
795
- const manager = new SceneManager(threeScene, elementPlugins, capabilities);
878
+ if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);
879
+ const manager = new SceneManager(threeScene, elementPlugins, capabilities, {
880
+ camera,
881
+ renderer
882
+ });
796
883
  manager.reconcile(scene);
797
884
  const input = new InputBus();
798
885
  const controlSettings = new ControlSettings(loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}));
@@ -1092,6 +1179,6 @@ function createGallery(scene, options) {
1092
1179
  };
1093
1180
  }
1094
1181
  //#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 };
1182
+ export { COLLIDERS, CapabilityRegistry, ControlSettings, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, InputBus, InteractionController, MODE_PRESETS, ROAM_CONTROL, SceneManager, TERRAIN, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf };
1096
1183
 
1097
1184
  //# sourceMappingURL=index.mjs.map