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