@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.cjs CHANGED
@@ -60,6 +60,187 @@ var EnvironmentController = class {
60
60
  }
61
61
  };
62
62
  //#endregion
63
+ //#region src/builders/ktx2.ts
64
+ let loader = null;
65
+ /**
66
+ * Configure the shared KTX2 loader. Called by the kernel once the renderer
67
+ * exists; `.ktx2` textures and glTF models then decode through it.
68
+ */
69
+ const configureKtx2 = (renderer, path) => {
70
+ loader = import("three/examples/jsm/loaders/KTX2Loader.js").then(({ KTX2Loader }) => {
71
+ const instance = new KTX2Loader();
72
+ instance.setTranscoderPath(path);
73
+ instance.detectSupport(renderer);
74
+ return instance;
75
+ });
76
+ };
77
+ /** The configured loader, or null when the gallery did not set `ktx2Path`. */
78
+ const getKtx2Loader = () => loader;
79
+ //#endregion
80
+ //#region src/builders/textureCache.ts
81
+ /** Two surfaces can share a texture only at the same tiling and color space. */
82
+ const textureKey = (src, repeat, srgb) => `${src}|${repeat[0]}x${repeat[1]}|${srgb ? "srgb" : "linear"}`;
83
+ /** Tiling and color space of a texture; the image arrives later from the loader. */
84
+ const configureTexture = (texture, repeat, srgb) => {
85
+ texture.wrapS = three.RepeatWrapping;
86
+ texture.wrapT = three.RepeatWrapping;
87
+ texture.repeat.set(repeat[0], repeat[1]);
88
+ texture.colorSpace = srgb ? three.SRGBColorSpace : three.NoColorSpace;
89
+ };
90
+ /**
91
+ * Shares textures between materials: the same source at the same tiling and
92
+ * color space becomes one texture, loaded once and uploaded to the GPU once.
93
+ * Reference counted, so releasing a material keeps the texture alive while
94
+ * another one still samples it.
95
+ */
96
+ var TextureCache = class {
97
+ source;
98
+ entries = /* @__PURE__ */ new Map();
99
+ owners = /* @__PURE__ */ new Map();
100
+ constructor(source) {
101
+ this.source = source;
102
+ }
103
+ /** The shared texture for a source, created on first use. */
104
+ acquire(src, repeat, srgb) {
105
+ const key = textureKey(src, repeat, srgb);
106
+ const existing = this.entries.get(key);
107
+ if (existing) {
108
+ existing.refs += 1;
109
+ return existing.texture;
110
+ }
111
+ const texture = this.source(src);
112
+ configureTexture(texture, repeat, srgb);
113
+ const entry = {
114
+ key,
115
+ texture,
116
+ refs: 1
117
+ };
118
+ this.entries.set(key, entry);
119
+ this.owners.set(texture, entry);
120
+ return texture;
121
+ }
122
+ /** Drop one reference; the texture is disposed when the last one goes. */
123
+ release(texture) {
124
+ const entry = this.owners.get(texture);
125
+ if (!entry) {
126
+ texture.dispose();
127
+ return;
128
+ }
129
+ entry.refs -= 1;
130
+ if (entry.refs > 0) return;
131
+ this.entries.delete(entry.key);
132
+ this.owners.delete(texture);
133
+ texture.dispose();
134
+ }
135
+ /** Textures currently shared. */
136
+ get size() {
137
+ return this.entries.size;
138
+ }
139
+ };
140
+ //#endregion
141
+ //#region src/builders/texture.ts
142
+ const textureLoader = new three.TextureLoader();
143
+ const cache = new TextureCache((src) => textureLoader.load(src));
144
+ const ktx2Entries = /* @__PURE__ */ new Map();
145
+ const ktx2Owners = /* @__PURE__ */ new Map();
146
+ /**
147
+ * KTX2 textures resolve asynchronously, so the material slot is filled through
148
+ * a waiter list. One entry per source and tiling is shared by every material
149
+ * that samples it, just like the image cache.
150
+ */
151
+ const loadKtx2 = (material, slot, src, repeat, srgb) => {
152
+ const loader = getKtx2Loader();
153
+ if (!loader) {
154
+ console.warn(`KTX2 texture "${src}" needs the gallery's ktx2Path option.`);
155
+ return;
156
+ }
157
+ const key = textureKey(src, repeat, srgb);
158
+ let entry = ktx2Entries.get(key);
159
+ if (!entry) {
160
+ entry = {
161
+ key,
162
+ refs: 0,
163
+ waiters: []
164
+ };
165
+ ktx2Entries.set(key, entry);
166
+ const created = entry;
167
+ loader.then((ktx2) => ktx2.load(src, (texture) => {
168
+ configureTexture(texture, repeat, srgb);
169
+ texture.needsUpdate = true;
170
+ created.texture = texture;
171
+ ktx2Owners.set(texture, created);
172
+ for (const assign of created.waiters) assign(texture);
173
+ created.waiters = [];
174
+ }, void 0, (error) => console.error(`Failed to load KTX2 texture "${src}".`, error)));
175
+ }
176
+ entry.refs += 1;
177
+ if (entry.texture) {
178
+ material[slot] = entry.texture;
179
+ material.needsUpdate = true;
180
+ } else entry.waiters.push((texture) => {
181
+ material[slot] = texture;
182
+ material.needsUpdate = true;
183
+ });
184
+ };
185
+ /**
186
+ * Load one map into `material[slot]`. Textures are shared: every material that
187
+ * samples the same source at the same tiling reuses one texture object. `.exr`
188
+ * maps go through EXRLoader, imported on demand, and are not shared because
189
+ * they resolve asynchronously; `.ktx2` maps share through their own cache.
190
+ */
191
+ const loadMap = (material, slot, src, repeat, srgb) => {
192
+ if (src.toLowerCase().endsWith(".ktx2")) {
193
+ loadKtx2(material, slot, src, repeat, srgb);
194
+ return;
195
+ }
196
+ if (src.toLowerCase().endsWith(".exr")) {
197
+ import("three/examples/jsm/loaders/EXRLoader.js").then(({ EXRLoader }) => {
198
+ new EXRLoader().load(src, (texture) => {
199
+ configureTexture(texture, repeat, srgb);
200
+ texture.needsUpdate = true;
201
+ material[slot] = texture;
202
+ material.needsUpdate = true;
203
+ });
204
+ });
205
+ return;
206
+ }
207
+ material[slot] = cache.acquire(src, repeat, srgb);
208
+ };
209
+ /**
210
+ * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
211
+ * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
212
+ * provide one.
213
+ */
214
+ const applyTexture = (material, texture) => {
215
+ const repeat = texture.repeat ?? [1, 1];
216
+ loadMap(material, "map", texture.src, repeat, true);
217
+ if (texture.normal) {
218
+ loadMap(material, "normalMap", texture.normal, repeat, false);
219
+ const scale = texture.normalScale ?? 1;
220
+ material.normalScale = new three.Vector2(scale, scale);
221
+ }
222
+ if (texture.roughness) loadMap(material, "roughnessMap", texture.roughness, repeat, false);
223
+ if (texture.ao) loadMap(material, "aoMap", texture.ao, repeat, false);
224
+ material.needsUpdate = true;
225
+ return texture.ao !== void 0;
226
+ };
227
+ /**
228
+ * Release one reference to a texture assigned by `applyTexture`. Shared
229
+ * textures stay alive while another material still samples them.
230
+ */
231
+ const releaseTexture = (texture) => {
232
+ const ktx2 = ktx2Owners.get(texture);
233
+ if (!ktx2) {
234
+ cache.release(texture);
235
+ return;
236
+ }
237
+ ktx2.refs -= 1;
238
+ if (ktx2.refs > 0) return;
239
+ ktx2Owners.delete(texture);
240
+ ktx2Entries.delete(ktx2.key);
241
+ texture.dispose();
242
+ };
243
+ //#endregion
63
244
  //#region src/interaction/InteractionController.ts
