@scenography/engine 0.1.0 → 0.3.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.d.cts CHANGED
@@ -1,21 +1,546 @@
1
- import { Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
2
- import { Artwork, Scene as Scene$1, Wall } from "@scenography/core";
1
+ import { MeshStandardMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
2
+ import { DataPlugin, Scene as Scene$1, SurfaceTexture, Vec2 } from "@scenography/scene";
3
3
 
4
+ //#region src/camera/api.d.ts
5
+ /** Options for framing an artwork on screen. */
6
+ interface FocusOptions {
7
+ /** Horizontal screen position of the piece's center, `0`=left … `1`=right. Default `0.5`. */
8
+ centerX?: number;
9
+ /** Fraction of the viewport width available for the piece (e.g. `0.6`). Default `1`. */
10
+ widthFraction?: number;
11
+ /** Tween duration in ms. Default `900`. */
12
+ duration?: number;
13
+ /**
14
+ * Minimum camera distance from the piece, in world units. When the framing that
15
+ * fits the piece would sit closer, the camera pulls back to this instead (the
16
+ * piece stays centered, just smaller). Default `0` (no minimum). Useful on close
17
+ * so a small piece doesn't leave the visitor glued to the wall.
18
+ */
19
+ minDistance?: number;
20
+ }
21
+ /**
22
+ * Public camera-framing API exposed on the gallery context as `ctx.camera`.
23
+ * Implemented by a framing system plugin (`@scenography/plugin-focus`); when no
24
+ * such plugin is registered the kernel provides a no-op so callers are safe.
25
+ */
26
+ interface CameraApi {
27
+ /** Animate to frame an artwork (by `id`), e.g. offset into the free area beside a panel. */
28
+ focusArtwork(id: string, options?: FocusOptions): void;
29
+ /** Animate to center an artwork full-width (used when an info panel closes). */
30
+ centerArtwork(id: string, options?: FocusOptions): void;
31
+ /** Drop any active framing watch and hand control fully back to the user. */
32
+ release(): void;
33
+ /**
34
+ * Animate the camera back to the scene's start position and orientation — a
35
+ * "home" for visitors who lose their way and want to return to the entrance.
36
+ */
37
+ reset(): void;
38
+ /** Whether a framing tween is in progress (e.g. to suppress click-to-select). */
39
+ isAnimating(): boolean;
40
+ }
41
+ //#endregion
42
+ //#region src/api/index.d.ts
43
+ /**
44
+ * The public `ctx` API contracts implemented by plugins and surfaced on the
45
+ * gallery context. The interfaces live in the kernel (they are the supported
46
+ * surface); the implementations live in the plugins that own each concern.
47
+ */
48
+ interface LightSetOptions {
49
+ /** Fade duration in ms. `0`/omitted applies instantly. */
50
+ duration?: number;
51
+ }
52
+ interface LightSet {
53
+ intensity?: number;
54
+ /** CSS-style color. Applied instantly (not tweened). */
55
+ color?: string;
56
+ }
57
+ /** `ctx.lights` — high-level control over scene lights by id. */
58
+ interface LightsApi {
59
+ /** Set intensity (tweened) and/or color (instant) on a light by `id`. */
60
+ set(id: string, props: LightSet, opts?: LightSetOptions): void;
61
+ /** Dim every light toward `factor` of its current intensity (default `0`). */
62
+ dimAll(opts?: LightSetOptions & {
63
+ factor?: number;
64
+ }): void;
65
+ /** Fade every light back to the intensity captured before the last dim. */
66
+ restore(opts?: LightSetOptions): void;
67
+ }
68
+ /** `ctx.video` — high-level control over video screens by id. */
69
+ interface VideoApi {
70
+ /** Start (or resume) playback of the video with this `id`. */
71
+ play(id: string): void;
72
+ /** Pause the video with this `id`. */
73
+ pause(id: string): void;
74
+ /** Show or hide the screen with this `id`. */
75
+ setVisible(id: string, visible: boolean): void;
76
+ }
77
+ /** `ctx.media` — high-level control over background audio by id. */
78
+ interface MediaController {
79
+ /** Start (or resume) a source by `id`. */
80
+ play(id: string): void;
81
+ /** Pause a source, keeping its position. */
82
+ pause(id: string): void;
83
+ /** Stop a source and rewind it. */
84
+ stop(id: string): void;
85
+ /** Set a source's volume `0`..`1`, optionally fading over `duration` ms. */
86
+ setVolume(id: string, volume: number, opts?: {
87
+ duration?: number;
88
+ }): void;
89
+ /** Whether a source is currently playing. */
90
+ playing(id: string): boolean;
91
+ /** Mute or unmute everything at the master output (per-source volumes are kept). */
92
+ setMuted(muted: boolean): void;
93
+ /** Whether the master output is muted. */
94
+ muted(): boolean;
95
+ }
96
+ /** `ctx.sky` — drive the procedural sky's time of day at runtime. */
97
+ interface SkyApi {
98
+ /** Set the hour of day (`0`..`24`), updating sun, sky, fill and stars live. */
99
+ setTime(hour: number): void;
100
+ /** Current hour of day. */
101
+ getTime(): number;
102
+ /** Set the sun's compass offset (degrees). */
103
+ setAzimuth(degrees: number): void;
104
+ /** Current sun compass offset (degrees). */
105
+ getAzimuth(): number;
106
+ }
107
+ //#endregion
108
+ //#region src/input/InputBus.d.ts
109
+ /**
110
+ * The shared analog-input channel: the current movement and look intent, each
111
+ * axis in `-1..1` and held until a producer changes it. Producers (on-screen
112
+ * joysticks in the overlay, a gamepad…) write to it; whatever camera system is
113
+ * active reads it and interprets it its own way — free-roam walks with it, a
114
+ * fixed camera might only use the look axes to glance around. This is what makes
115
+ * the joysticks reusable across camera types. Pure state — no three.js.
116
+ */
117
+ declare class InputBus {
118
+ /** Forward (`+1`) / backward (`-1`). */
119
+ moveForward: number;
120
+ /** Strafe right (`+1`) / left (`-1`). */
121
+ moveStrafe: number;
122
+ /** Look right (`+1`) / left (`-1`). */
123
+ lookX: number;
124
+ /** Look up (`+1`) / down (`-1`). */
125
+ lookY: number;
126
+ /** Set the movement intent (forward, strafe). */
127
+ setMove(forward: number, strafe?: number): void;
128
+ /** Set the look intent (turn, pitch). */
129
+ setLook(x: number, y?: number): void;
130
+ }
131
+ //#endregion
132
+ //#region src/input/controlSettings.d.ts
133
+ /**
134
+ * The visitor's camera-control preferences: a set of orthogonal capability flags
135
+ * plus a derived, friendly "mode". Pure state — no three.js, no DOM. The engine
136
+ * wires the flags to effects (gyroscope head-look, stereo split); consumers read
137
+ * them (the analog look-invert axes); the overlay drives it from a mode picker
138
+ * (the `ScanEye` button) and the settings panel. Persistence and device
139
+ * feature-detection live in those callers, not here, so this stays trivially
140
+ * unit-testable.
141
+ */
142
+ /**
143
+ * Orthogonal control capabilities. `gyro` / `splitScreen` / `joysticks` are what a
144
+ * mode fixes; the two `invertLook*` axes are independent look-feel preferences
145
+ * (some people feel the right stick inverted), kept separate from the mode.
146
+ */
147
+ interface ControlFlags {
148
+ /** Orient the camera from the device gyroscope (head-look). */
149
+ gyro: boolean;
150
+ /** Split the view into a stereo left/right eye pair (phone-in-a-headset). */
151
+ splitScreen: boolean;
152
+ /** Show the on-screen virtual joysticks. */
153
+ joysticks: boolean;
154
+ /** Invert the horizontal (turn) axis of analog look. */
155
+ invertLookX: boolean;
156
+ /** Invert the vertical (pitch) axis of analog look. */
157
+ invertLookY: boolean;
158
+ }
159
+ /** A named preset over the mode-defining flags; `custom` when none matches. */
160
+ type ControlMode = "touch" | "vr" | "cardboard" | "custom";
161
+ /** The three flags a mode fixes (the look-invert axes stay a separate preference). */
162
+ type ModeFlags = Pick<ControlFlags, "gyro" | "splitScreen" | "joysticks">;
163
+ /**
164
+ * Mode presets. Touch = two joysticks, no gyro. VR = the gyro aims and a single
165
+ * move joystick walks you, no split. Cardboard = gyro aims, stereo split, gaze
166
+ * select (no joysticks). The move-only single stick in VR is derived downstream
167
+ * from `gyro && !splitScreen`, so it needs no flag of its own.
168
+ */
169
+ declare const MODE_PRESETS: Record<Exclude<ControlMode, "custom">, ModeFlags>;
170
+ /** Default flags: plain touch, no look inversion. */
171
+ declare const DEFAULT_CONTROL_FLAGS: ControlFlags;
172
+ /** The mode a flag set represents, or `custom` when no preset matches. */
173
+ declare function modeOf(flags: ControlFlags): ControlMode;
174
+ /** Apply a mode's preset onto `flags`, leaving the look-invert axes untouched. */
175
+ declare function applyMode(flags: ControlFlags, mode: Exclude<ControlMode, "custom">): ControlFlags;
176
+ /**
177
+ * Live control-preferences state: holds the flags, derives the mode, and notifies
178
+ * subscribers on every real change. Callers apply the effects — the engine wires
179
+ * gyro and split, the overlay redraws — so this object is just the shared source
180
+ * of truth they all read and write.
181
+ */
182
+ declare class ControlSettings {
183
+ private flags;
184
+ private readonly subscribers;
185
+ constructor(initial?: Partial<ControlFlags>);
186
+ /** The current flags (a copy — mutate through `setMode` / `setFlag`). */
187
+ get(): ControlFlags;
188
+ /** The mode the current flags represent (`custom` when hand-tuned). */
189
+ getMode(): ControlMode;
190
+ /** Switch to a mode's preset (keeps the look-invert axes). No-op if unchanged. */
191
+ setMode(mode: Exclude<ControlMode, "custom">): void;
192
+ /** Set one flag. Flipping a mode-defining flag can drop you into `custom`. */
193
+ setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;
194
+ /** Subscribe to changes; returns an unsubscribe. Fires on every real change. */
195
+ subscribe(fn: (flags: ControlFlags) => void): () => void;
196
+ private replace;
197
+ }
198
+ //#endregion
199
+ //#region src/plugin/CapabilityRegistry.d.ts
200
+ /**
201
+ * Shared runtime contracts between plugins, keyed by name. A plugin (or the
202
+ * kernel) *provides* a capability — e.g. the floor provides `terrain` — and
203
+ * other plugins *consume* it if present, without depending on the provider's
204
+ * package. Each name keeps a monotonic version counter so consumers can hash
205
+ * their dependencies cheaply and rebuild when a provider changes.
206
+ */
207
+ declare class CapabilityRegistry {
208
+ private readonly values;
209
+ private readonly versions;
210
+ /** Last change-token seen per name, to suppress redundant version bumps. */
211
+ private readonly tokens;
212
+ /**
213
+ * Publish (or replace) the capability under `name`. Pass a `token` (a cheap
214
+ * hash of the inputs) to dedupe: re-providing with an unchanged token is a
215
+ * no-op, so consumers that depend on this capability don't rebuild when the
216
+ * provider recomputes an identical value.
217
+ */
218
+ provide<T>(name: string, value: T, token?: string): void;
219
+ /** Remove the capability; consumers see `undefined` and a version bump. */
220
+ revoke(name: string): void;
221
+ get<T>(name: string): T | undefined;
222
+ /** Monotonic change counter for `name` (`0` if never provided). */
223
+ version(name: string): number;
224
+ private bump;
225
+ }
226
+ //#endregion
227
+ //#region src/plugin/enginePlugin.d.ts
228
+ /** What a plugin materializes for one element: an object plus lifecycle hooks. */
229
+ interface BuiltElement {
230
+ /** Mounted into (and removed from) the three.js scene by the kernel. */
231
+ object?: Object3D;
232
+ /** Advance time-based behavior; called once per frame with the delta in ms. */
233
+ update?: (deltaMs: number) => void;
234
+ /**
235
+ * Render an auxiliary pass before the main frame — e.g. a planar reflection
236
+ * or a portal view rendered into a texture the element's material samples.
237
+ * Called once per frame with the live renderer, scene and camera.
238
+ */
239
+ beforeRender?: (renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera) => void;
240
+ /** Extra cleanup beyond the object's geometries/materials (which the kernel disposes). */
241
+ dispose?: () => void;
242
+ }
243
+ /** What the kernel hands a plugin while building. */
244
+ interface EngineContext {
245
+ capabilities: CapabilityRegistry;
246
+ }
247
+ /**
248
+ * An aggregate controller over all of an element plugin's live objects, exposing
249
+ * a public api (mapped onto `ctx` by the plugin's kind — e.g. the light plugin's
250
+ * `lights` api becomes `ctx.lights`). It reads the elements fresh each call, so
251
+ * it keeps working across hot-swaps.
252
+ */
253
+ interface ElementController<Api = unknown> {
254
+ /** The api surfaced on `ctx`. */
255
+ api?: Api;
256
+ /** Advance time-based control (e.g. light-intensity tweens). */
257
+ update?(deltaMs: number): void;
258
+ /** Tear down. */
259
+ dispose?(): void;
260
+ }
261
+ /**
262
+ * The runtime half of a plugin: embeds its data half (the scene key, its
263
+ * validation) and adds how each element materializes into three.js objects.
264
+ * The kernel reconciles by content: an element rebuilds when its definition
265
+ * changes, or when any capability named in `dependsOn` is re-provided.
266
+ */
267
+ interface EnginePlugin<T = unknown> {
268
+ data: DataPlugin<T>;
269
+ /** Capability names whose changes force a rebuild of this plugin's elements. */
270
+ dependsOn?: readonly string[];
271
+ /** Method syntax on purpose: keeps `EnginePlugin<Water>` assignable to `EnginePlugin[]`. */
272
+ build(value: T, ctx: EngineContext): BuiltElement;
273
+ /**
274
+ * Publish capabilities derived from *all* of this plugin's elements, once
275
+ * they have reconciled — the aggregate a single `build` can't see (e.g. the
276
+ * wall plugin indexing every edge into the `edges` capability). Pass a change
277
+ * token to `ctx.capabilities.provide` so consumers don't rebuild needlessly.
278
+ */
279
+ provide?(values: T[], ctx: EngineContext): void;
280
+ /**
281
+ * Build an aggregate controller over this plugin's live elements (e.g. the
282
+ * light plugin's `ctx.lights`). `elements` returns the current objects by id,
283
+ * queried fresh so it survives hot-swaps.
284
+ */
285
+ controller?(elements: () => ReadonlyMap<string, Object3D>): ElementController;
286
+ }
287
+ //#endregion
288
+ //#region src/plugin/systemPlugin.d.ts
289
+ /**
290
+ * What the kernel hands a system on setup. A *system* plugin — unlike an element
291
+ * plugin — produces no scene object; it drives shared runtime state (the camera,
292
+ * input…) each frame. The camera is a kernel primitive (the renderer needs one),
293
+ * so systems receive it and drive it, publishing/consuming capabilities to
294
+ * cooperate (e.g. the camera plugin publishes `roamControl`).
295
+ */
296
+ interface SystemContext {
297
+ /** The current scene (its data keys, start pose, boundary…); also `setScene` on hot-swap. */
298
+ scene: Scene$1;
299
+ /** The render camera the system may drive. */
300
+ camera: PerspectiveCamera;
301
+ /** The canvas, for pointer/keyboard listeners. */
302
+ canvas: HTMLElement;
303
+ /** The three.js scene graph, for systems that manage their own objects (sky…). */
304
+ scene3d: Scene;
305
+ /** The renderer, for systems that need it (e.g. sky IBL generation). */
306
+ renderer: WebGLRenderer;
307
+ /** Shared analog input (joysticks feed it); systems interpret it. */
308
+ input: InputBus;
309
+ /** The visitor's current control flags (e.g. look-invert axes), read live. */
310
+ controlFlags: () => ControlFlags;
311
+ /** Capability registry for cross-plugin contracts (terrain, colliders…). */
312
+ capabilities: CapabilityRegistry;
313
+ /** Emit a gallery event. */
314
+ emit: (event: string, payload?: unknown) => void;
315
+ /** Resolve a live scene object by id (e.g. for a framing system). */
316
+ resolve: (id: string) => Object3D | undefined;
317
+ }
318
+ /** A running system: its per-frame update, hot-swap and teardown hooks. */
319
+ interface SystemInstance<Api = unknown> {
320
+ /** Advance the system by the frame delta (ms). */
321
+ update?(deltaMs: number): void;
322
+ /** React to a hot-swapped scene definition. */
323
+ setScene?(scene: Scene$1): void;
324
+ /** Tear down (detach listeners, revoke capabilities). */
325
+ dispose?(): void;
326
+ /**
327
+ * Public API the system exposes to userland/overlay. The kernel maps known
328
+ * system names onto `ctx` (e.g. a `focus` system's api → `ctx.camera`).
329
+ */
330
+ api?: Api;
331
+ }
332
+ /**
333
+ * A system plugin: sets up once against the kernel handles and returns its
334
+ * running instance. Registered alongside element plugins in `plugins`; the
335
+ * kernel tells them apart by shape (`setup` vs `build`).
336
+ */
337
+ interface SystemPlugin<Api = unknown> {
338
+ name: string;
339
+ setup(ctx: SystemContext): SystemInstance<Api>;
340
+ }
341
+ /** Narrow a mixed plugin to a system plugin (has `setup`, not `build`). */
342
+ declare const isSystemPlugin: (plugin: unknown) => plugin is SystemPlugin;
343
+ //#endregion
344
+ //#region src/plugin/roamControl.d.ts
345
+ /** Capability name under which a roaming camera system exposes pose control. */
346
+ declare const ROAM_CONTROL = "roamControl";
347
+ /**
348
+ * Handle a camera plugin publishes so other camera systems can cooperate with
349
+ * it — chiefly a framing/tour system that suspends free-roam while it animates
350
+ * the camera, then reads/writes the pose to hand control back cleanly.
351
+ */
352
+ interface RoamControl {
353
+ /** Enable/disable free-roam input (disabled while another system owns the camera). */
354
+ setEnabled(enabled: boolean): void;
355
+ /** Current pose: world position plus yaw (`phi`) and pitch (`theta`) in radians. */
356
+ getPose(): {
357
+ position: Vector3;
358
+ phi: number;
359
+ theta: number;
360
+ };
361
+ /** Force the pose so free-roam resumes from it. */
362
+ setPose(position: Vector3, phi: number, theta: number): void;
363
+ /** Current eye height above the ground (world units). */
364
+ getEyeHeight(): number;
365
+ }
366
+ //#endregion
367
+ //#region src/plugin/terrain.d.ts
368
+ /** Capability name under which the floor publishes its terrain. */
369
+ declare const TERRAIN = "terrain";
370
+ /**
371
+ * The geometry a terrain consumer relies on — the fields needed to sample the
372
+ * same ground a floor was displaced by (e.g. the water shader). A floor
373
+ * definition satisfies this structurally; the kernel stays floor-agnostic.
374
+ */
375
+ interface TerrainDefinition {
376
+ /** Extent on the ground plane, `[width (x), depth (z)]`. */
377
+ size: Vec2;
378
+ /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */
379
+ center?: Vec2;
380
+ /** Base vertical position (world Y). Defaults to `0`. */
381
+ y?: number;
382
+ /** Grayscale heightmap URL that displaces the ground. */
383
+ heightmap?: string;
384
+ /** Max displacement (world units) at white in the heightmap. */
385
+ heightScale?: number;
386
+ }
387
+ /**
388
+ * The walkable ground, published by the floor: its definition (for shaders that
389
+ * sample the same heightmap), the live height function (heightmap-aware once the
390
+ * image loads) and a promise that resolves once that sampling is in effect.
391
+ */
392
+ interface TerrainCapability {
393
+ definition: TerrainDefinition;
394
+ heightAt: (x: number, z: number) => number;
395
+ /** Resolves once `heightAt` reflects the heightmap (immediately when there is none). */
396
+ ready: Promise<void>;
397
+ }
398
+ //#endregion
399
+ //#region src/plugin/colliders.d.ts
400
+ /** Capability name under which walkable-space obstacles are published. */
401
+ declare const COLLIDERS = "colliders";
402
+ /**
403
+ * A collision obstacle as an oriented box on the XZ plane. The camera controls
404
+ * consume these to keep the visitor out of walls (and any other plugin that
405
+ * wants to block movement can provide more of them). Purely a movement contract
406
+ * — no geometry, no three.js.
407
+ */
408
+ interface Collider {
409
+ /** Box center `[x, z]`. */
410
+ cx: number;
411
+ cz: number;
412
+ /** Unit direction along the box's length. */
413
+ dx: number;
414
+ dz: number;
415
+ /** Half the box length. */
416
+ halfLength: number;
417
+ /** Half the box thickness. */
418
+ halfThickness: number;
419
+ }
420
+ //#endregion
421
+ //#region src/plugin/edges.d.ts
422
+ /** Capability name under which named wall edges are published for hanging. */
423
+ declare const EDGES = "edges";
424
+ /** A named anchor an artwork can hang on: a wall segment resolved to geometry. */
425
+ interface EdgeAnchor {
426
+ a: Vec2;
427
+ b: Vec2;
428
+ height: number;
429
+ thickness: number;
430
+ }
431
+ /** The `name -> anchor` index a provider (e.g. the wall plugin) publishes. */
432
+ type EdgeIndex = ReadonlyMap<string, EdgeAnchor>;
433
+ //#endregion
434
+ //#region src/plugin/eyeHeight.d.ts
435
+ /**
436
+ * Capability name under which the visitor's eye height (world units) is
437
+ * published — the default vertical placement for wall-hung artworks. The kernel
438
+ * provides it from the camera start; a camera plugin will own it later.
439
+ */
440
+ declare const EYE_HEIGHT = "eyeHeight";
441
+ //#endregion
4
442
  //#region src/createGallery.d.ts
5
- interface GalleryOptions {
443
+ /** MSAA sample count for antialiasing. `0` disables it. */
444
+ type Antialias = 0 | 2 | 4 | 8;
445
+ /** Toggleable rendering features. All can be flipped live via `setRender`. */
446
+ interface RenderOptions {
447
+ /**
448
+ * Antialiasing as MSAA sample count (`0` off, `2`/`4`/`8`). Applied to the
449
+ * post-process render target so it survives SSAO/bloom and toggles live,
450
+ * unlike the canvas's native MSAA. Defaults to `4`.
451
+ */
452
+ antialias?: Antialias;
453
+ /** Screen-space ambient occlusion as a post-process. Defaults to `false`. */
454
+ ssao?: boolean;
455
+ /**
456
+ * Bloom strength: a soft glow bleeding out of the brightest areas. `0` off;
457
+ * gentle around `0.3`, strong around `1`. Defaults to `0`.
458
+ */
459
+ bloom?: number;
460
+ /** Real-time shadow mapping (PCF). Defaults to `true`. */
461
+ shadows?: boolean;
462
+ /**
463
+ * Cardboard / stereoscopic mode: split the view into a left/right eye pair for
464
+ * a phone-in-a-headset viewer. Skips post-processing while active. Defaults to
465
+ * `false`.
466
+ */
467
+ cardboard?: boolean;
468
+ /**
469
+ * Interocular distance for cardboard mode, in world units. The physical ≈6.4 cm
470
+ * is too much parallax to fuse comfortably through cardboard lenses, so this
471
+ * defaults to `3`; tune it live for the viewer. `0` flattens to mono.
472
+ */
473
+ eyeSeparation?: number;
474
+ }
475
+ interface GalleryOptions extends RenderOptions {
6
476
  /** Canvas the gallery renders into. */
7
477
  canvas: HTMLCanvasElement;
8
478
  /** Background color. Defaults to a soft gray. */
9
479
  background?: string;
10
- antialias?: boolean;
480
+ /**
481
+ * Runtime plugins: element plugins materialize a scene key; system plugins
482
+ * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells
483
+ * them apart by shape.
484
+ */
485
+ plugins?: (EnginePlugin | SystemPlugin)[];
486
+ }
487
+ /**
488
+ * Drive the free-roam camera from outside the engine — e.g. on-screen touch
489
+ * controls in the overlay. Inputs are analog (`-1..1`) and held until changed.
490
+ */
491
+ interface ControlsApi {
492
+ /** Walk forward (`+1`) / back (`-1`); optional strafe right (`+1`) / left (`-1`). */
493
+ setMoveInput(forward: number, strafe?: number): void;
494
+ /** Turn right (`+1`) / left (`-1`); optional look up (`+1`) / down (`-1`). */
495
+ setLookInput(x: number, y?: number): void;
496
+ }
497
+ /**
498
+ * The visitor's camera-control preferences (view mode + capability flags). The
499
+ * engine applies the effects that touch rendering/input — gyroscope head-look and
500
+ * the stereo split — as the flags change; the overlay drives it from the mode
501
+ * picker and settings, and reads it to show the right joysticks. See
502
+ * `ControlSettings`.
503
+ */
504
+ interface ViewControlsApi {
505
+ /** Current capability flags (a copy). */
506
+ get(): ControlFlags;
507
+ /** The mode the flags represent (`custom` when hand-tuned in settings). */
508
+ getMode(): ControlMode;
509
+ /** Switch to a mode's preset (keeps the look-invert axes). */
510
+ setMode(mode: Exclude<ControlMode, "custom">): void;
511
+ /** Set one capability flag (may drop the mode into `custom`). */
512
+ setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;
513
+ /** Subscribe to changes; returns an unsubscribe. */
514
+ subscribe(fn: (flags: ControlFlags) => void): () => void;
515
+ }
516
+ /** Control the HDRI sky / environment at runtime, for behavior code. */
517
+ interface EnvironmentApi {
518
+ /** Set the visible sky background brightness (`0` = black). */
519
+ setBackgroundIntensity(value: number): void;
11
520
  }
12
521
  /**
13
522
  * High-level API handed to behavior code. Deliberately NOT raw three.js: this is
14
523
  * the supported surface. Reach into `three` only as a documented escape hatch.
15
524
  */
16
525
  interface GalleryContext {
17
- /** Resolve the live object for a wall/artwork `id`. */
526
+ /** Resolve the live object for a wall/artwork/light `id`. */
18
527
  entity(id: string): Object3D | undefined;
528
+ /** Control scene lights (dim, set intensity/color) by `id`. */
529
+ readonly lights: LightsApi;
530
+ /** Control background audio (play, pause, fade) by `id`. */
531
+ readonly media: MediaController;
532
+ /** Drive the camera externally (on-screen touch controls, etc.). */
533
+ readonly controls: ControlsApi;
534
+ /** The visitor's camera-control preferences (view mode + capability flags). */
535
+ readonly viewControls: ViewControlsApi;
536
+ /** Control the HDRI sky / environment at runtime. */
537
+ readonly environment: EnvironmentApi;
538
+ /** Drive the procedural sky's time of day. */
539
+ readonly sky: SkyApi;
540
+ /** Animate the camera to frame artworks (e.g. when an info panel opens/closes). */
541
+ readonly camera: CameraApi;
542
+ /** Control video screens (play, pause, show/hide) by `id`. */
543
+ readonly video: VideoApi;
19
544
  /** Escape hatch to the underlying three.js objects for advanced use. */
20
545
  readonly three: {
21
546
  scene: Scene;
@@ -36,8 +561,12 @@ interface Gallery {
36
561
  on(event: string, handler: (payload?: unknown) => void): () => void;
37
562
  /** Emit a named event to subscribers. */
38
563
  emit(event: string, payload?: unknown): void;
564
+ /** Run a handler every frame with the frame delta in ms; returns unsubscribe. */
565
+ onFrame(handler: (deltaMs: number) => void): () => void;
39
566
  /** Resize the renderer/camera to the given pixel size. */
40
567
  resize(width: number, height: number): void;
568
+ /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */
569
+ setRender(options: RenderOptions): void;
41
570
  readonly ctx: GalleryContext;
42
571
  }
43
572
  /**
@@ -57,64 +586,80 @@ declare function createGallery(scene: Scene$1, options: GalleryOptions): Gallery
57
586
  */
58
587
  declare class SceneManager {
59
588
  private readonly root;
60
- private readonly walls;
61
- private readonly artworks;
62
- constructor(root: Scene);
589
+ private readonly plugins;
590
+ private readonly capabilities;
591
+ /** Mounted plugin elements, per kind, keyed by element id ("" for singletons). */
592
+ private readonly pluginElements;
593
+ constructor(root: Scene, plugins?: EnginePlugin[], capabilities?: CapabilityRegistry);
63
594
  reconcile(scene: Scene$1): void;
64
- private reconcileWalls;
65
- private reconcileArtworks;
66
- /** Look up the live three.js object for a wall or artwork `id`. */
595
+ /**
596
+ * Reconcile every registered plugin's elements by content: an element
597
+ * rebuilds when its definition changes or when a capability it depends on
598
+ * was re-provided (the capability versions are part of the hash).
599
+ */
600
+ private reconcilePlugins;
601
+ private removePluginElement;
602
+ /** Advance time-based plugin elements (e.g. water ripple). Call once per frame. */
603
+ updateElements(deltaMs: number): void;
604
+ /** Run every element's auxiliary pre-render pass (e.g. water reflection). */
605
+ beforeRender(renderer: WebGLRenderer, scene: Scene, camera: PerspectiveCamera): void;
606
+ /** Look up the live three.js object for any plugin element `id`. */
67
607
  get(id: string): Object3D | undefined;
608
+ /** Live objects of one plugin kind, keyed by `id` — for that plugin's controller. */
609
+ elementsOf(kind: string): ReadonlyMap<string, Object3D>;
68
610
  dispose(): void;
69
611
  }
70
612
  //#endregion
71
- //#region src/controls/FreeRoamControls.d.ts
72
- interface FreeRoamOptions {
73
- /** Square half-extent that clamps XZ movement. */
74
- bounds?: number;
75
- /** Starting position. */
76
- position?: Vector3;
77
- /** Starting yaw in radians. */
78
- yaw?: number;
79
- }
613
+ //#region src/builders/texture.d.ts
614
+ /**
615
+ * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV
616
+ * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must
617
+ * provide one.
618
+ */
619
+ declare const applyTexture: (material: MeshStandardMaterial, texture: SurfaceTexture) => boolean;
620
+ //#endregion
621
+ //#region src/interaction/InteractionController.d.ts
80
622
  /**
81
- * First-person, free-roaming camera controls ported from the original Virtual
82
- * Gallery: drag-to-look with the mouse, WASD to walk, a subtle head-bob while
83
- * moving, and square boundary clamping. The cardboard / device-orientation path
84
- * was intentionally dropped.
85
- *
86
- * Framework-agnostic: construct it with a camera and the canvas element, call
87
- * `attach()`, then `update(delta)` once per frame.
623
+ * Pointer picking for artworks: hovering a piece's click target (its label, or
624
+ * the piece itself when it has none) draws a pulsing red frame and shows a
625
+ * pointer cursor; clicking it emits `artwork:select { id }`. Targets are marked
626
+ * in the scene graph (`userData.pickTarget`/`pickInfo`) by the artwork builder,
627
+ * so this just raycasts and walks up to find them. An optional per-id radius
628
+ * gates interaction to when the camera is reasonably close.
88
629
  */
89
- declare class FreeRoamControls {
90
- bounds: number;
630
+ declare class InteractionController {
91
631
  private readonly camera;
92
- private readonly domElement;
93
- private readonly mouse;
94
- private readonly keyboard;
95
- private readonly headBob;
96
- private readonly look;
97
- private rotation;
98
- private translation;
632
+ private readonly canvas;
633
+ private readonly scene;
634
+ private readonly emit;
635
+ private readonly canInteract;
636
+ private readonly raycaster;
637
+ private readonly pointer;
638
+ private over;
639
+ private downPos;
640
+ private radii;
641
+ private hovered;
642
+ private time;
643
+ private readonly highlight;
644
+ private highlightMat;
99
645
  private readonly handlers;
100
- constructor(camera: PerspectiveCamera, domElement: HTMLElement, options?: FreeRoamOptions);
646
+ constructor(camera: PerspectiveCamera, canvas: HTMLCanvasElement, scene: Scene, emit: (event: string, payload?: unknown) => void, canInteract: () => boolean);
647
+ /** Per-artwork max interaction distance (XZ). Absent id = no distance gate. */
648
+ setRadii(radii: Map<string, number>): void;
101
649
  attach(): void;
102
650
  detach(): void;
103
- /** Advance the camera by `delta` seconds of input. */
104
- update(delta: number): void;
651
+ private setPointer;
652
+ private onMove;
653
+ private onDown;
654
+ private onUp;
655
+ /** Resolve the nearest pickable artwork under the pointer, honoring the gate. */
656
+ private raycast;
657
+ /** Recompute hover state and animate the highlight. Call once per frame. */
658
+ update(deltaMs: number): void;
659
+ private clearHighlight;
660
+ /** Position a fresh pulsing red frame over the picked target's front face. */
661
+ private buildHighlight;
105
662
  }
106
663
  //#endregion
107
- //#region src/builders/wall.d.ts
108
- /** Build the three.js object that materializes a `Wall` definition. */
109
- declare function buildWall(wall: Wall): Object3D;
110
- //#endregion
111
- //#region src/builders/artwork.d.ts
112
- /**
113
- * Build the three.js object for an `Artwork`, placing it on its wall. The piece
114
- * is positioned in the wall's local plane via `offset` and nudged forward along
115
- * the wall normal so it sits in front of the surface.
116
- */
117
- declare function buildArtwork(artwork: Artwork, wall: Wall): Object3D;
118
- //#endregion
119
- export { FreeRoamControls, type FreeRoamOptions, type Gallery, type GalleryContext, type GalleryOptions, SceneManager, buildArtwork, buildWall, createGallery };
664
+ 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 };
120
665
  //# sourceMappingURL=index.d.cts.map