@waica/engine 0.27.0 → 0.29.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.
Files changed (43) hide show
  1. package/README.md +30 -8
  2. package/dist/anchored-pieces.js +21 -5
  3. package/dist/archetype.d.ts +15 -0
  4. package/dist/archetype.js +41 -1
  5. package/dist/assets/glb-clip-names.d.ts +8 -0
  6. package/dist/assets/glb-clip-names.js +49 -0
  7. package/dist/camera-projection.d.ts +6 -5
  8. package/dist/camera-projection.js +8 -7
  9. package/dist/camera-rig.d.ts +54 -4
  10. package/dist/camera-rig.js +116 -6
  11. package/dist/components/animator.d.ts +6 -0
  12. package/dist/components/animator.js +12 -0
  13. package/dist/entity-update-schedules.d.ts +21 -0
  14. package/dist/entity-update-schedules.js +67 -0
  15. package/dist/game.d.ts +14 -12
  16. package/dist/game.js +41 -75
  17. package/dist/index.d.ts +10 -2
  18. package/dist/index.js +4 -0
  19. package/dist/input-mouse.d.ts +83 -0
  20. package/dist/input-mouse.js +205 -0
  21. package/dist/input.d.ts +26 -5
  22. package/dist/input.js +58 -10
  23. package/dist/orbit-camera.d.ts +68 -0
  24. package/dist/orbit-camera.js +92 -0
  25. package/dist/physics-3d/world-queries.d.ts +10 -1
  26. package/dist/physics-3d/world-queries.js +6 -2
  27. package/dist/pointer-raycast.d.ts +24 -6
  28. package/dist/pointer-raycast.js +44 -14
  29. package/dist/runtime-bridge.d.ts +12 -2
  30. package/dist/runtime-bridge.js +12 -1
  31. package/dist/runtime-inspection.js +3 -2
  32. package/dist/runtime-view-snapshot.d.ts +5 -8
  33. package/dist/runtime-view-snapshot.js +15 -11
  34. package/dist/scene-camera-3d.d.ts +14 -15
  35. package/dist/scene-camera-3d.js +14 -10
  36. package/dist/scene-camera-orbit.d.ts +72 -0
  37. package/dist/scene-camera-orbit.js +155 -0
  38. package/dist/scene-field-issues.d.ts +16 -0
  39. package/dist/scene-field-issues.js +11 -0
  40. package/dist/scene.js +5 -3
  41. package/dist/spatial-query-3d.d.ts +2 -2
  42. package/dist/spatial-query-3d.js +13 -4
  43. package/package.json +1 -1