64
245
  const HIGHLIGHT_COLOR = "#ef4444";
65
246
  const HIGHLIGHT_THICKNESS = 1.5;
@@ -503,116 +684,6 @@ const EDGES = "edges";
503
684
  */
504
685
  const EYE_HEIGHT = "eyeHeight";
505
686
  //#endregion
506
- //#region src/builders/textureCache.ts
507
- /** Two surfaces can share a texture only at the same tiling and color space. */
508
- const textureKey = (src, repeat, srgb) => `${src}|${repeat[0]}x${repeat[1]}|${srgb ? "srgb" : "linear"}`;
509
- /** Tiling and color space of a texture; the image arrives later from the loader. */
510
- const configureTexture = (texture, repeat, srgb) => {
511
- texture.wrapS = three.RepeatWrapping;
512
- texture.wrapT = three.RepeatWrapping;
513
- texture.repeat.set(repeat[0], repeat[1]);
514
- texture.colorSpace = srgb ? three.SRGBColorSpace : three.NoColorSpace;
515
- };
516
- /**
517
- * Shares textures between materials: the same source at the same tiling and
518
- * color space becomes one texture, loaded once and uploaded to the GPU once.
519
- * Reference counted, so releasing a material keeps the texture alive while
520
- * another one still samples it.
521
- */
522
- var TextureCache = class {
523
- source;
524
- entries = /* @__PURE__ */ new Map();
525
- owners = /* @__PURE__ */ new Map();
526
- constructor(source) {
527
- this.source = source;
528
- }
529
- /** The shared texture for a source, created on first use. */
530
- acquire(src, repeat, srgb) {
531
- const key = textureKey(src, repeat, srgb);
532
- const existing = this.entries.get(key);
533
- if (existing) {
534
- existing.refs += 1;
535
- return existing.texture;
536
- }
537
- const texture = this.source(src);
538
- configureTexture(texture, repeat, srgb);
539
- const entry = {
540
- key,
541
- texture,
542
- refs: 1
543
- };
544
- this.entries.set(key, entry);
545
- this.owners.set(texture, entry);
546
- return texture;
547
- }
548
- /** Drop one reference; the texture is disposed when the last one goes. */
549
- release(texture) {
550
- const entry = this.owners.get(texture);
551
- if (!entry) {
552
- texture.dispose();
553
- return;
554
- }
555
- entry.refs -= 1;
556
- if (entry.refs > 0) return;
557
- this.entries.delete(entry.key);
558
- this.owners.delete(texture);
559
- texture.dispose();
560
- }
561
- /** Textures currently shared. */
562
- get size() {
563
- return this.entries.size;
564
- }
565
- };
566
- //#endregion
567
- //#region src/builders/texture.ts
568
- const textureLoader = new three.TextureLoader();
569
- const cache = new TextureCache((src) => textureLoader.load(src));
570
- /**
571
- * Load one map into `material[slot]`. Textures are shared: every material that
572
- * samples the same source at the same tiling reuses one texture object. `.exr`
573
- * maps go through EXRLoader, imported on demand, and are not shared because
574
- * they resolve asynchronously.
575
- */
576
- const loadMap = (material, slot, src, repeat, srgb) => {
577
- if (src.toLowerCase().endsWith(".exr")) {
578
- import("three/examples/jsm/loaders/EXRLoader.js").then(({ EXRLoader }) => {
579
- new EXRLoader().load(src, (texture) => {
580
- configureTexture(texture, repeat, srgb);
581
- texture.needsUpdate = true;
582
- material[slot] = texture;
583
- material.needsUpdate = true;
584
- });
585
- });
586
- return;
587
- }
588
- material[slot] = cache.acquire(src, repeat, srgb);
589
- };
590
- /**
591
- * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
592
- * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
593
- * provide one.
594
- */
595
- const applyTexture = (material, texture) => {
596
- const repeat = texture.repeat ?? [1, 1];
597
- loadMap(material, "map", texture.src, repeat, true);
598
- if (texture.normal) {
599
- loadMap(material, "normalMap", texture.normal, repeat, false);
600
- const scale = texture.normalScale ?? 1;
601
- material.normalScale = new three.Vector2(scale, scale);
602
- }
603
- if (texture.roughness) loadMap(material, "roughnessMap", texture.roughness, repeat, false);
604
- if (texture.ao) loadMap(material, "aoMap", texture.ao, repeat, false);
605
- material.needsUpdate = true;
606
- return texture.ao !== void 0;
607
- };
608
- /**
609
- * Release one reference to a texture assigned by `applyTexture`. Shared
610
- * textures stay alive while another material still samples them.
611
- */
612
- const releaseTexture = (texture) => {
613
- cache.release(texture);
614
- };
615
- //#endregion
616
687
  //#region src/SceneManager.ts
