@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.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,
|
|
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),
|
|
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
|
-
|
|
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
|