@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 +202 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -3
- package/dist/index.d.mts +26 -3
- package/dist/index.mjs +202 -115
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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,
|
|
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),
|
|
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
|
-
|
|
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
|
|