617
688
  const TEXTURE_SLOTS = [
618
689
  "map",
@@ -656,12 +727,24 @@ var SceneManager = class {
656
727
  root;
657
728
  plugins;
658
729
  capabilities;
730
+ view;
659
731
  /** Mounted plugin elements, per kind, keyed by element id ("" for singletons). */
660
732
  pluginElements = /* @__PURE__ */ new Map();
661
- constructor(root, plugins = [], capabilities = new CapabilityRegistry()) {
733
+ constructor(root, plugins = [], capabilities = new CapabilityRegistry(), view = {
734
+ camera: new three.PerspectiveCamera(),
735
+ renderer: {}
736
+ }) {
662
737
  this.root = root;
663
738
  this.plugins = plugins;
664
739
  this.capabilities = capabilities;
740
+ this.view = view;
741
+ }
742
+ /** The build context handed to every plugin. */
743
+ context() {
744
+ return {
745
+ capabilities: this.capabilities,
746
+ ...this.view
747
+ };
665
748
  }
666
749
  reconcile(scene) {
667
750
  const eye = scene.camera.start.position[1];
@@ -694,7 +777,7 @@ var SceneManager = class {
694
777
  const existing = mounted.get(key);
695
778
  if (existing?.hash === hash) continue;
696
779
  if (existing) this.removePluginElement(mounted, key, existing);
697
- const built = plugin.build(value, { capabilities: this.capabilities });
780
+ const built = plugin.build(value, this.context());
698
781
  if (built.object) this.root.add(built.object);
699
782
  mounted.set(key, {
700
783
  ...built,
@@ -702,7 +785,7 @@ var SceneManager = class {
702
785
  });
703
786
  }
704
787
  for (const [key, entry] of mounted) if (!seen.has(key)) this.removePluginElement(mounted, key, entry);
705
- plugin.provide?.(items.map(([, value]) => value), { capabilities: this.capabilities });
788
+ plugin.provide?.(items.map(([, value]) => value), this.context());
706
789
  }
707
790
  }
708
791
  removePluginElement(mounted, key, entry) {
@@ -793,7 +876,11 @@ function createGallery(scene, options) {
793
876
  const systemPlugins = allPlugins.filter(isSystemPlugin);
794
877
  const elementPlugins = allPlugins.filter((p) => !isSystemPlugin(p));
795
878
  const capabilities = new CapabilityRegistry();
796
- const manager = new SceneManager(threeScene, elementPlugins, capabilities);
879
+ if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);
880
+ const manager = new SceneManager(threeScene, elementPlugins, capabilities, {
881
+ camera,
882
+ renderer
883
+ });
797
884
  manager.reconcile(scene);
798
885
  const input = new InputBus();
799
886
  const controlSettings = new ControlSettings(loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}));
@@ -1108,6 +1195,7 @@ exports.TERRAIN = TERRAIN;
1108
1195
  exports.applyMode = applyMode;
1109
1196
  exports.applyTexture = applyTexture;
1110
1197
  exports.createGallery = createGallery;
1198
+ exports.getKtx2Loader = getKtx2Loader;
1111
1199
  exports.isSystemPlugin = isSystemPlugin;
1112
1200
  exports.modeOf = modeOf;
1113
1201