@@ -75,6 +75,12 @@ export declare class Animator extends Component implements ClipTarget {
75
75
  get clipNames(): readonly string[];
76
76
  get clipFinished(): boolean;
77
77
  get clipsPending(): boolean;
78
+ /**
79
+ * How far the playing clip is, from 0 at its start to 1 at its end (issue
80
+ * #181): its time over its duration, so a finished one-shot holds 1 and a
81
+ * loop starts over at 0. 0 while nothing plays.
82
+ */
83
+ get clipProgress(): number;
78
84
  onReady(): void;
79
85
  /** Plays a clip: a no-op for the running one, a restart for a finished one, a crossfade from any other over `fade` seconds. */
80
86
  play(name: string): void;
@@ -58,6 +58,18 @@ export class Animator extends Component {
58
58
  get clipsPending() {
59
59
  return this.pending;
60
60
  }
61
+ /**
62
+ * How far the playing clip is, from 0 at its start to 1 at its end (issue
63
+ * #181): its time over its duration, so a finished one-shot holds 1 and a
64
+ * loop starts over at 0. 0 while nothing plays.
65
+ */
66
+ get clipProgress() {
67
+ const action = this.active;
68
+ if (!action)
69
+ return 0;
70
+ const { duration } = action.getClip();
71
+ return duration > 0 ? Math.min(action.time / duration, 1) : 1;
72
+ }
61
73
  onReady() {
62
74
  if (this.game.space !== '3d')
63
75
  return;
@@ -0,0 +1,21 @@
1
+ import type { Component, ComponentClass } from './component.js';
2
+ import type { Entity } from './entity.js';
3
+ /**
4
+ * Each entity's Component Update Schedule (ADR 0004), as the Game runs it once
5
+ * per Simulation Step: resolved from the entity's composition, cached per
6
+ * composition signature, and reported once per invalid composition.
7
+ */
8
+ export declare class EntityUpdateSchedules {
9
+ private readonly invalidCompositions;
10
+ /**
11
+ * The resolved order for the last composition signature seen per entity:
12
+ * the resolve (Tarjan SCC + Kahn sort) it's built from is pure for a fixed
13
+ * composition, so a signature match skips it entirely. Only ever holds a
14
+ * successful resolution — an invalid composition is never cached, since
15
+ * invalidCompositions already dedupes its console.error.
16
+ */
17
+ private readonly cache;
18
+ /** The entity's components in update order, or null (reported once) when its composition is invalid. */
19
+ of(entity: Entity, registered?: Readonly<Record<string, ComponentClass>>): Component[] | null;
20
+ private resolve;
21
+ }
@@ -0,0 +1,67 @@
1
+ import { componentClassOf } from './component-registry.js';
2
+ import { implementsOnUpdate, resolveComponentUpdateSchedule } from './component-update-schedule.js';
3
+ /** Maps a resolved update order back to the entity's components; the order only names components it was built from. */
4
+ function componentsInOrder(order, byName) {
5
+ return order.map((name) => {
6
+ const component = byName.get(name);
7
+ if (!component)
8
+ throw new Error(`Component update schedule names "${name}", which is not on the entity`);
9
+ return component;
10
+ });
11
+ }
12
+ /**
13
+ * Each entity's Component Update Schedule (ADR 0004), as the Game runs it once
14
+ * per Simulation Step: resolved from the entity's composition, cached per
15
+ * composition signature, and reported once per invalid composition.
16
+ */
17
+ export class EntityUpdateSchedules {
18
+ invalidCompositions = new WeakMap();
19
+ /**
20
+ * The resolved order for the last composition signature seen per entity:
21
+ * the resolve (Tarjan SCC + Kahn sort) it's built from is pure for a fixed
22
+ * composition, so a signature match skips it entirely. Only ever holds a
23
+ * successful resolution — an invalid composition is never cached, since
24
+ * invalidCompositions already dedupes its console.error.
25
+ */
26
+ cache = new WeakMap();
27
+ /** The entity's components in update order, or null (reported once) when its composition is invalid. */
28
+ of(entity, registered = {}) {
29
+ const components = [...entity.components];
30
+ const byName = new Map();
31
+ const names = [];
32
+ const signatureParts = [];
33
+ for (const component of components) {
34
+ const Class = componentClassOf(component);
35
+ const name = Class.componentName;
36
+ names.push(name);
37
+ byName.set(name, component);
38
+ signatureParts.push(`${name}:${implementsOnUpdate(Class) ? 'updates' : 'passive'}:` +
39
+ [...new Set(Class.updateAfter ?? [])].sort().join(','));
40
+ }
41
+ const signature = signatureParts.sort().join('|');
42
+ const cached = this.cache.get(entity);
43
+ if (cached && cached.signature === signature)
44
+ return componentsInOrder(cached.order, byName);
45
+ return this.resolve(entity, { components, names, byName, signature }, registered);
46
+ }
47
+ resolve(entity, composition, registered) {
48
+ const registry = { ...registered };
49
+ for (const component of composition.components) {
50
+ const Class = componentClassOf(component);
51
+ registry[Class.componentName] = Class;
52
+ }
53
+ const result = resolveComponentUpdateSchedule(composition.names, registry);
54
+ if (!result.ok) {
55
+ this.cache.delete(entity);
56
+ if (this.invalidCompositions.get(entity) !== composition.signature) {
57
+ this.invalidCompositions.set(entity, composition.signature);
58
+ console.error(`[waica] invalid component update schedule for "${entity.name}": ` +
59
+ result.issues.map((issue) => issue.cause).join(' '));
60
+ }
61
+ return null;
62
+ }
63
+ this.invalidCompositions.delete(entity);
64
+ this.cache.set(entity, { signature: composition.signature, order: result.order });
65
+ return componentsInOrder(result.order, composition.byName);
66
+ }
67
+ }
package/dist/game.d.ts CHANGED
@@ -135,15 +135,8 @@ export declare class Game {
135
135
  /** Off, lit or through the Post Effects: how each frame is drawn (ADR 0026). */
136
136
  private readonly frame;
137
137
  private readonly updateFns;
138
- private readonly invalidUpdateCompositions;
139
- /**
140
- * componentUpdateSchedule's result for the last composition signature seen
141
- * per entity: the resolve (Tarjan SCC + Kahn sort) it's built from is pure
142
- * for a fixed composition, so a signature match skips it entirely. Only
143
- * ever holds a successful resolution — an invalid composition is never
144
- * cached, since invalidUpdateCompositions already dedupes its console.error.
145
- */
146
- private readonly updateScheduleCache;
138
+ /** Each entity's Component Update Schedule (ADR 0004), resolved once per composition. */
139
+ private readonly updateSchedules;
147
140
  private readonly resolution;
148
141
  /** The constructor's viewHeight — unloadScene() restores it. */
149
142
  private readonly baseViewHeight;
@@ -247,15 +240,23 @@ export declare class Game {
247
240
  * simulating, follows/clamps per its settings. Called by loadScene.
248
241
  */
249
242
  setSceneCamera(json?: SceneCameraJson): void;
243
+ /** Puts a following perspective camera on its orbit; loadScene calls it after the spawns, so the first frame is framed (issue #181). */
244
+ placeSceneCamera(): void;
250
245
  start(): void;
251
246
  stop(): void;
252
247
  /** Internal: called by Entity.destroy(). Its Anchored Pieces go (or freeze) with it. */
253
248
  removeEntity(entity: Entity): void;
254
249
  /** The scene's render projection; null keeps logical and render space identical. */
255
250
  get projection(): 'isometric' | null;
251
+ /**
252
+ * The yaw of the scene's orbit camera in degrees — 0 looks toward -z, 90
253
+ * toward +x — or null when the scene camera follows nothing (issue #181);
254
+ * what a camera-relative mover rotates its input by.
255
+ */
256
+ get cameraYaw(): number | null;
256
257
  /** The live scene's space (`render.space`); `'2d'` with no scene (ADR 0027). */
257
258
  get space(): SceneSpace;
258
- /** The camera the live scene draws with: orthographic in 2D scenes, perspective in 3D ones. */
259
+ /** The camera the live scene draws with: orthographic in 2D scenes, perspective in 3D ones, or a host's view camera over a 3D one (`cameraRig.setViewCamera`). */
259
260
  get camera(): GameCamera;
260
261
  /**
261
262
  * Where a logical point falls on the canvas, in CSS px from its top-left
@@ -309,7 +310,7 @@ export declare class Game {
309
310
  */
310
311
  private runFrame;
311
312
  /**
312
- * One Simulation Step: the pad is read first (issue #75 CA-6), then
313
+ * One Simulation Step: the pad and the mouse are read first (issue #75 CA-6, issue #181 CA-2), then
313
314
  * game.time's start-of-step pass (ADR 0017, CA-3), then the Component Update Schedule (ADR 0004) in full,
314
315
  * collisions, the scene camera and the host's callbacks, every one of
315
316
  * them handed exactly SIMULATION_STEP (CA-1); then the input frame ends.
@@ -321,7 +322,8 @@ export declare class Game {
321
322
  private flushPendingSceneLoad;
322
323
  private unregisterRuntimeBridge;
323
324
  private renderSurface;
324
- private componentUpdateSchedule;
325
+ /** True while a simulating scene's camera orbits a followed entity: what the pointer lock is for. */
326
+ private get orbitPlays();
325
327
  private updateSceneCamera;
326
328
  private renderPoint;
327
329
  private resize;
package/dist/game.js CHANGED
@@ -6,9 +6,10 @@ import { listenerPosition, screenPanOf } from './audio/spatial.js';
6
6
  import { worldToNormalized } from './camera-projection.js';
7
7
  import { CameraRig } from './camera-rig.js';
8
8
  import { advanceCameraEffects, CameraEffects, layoutCameraEffects } from './camera-effects.js';
9
- import { implementsOnUpdate, resolveComponentUpdateSchedule } from './component-update-schedule.js';
10
9
  import { Entity } from './entity.js';
10
+ import { EntityUpdateSchedules } from './entity-update-schedules.js';
11
11
  import { Emitter } from './events.js';
12
+ import { RigidBody } from './components/rigid-body.js';
12
13
  import { consumeSimulationSteps, MAX_CHAINED_HOPS, SIMULATION_STEP, snapElapsedToStep, } from './fixed-step.js';
13
14
  import { advanceGameTime, GameTime } from './game-time.js';
14
15
  import { Input } from './input.js';
@@ -34,15 +35,6 @@ import { createSpatialQuery } from './spatial-query.js';
34
35
  import { Stats } from './stats.js';
35
36
  import { anchoredPiecesOf, GameUi } from './ui.js';
36
37
  import { componentClassOf } from './component-registry.js';
37
- /** Maps a resolved update order back to the entity's components; the order only names components it was built from. */
38
- function componentsInOrder(order, byName) {
39
- return order.map((name) => {
40
- const component = byName.get(name);
41
- if (!component)
42
- throw new Error(`Component update schedule names "${name}", which is not on the entity`);
43
- return component;
44
- });
45
- }
46
38
  /**
47
39
  * Engine core: loop, unified 2D/3D three scene, orthographic camera,
48
40
  * entities with components, and input. See DESIGN.md.
@@ -95,15 +87,8 @@ export class Game {
95
87
  /** Off, lit or through the Post Effects: how each frame is drawn (ADR 0026). */
96
88
  frame;
97
89
  updateFns = new Set();
98
- invalidUpdateCompositions = new WeakMap();
99
- /**
100
- * componentUpdateSchedule's result for the last composition signature seen
101
- * per entity: the resolve (Tarjan SCC + Kahn sort) it's built from is pure
102
- * for a fixed composition, so a signature match skips it entirely. Only
103
- * ever holds a successful resolution — an invalid composition is never
104
- * cached, since invalidUpdateCompositions already dedupes its console.error.
105
- */
106
- updateScheduleCache = new WeakMap();
90
+ /** Each entity's Component Update Schedule (ADR 0004), resolved once per composition. */
91
+ updateSchedules = new EntityUpdateSchedules();
107
92
  resolution;
108
93
  /** The constructor's viewHeight — unloadScene() restores it. */
109
94
  baseViewHeight;
@@ -119,6 +104,9 @@ export class Game {
119
104
  find: (name) => this.find(name),
120
105
  renderPoint: (x, y) => this.renderPoint(x, y),
121
106
  viewHeight: () => this.viewHeight,
107
+ input: () => this.input,
108
+ // Fixed level geometry only (a Collider with no RigidBody): a character or a crate crossing behind the Player never moves the camera.
109
+ rayDistance: ({ origin, direction, maxDistance, exclude }) => this.query.ray(origin, direction, maxDistance, { exclude, without: [RigidBody] })?.distance ?? null,
122
110
  };
123
111
  renderSort = null;
124
112
  sceneProjection = null;
@@ -146,7 +134,14 @@ export class Game {
146
134
  this.baseViewHeight = viewHeight;
147
135
  this.viewHeight = viewHeight;
148
136
  this.resolution = options.resolution ?? null;
149
- this.input = new Input(options.bindings, { gamepadDeadZone: options.gamepadDeadZone });
137
+ this.input = new Input(options.bindings, {
138
+ gamepadDeadZone: options.gamepadDeadZone,
139
+ canvas,
140
+ // A click takes the pointer lock only while an orbit camera plays (never in the editor's edit mode).
141
+ lockOnClick: () => this.orbitPlays,
142
+ // The wheel zooms the orbit; any other scene leaves the page's scroll alone (CA-2 as amended).
143
+ preventsWheelScroll: () => this.orbitPlays,
144
+ });
150
145
  this.query = createSpatialQuery(this);
151
146
  this.stats = new Stats(options.stats);
152
147
  this.ui = new GameUi(this.stats, () => canvas.parentElement ?? document.body);
@@ -410,6 +405,10 @@ export class Game {
410
405
  this.camera.position.y = center.y;
411
406
  this.setViewHeight(cam.zoom);
412
407
  }
408
+ /** Puts a following perspective camera on its orbit; loadScene calls it after the spawns, so the first frame is framed (issue #181). */
409
+ placeSceneCamera() {
410
+ this.cameraRig.placeOrbit(this.followHost);
411
+ }
413
412
  start() {
414
413
  if (this.disposed)
415
414
  return;
@@ -422,6 +421,7 @@ export class Game {
422
421
  resume: (onStep) => this.resumeRuntime(onStep),
423
422
  pause: () => this.stop(),
424
423
  injectAction: (action, operation, value) => this.input.injectAction(action, operation, value),
424
+ look: (dx, dy) => this.input.injectLook(dx, dy),
425
425
  availableActions: () => this.input.availableActions(),
426
426
  heldActions: () => this.input.heldActions(),
427
427
  actionValues: () => this.input.actionValues(),
@@ -465,11 +465,19 @@ export class Game {
465
465
  get projection() {
466
466
  return this.sceneProjection;
467
467
  }
468
+ /**
469
+ * The yaw of the scene's orbit camera in degrees — 0 looks toward -z, 90
470
+ * toward +x — or null when the scene camera follows nothing (issue #181);
471
+ * what a camera-relative mover rotates its input by.
472
+ */
473
+ get cameraYaw() {
474
+ return this.cameraRig.orbitView?.yaw ?? null;
475
+ }
468
476
  /** The live scene's space (`render.space`); `'2d'` with no scene (ADR 0027). */
469
477
  get space() {
470
478
  return this.sceneSpace;
471
479
  }
472
- /** The camera the live scene draws with: orthographic in 2D scenes, perspective in 3D ones. */
480
+ /** The camera the live scene draws with: orthographic in 2D scenes, perspective in 3D ones, or a host's view camera over a 3D one (`cameraRig.setViewCamera`). */
473
481
  get camera() {
474
482
  return this.cameraRig.camera;
475
483
  }
@@ -604,6 +612,9 @@ export class Game {
604
612
  this.finishStep();
605
613
  }
606
614
  this.audio.setActive(this.simulate);
615
+ // The pointer lock belongs to a playing orbit: a pause, a swap or an unload gives the cursor back.
616
+ if (!this.orbitPlays)
617
+ this.input.releasePointerLock();
607
618
  // Positional audio (CA-8): recomputed every frame, on this same pass —
608
619
  // never a second walk of `this.entities`, since `this.audio` already
609
620
  // holds direct references to whichever entities are tracked.
@@ -615,19 +626,20 @@ export class Game {
615
626
  }
616
627
  }
617
628
  /**
618
- * One Simulation Step: the pad is read first (issue #75 CA-6), then
629
+ * One Simulation Step: the pad and the mouse are read first (issue #75 CA-6, issue #181 CA-2), then
619
630
  * game.time's start-of-step pass (ADR 0017, CA-3), then the Component Update Schedule (ADR 0004) in full,
620
631
  * collisions, the scene camera and the host's callbacks, every one of
621
632
  * them handed exactly SIMULATION_STEP (CA-1); then the input frame ends.
622
633
  */
623
634
  simulateStep() {
624
635
  this.input.pollGamepad();
636
+ this.input.pollMouse();
625
637
  advanceGameTime(this.time);
626
638
  // Existing drains advance before entity updates, so one detached during
627
639
  // this step begins on the next Simulation Step (ADR 0022).
628
640
  sceneDrainsOf(this).advance(SIMULATION_STEP);
629
641
  for (const entity of [...this.entities]) {
630
- const schedule = this.componentUpdateSchedule(entity);
642
+ const schedule = this.updateSchedules.of(entity, this.registry?.components);
631
643
  if (!schedule)
632
644
  continue;
633
645
  for (const component of schedule)
@@ -681,11 +693,7 @@ export class Game {
681
693
  // The shake offset exists only while drawing (ADR 0020): Anchored Pieces
682
694
  // and the render see the drawn center, then the base comes back exactly,
683
695
  // so follow smoothing never starts from its own jitter.
684
- const { x, y } = this.camera.position;
685
- // A perspective camera does not shake: the offset is in orthographic world units.
686
- const shake = this.sceneSpace === '3d' ? { x: 0, y: 0 } : this.cameraEffects.state.shake;
687
- this.camera.position.x = x + shake.x;
688
- this.camera.position.y = y + shake.y;
696
+ const restoreBase = this.cameraRig.shake(this.cameraEffects.state.shake, this.viewHeight);
689
697
  try {
690
698
  anchoredPiecesOf(this.ui).place();
691
699
  // Before ready() the frame simulated, but three cannot draw yet.
@@ -694,57 +702,15 @@ export class Game {
694
702
  this.frame.draw();
695
703
  }
696
704
  finally {
697
- this.camera.position.x = x;
698
- this.camera.position.y = y;
705
+ restoreBase();
699
706
  }
700
707
  }
701
- componentUpdateSchedule(entity) {
702
- const components = [...entity.components];
703
- const byName = new Map();
704
- const names = [];
705
- const signatureParts = [];
706
- for (const component of components) {
707
- const Class = componentClassOf(component);
708
- const name = Class.componentName;
709
- names.push(name);
710
- byName.set(name, component);
711
- signatureParts.push(`${name}:${implementsOnUpdate(Class) ? 'updates' : 'passive'}:` +
712
- [...new Set(Class.updateAfter ?? [])].sort().join(','));
713
- }
714
- const signature = signatureParts.sort().join('|');
715
- // The resolve below (duplicate check + Tarjan SCC + Kahn sort) is pure
716
- // for a fixed composition, and this method now runs once per entity per
717
- // Simulation Step rather than once per rendered frame: skip it entirely
718
- // when nothing about this entity's components changed since last time.
719
- const cached = this.updateScheduleCache.get(entity);
720
- if (cached && cached.signature === signature) {
721
- return componentsInOrder(cached.order, byName);
722
- }
723
- const registry = {
724
- ...(this.registry?.components ?? {}),
725
- };
726
- for (const component of components) {
727
- const Class = componentClassOf(component);
728
- registry[Class.componentName] = Class;
729
- }
730
- const result = resolveComponentUpdateSchedule(names, registry);
731
- if (!result.ok) {
732
- this.updateScheduleCache.delete(entity);
733
- if (this.invalidUpdateCompositions.get(entity) !== signature) {
734
- this.invalidUpdateCompositions.set(entity, signature);
735
- console.error(`[waica] invalid component update schedule for "${entity.name}": ` +
736
- result.issues.map((issue) => issue.cause).join(' '));
737
- }
738
- return null;
739
- }
740
- this.invalidUpdateCompositions.delete(entity);
741
- this.updateScheduleCache.set(entity, { signature, order: result.order });
742
- return componentsInOrder(result.order, byName);
708
+ /** True while a simulating scene's camera orbits a followed entity: what the pointer lock is for. */
709
+ get orbitPlays() {
710
+ return this.simulate && this.cameraRig.orbiting;
743
711
  }
744
712
  updateSceneCamera(dt) {
745
- if (!this.sceneCamera)
746
- return;
747
- this.cameraRig.follow(this.sceneCamera, this.followHost, dt);
713
+ this.cameraRig.step(this.sceneCamera, this.followHost, dt);
748
714
  }
749
715
  renderPoint(x, y) {
750
716
  return this.sceneProjection === 'isometric' ? projectIsometric(x, y) : { x, y };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { AssetLoader } from './assets/asset-loader.js';
4
4
  export type { AssetStatus } from './assets/asset-loader.js';
5
5
  export type { TextureBackend } from './assets/texture-backend.js';
6
6
  export { GltfModelBackend } from './assets/model-backend.js';
7
+ export { glbClipNames } from './assets/glb-clip-names.js';
7
8
  export type { LoadedModel, ModelBackend } from './assets/model-backend.js';
8
9
  export type { ModelHandle, ModelOutcome } from './assets/model-cache.js';
9
10
  export { AudioSubsystem } from './audio/audio-subsystem.js';
@@ -29,6 +30,9 @@ export type { EasingName, TimerHandle, TimerOptions, TweenOptions } from './game
29
30
  export type { SceneCameraJson, OrthographicSceneCameraJson, CameraLimitsJson, CameraVelocity, CameraVelocityProvider, ResolvedSceneCamera, } from './camera.js';
30
31
  export { isPerspectiveCameraJson, PERSPECTIVE_DEFAULTS, perspectiveCameraIssues, placePerspectiveCamera, resolvePerspectiveCamera, } from './scene-camera-3d.js';
31
32
  export type { PerspectiveSceneCameraJson, ResolvedPerspectiveCamera, SceneFieldIssue, Vec3Json, } from './scene-camera-3d.js';
33
+ export { ORBIT_DEFAULTS, resolveOrbit } from './scene-camera-orbit.js';
34
+ export type { OrbitCameraJson, ResolvedOrbit } from './scene-camera-orbit.js';
35
+ export type { OrbitView } from './orbit-camera.js';
32
36
  export { isPerspectiveCamera, worldToNormalized } from './camera-projection.js';
33
37
  export type { GameCamera, NormalizedPoint, ViewCamera, WorldPoint } from './camera-projection.js';
34
38
  export { componentSpaceMismatch, componentSpaceOf, entityTransformIssues, resolveSceneSpace, SCENE_SPACES, sceneSpaceIssues, } from './scene-space.js';
@@ -46,9 +50,12 @@ export { resolveComponentUpdateSchedule } from './component-update-schedule.js';
46
50
  export type { ComponentUpdateCycleIssue, ComponentUpdateRegistry, ComponentUpdateScheduleIssue, ComponentUpdateScheduleResult, DuplicateComponentUpdateIssue, InvalidComponentUpdateConstraintIssue, InvalidComponentUpdateSchedule, InvalidUpdateConstraintReason, ValidComponentUpdateSchedule, } from './component-update-schedule.js';
47
51
  export { Input, DEFAULT_BINDINGS } from './input.js';
48
52
  export type { ActionName, InjectedActionOperation, InputBindings, InputOptions } from './input.js';
53
+ export type { MouseMovement } from './input-mouse.js';
49
54
  export { ACTION_HELD_THRESHOLD, firstStandardPadValues, GAMEPAD_CODE_PREFIX, gamepadControl, } from './gamepad.js';
50
55
  export type { GamepadControl } from './gamepad.js';
51
56
  export { Pointer } from './pointer.js';
57
+ export { raycastPick } from './pointer-raycast.js';
58
+ export type { PickedTarget, PickTarget, RaycastPick, ViewportFraction } from './pointer-raycast.js';
52
59
  export type { PointerCamera, PointerDeps, PointerPick, PointerResolution } from './pointer.js';
53
60
  export { RUNTIME_BRIDGE_CAPABILITIES, RUNTIME_BRIDGE_PROTOCOL_VERSION, RUNTIME_BRIDGE_SYMBOL, RuntimeBridgeOperationError, } from './runtime-bridge.js';
54
61
  export type { RuntimeBridge, RuntimeBridgeActivation, RuntimeBridgeFailure, RuntimeControlRequest, RuntimeControlResult, RuntimeMetadata, RuntimeMode, } from './runtime-bridge.js';
@@ -56,7 +63,8 @@ export type { RenderBackend } from './render-readiness.js';
56
63
  export { RUNTIME_PROJECTION_LIMITS } from './runtime-inspection.js';
57
64
  export type { RuntimeSnapshotLight, RuntimeSnapshotLighting, RuntimeSnapshotPointLight, RuntimeSnapshotSun, RuntimeSnapshotPost, } from './runtime-lighting-snapshot.js';
58
65
  export type { ProjectedValue, ProjectionIssue, ProjectionMarker, ProjectionMarkerKind, RuntimeComponentSnapshot, RuntimeEntitySnapshot, RuntimeSnapshot, RuntimeSnapshotAudio, RuntimeSnapshotCamera, RuntimeSnapshotFilters, RuntimeSnapshotTime, RuntimeSnapshotUi, RuntimeTransformSnapshot, } from './runtime-inspection.js';
59
- export type { ArchetypeArt, ArchetypeManifest, BrowserArchetypeManifest, EntityTemplate, } from './archetype.js';
66
+ export type { ArchetypeArt, ArchetypeManifest, BrowserArchetypeManifest, EntityTemplate, SceneArchetypeNeeds, } from './archetype.js';
67
+ export { sceneArchetypeNeeds } from './archetype.js';
60
68
  export { Stats } from './stats.js';
61
69
  export type { StatValue } from './stats.js';
62
70
  export { GameUi } from './ui.js';
@@ -103,7 +111,7 @@ export type { TilemapCell, TilemapCellBounds, TilemapGridSpec, } from './tilemap
103
111
  export { COLLISION_SHAPES, DEFAULT_COLLISION_POLYGON, collisionBounds, collisionOverlap, collisionVertices, resolveCollisionPoints, } from './collision-shape.js';
104
112
  export type { CollisionBody, CollisionBounds, CollisionPoint, CollisionShape, } from './collision-shape.js';
105
113
  export type { EntityWith, NearestQueryContext, NearestSpatialQueryFilter, RayHit, SpatialQuery, SpatialQueryFilter, } from './spatial-query.js';
106
- export type { Point3d, QueryVolume3d, RayHit3d } from './spatial-query-3d.js';
114
+ export type { Point3d, QueryColliders3d, QueryVolume3d, RayHit3d } from './spatial-query-3d.js';
107
115
  export { Emitter } from './events.js';
108
116
  export { loadScene, spawnFromJson, resolveEntityComponents, resolveProps } from './scene.js';
109
117
  export type { SceneJson, SceneEntityJson, SceneComponentJson, SceneRegistry, SceneRenderJson, SceneLightingJson, ScenePostJson, PrefabJson, } from './scene.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Game } from './game.js';
2
2
  export { AssetLoader } from './assets/asset-loader.js';
3
3
  export { GltfModelBackend } from './assets/model-backend.js';
4
+ export { glbClipNames } from './assets/glb-clip-names.js';
4
5
  export { AudioSubsystem } from './audio/audio-subsystem.js';
5
6
  export { installDirectionalAnimation, installedDirectionalAnimation, isAnimationFacingProvider, resolveDirectionalClip, } from './animation/directional.js';
6
7
  export { isClipTarget } from './animation/clip-target.js';
@@ -12,6 +13,7 @@ export { SIMULATION_STEP, SIMULATION_TIME_EPSILON } from './fixed-step.js';
12
13
  export { CameraEffects } from './camera-effects.js';
13
14
  export { GameTime, advanceGameTime } from './game-time.js';
14
15
  export { isPerspectiveCameraJson, PERSPECTIVE_DEFAULTS, perspectiveCameraIssues, placePerspectiveCamera, resolvePerspectiveCamera, } from './scene-camera-3d.js';
16
+ export { ORBIT_DEFAULTS, resolveOrbit } from './scene-camera-orbit.js';
15
17
  export { isPerspectiveCamera, worldToNormalized } from './camera-projection.js';
16
18
  export { componentSpaceMismatch, componentSpaceOf, entityTransformIssues, resolveSceneSpace, SCENE_SPACES, sceneSpaceIssues, } from './scene-space.js';
17
19
  export { applyTransformJson } from './entity-transform.js';
@@ -23,8 +25,10 @@ export { resolveComponentUpdateSchedule } from './component-update-schedule.js';
23
25
  export { Input, DEFAULT_BINDINGS } from './input.js';
24
26
  export { ACTION_HELD_THRESHOLD, firstStandardPadValues, GAMEPAD_CODE_PREFIX, gamepadControl, } from './gamepad.js';
25
27
  export { Pointer } from './pointer.js';
28
+ export { raycastPick } from './pointer-raycast.js';
26
29
  export { RUNTIME_BRIDGE_CAPABILITIES, RUNTIME_BRIDGE_PROTOCOL_VERSION, RUNTIME_BRIDGE_SYMBOL, RuntimeBridgeOperationError, } from './runtime-bridge.js';
27
30
  export { RUNTIME_PROJECTION_LIMITS } from './runtime-inspection.js';
31
+ export { sceneArchetypeNeeds } from './archetype.js';
28
32
  export { Stats } from './stats.js';
29
33
  export { GameUi } from './ui.js';
30
34
  export { Sprite } from './components/sprite.js';
@@ -0,0 +1,83 @@
1
+ /** The prefix of a mouse button's binding code, beside keys and `Gamepad:` codes (issue #181). */
2
+ export declare const MOUSE_CODE_PREFIX = "Mouse:";
3
+ /** A mouse movement or wheel reading, in CSS pixels. */
4
+ export interface MouseMovement {
5
+ readonly x: number;
6
+ readonly y: number;
7
+ }
8
+ /** What the mouse reports to the Input it belongs to: a button code going down or up, and whether an Action binds a code. */
9
+ export interface MouseButtonSink {
10
+ press(code: string): void;
11
+ release(code: string): void;
12
+ binds(code: string): boolean;
13
+ }
14
+ export interface MouseInputOptions {
15
+ /** The game canvas: buttons, the wheel and the pointer lock are read on it. Without one, only injected movement counts. */
16
+ canvas?: HTMLCanvasElement;
17
+ /** Asked on a press over the canvas while it is unlocked: true asks for the lock with that press, which presses nothing once granted (CA-3) and goes through when refused. */
18
+ lockOnClick?: () => boolean;
19
+ /** Asked on each wheel over the canvas: true keeps the page from scrolling (an orbit camera zooms with it); false by default (CA-2). */
20
+ preventsWheelScroll?: () => boolean;
21
+ }
22
+ /** True for a `Mouse:` code no button reports, which therefore never matches. */
23
+ export declare function isUnknownMouseCode(code: string): boolean;
24
+ /**
25
+ * The mouse as an Input source (issue #181): its buttons as `Mouse:` binding
26
+ * codes, its movement while the pointer is locked to the game canvas, the
27
+ * wheel over the canvas, and the pointer lock itself. Movement and wheel
28
+ * accumulate between Simulation Steps, are frozen by `poll()` at a step's
29
+ * start and read zero again after `endStep()`.
30
+ */
31
+ export declare class MouseInput {
32
+ private readonly buttons;
33
+ private pendingMove;
34
+ private pendingWheel;
35
+ private move;
36
+ private wheel;
37
+ private readonly preventsWheelScroll;
38
+ private readonly canvas;
39
+ private readonly lockOnClick;
40
+ /** Set by dispose(): a lock the browser grants after it is given straight back. */
41
+ private disposed;
42
+ /** The codes whose press asked for the lock, awaiting the browser's answer: true while still held. */
43
+ private readonly askedLock;
44
+ constructor(buttons: MouseButtonSink, options?: MouseInputOptions);
45
+ /** Is the pointer locked to the game canvas right now? */
46
+ get locked(): boolean;
47
+ /** The locked movement of the current Simulation Step; zero between steps. */
48
+ movement(): MouseMovement;
49
+ /** The wheel delta of the current Simulation Step, in CSS px (positive scrolls down); zero between steps. */
50
+ wheelDelta(): number;
51
+ /** Freezes what accumulated since the last step as this step's movement and wheel. */
52
+ poll(): void;
53
+ /** The step is over: its movement and wheel read zero until the next poll. */
54
+ endStep(): void;
55
+ /** Adds a movement as if the pointer were locked (the Runtime Bridge's `look`, CA-4). */
56
+ inject(dx: number, dy: number): void;
57
+ /** Exits the pointer lock when the game canvas holds it; a lock on any other element is left alone. */
58
+ releaseLock(): void;
59
+ /** Asks the browser to lock the pointer to the game canvas; a refusal (or no Pointer Lock API) leaves it unlocked. */
60
+ requestLock(): void;
61
+ /**
62
+ * The presses that asked for a lock the browser refused (or cannot give)
63
+ * reach their `Mouse:` codes after all (maintainer decision on issue #181):
64
+ * still held, or as a tap when the button was let go before the answer.
65
+ */
66
+ private pressRefused;
67
+ /** Stops listening, and releases the pointer lock if this canvas holds it (a Game that goes away, the editor's Play stopping). */
68
+ dispose(): void;
69
+ /** `document.pointerLockElement`, which a browser without the Pointer Lock API (or a test DOM) lacks. */
70
+ private lockElement;
71
+ private onPointerDown;
72
+ private onPointerUp;
73
+ /**
74
+ * A button going down or up while another is held fires `pointermove`, not
75
+ * `pointerdown`/`pointerup` (Pointer Events' chorded buttons). Its bit in
76
+ * `buttons` tells which; a press counts over the canvas only, like the first.
77
+ */
78
+ private onPointerChord;
79
+ /** The browser menu would swallow the release of a bound `Mouse:Right`; other games keep their menu. */
80
+ private onContextMenu;
81
+ private onWheel;
82
+ private onMouseMove;
83
+ }