@realitycollective/webxr-interactions 0.1.0-preview.1 → 0.1.0-preview.2

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- Change log for the Reality Collective WebXR Interaction Extensions packages. All four packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
3
+ Change log for the Reality Collective WebXR Interaction Extensions packages. All five packages are versioned and released together; the version below is the one carried by the `v<version>` release tag.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
6
6
 
@@ -13,15 +13,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
13
13
  - `@realitycollective/iwsdk-interactions` - Meta IWSDK adapter: player-rig poses and stateful gamepads as the provider, IWSDK's own targeting (`Pressed` / `Grabbed` tags) forwarded as pre-resolved hints, and native grab fulfilment when the app enables IWSDK grabbing/physics. One-call `registerInteractions(world)` setup.
14
14
  - `@realitycollective/xrblocks-interactions` - EXPERIMENTAL Google XR Blocks adapter, structurally typed against xrblocks v0.20.0 (`Input.getFrame()` ray sources and direct touches, pooled structs copied, no haptics upstream).
15
15
  - `demos/playground` - the standalone interaction playground: the full station set from one portable `InteractionDescriptor`, with client-side audio cues, opt-in controller haptics and client-side toss ballistics.
16
+ - `@realitycollective/webxr-interactions` - per-source velocity. `VelocityTracker` differentiates the grip poses the runtime already samples into linear and angular velocity, and the runtime runs one by default (`velocity: false` turns it off, `{ smoothing }` averages it). A source on its first frame, or one that reappears after dropping out, reports no velocity rather than a jump from its last known pose, and a provider that supplies its own velocity keeps it. Read it from `runtime.onSample(...)` or `runtime.getSource(id)`. `poseVelocity` and `clampDeadzone` join the math helpers.
17
+ - `@realitycollective/webxr-interactions` - `routeAudioToSink`, the audio counterpart to `routeHapticsToProvider`: an opt-in that plays the cues named in a map through a client-supplied sink. Cues absent from the map are ignored, so an app sonifies only the moments it has sounds for.
18
+ - `@realitycollective/iwsdk-interactions` and `@realitycollective/threejs-interactions` - presence control. `setPresenceVisible(target, visible)` shows and hides the hand and controller visuals per side; the IWSDK adapter adds `setPresenceModality(mode)` to force hands or controllers over the automatic choice, and the three.js adapter takes the app's own models through `registerVisual(handedness, root)`. Each provider reports `supportsPresence`; the XR Blocks adapter reports false, since XR Blocks exposes no way to hide its visuals.
19
+ - `@realitycollective/threejs-interactions` - native WebXR pose velocity. Where the browser reports `linearVelocity` or `angularVelocity` with a grip pose (or with the wrist joint a hand falls back to), the provider passes it through on the snapshot and the core's tracker leaves it alone; where it does not, the tracker derives velocity from consecutive poses as before. The desktop mouse source synthesises nothing: its grip rides the camera ray, so the derived velocity is camera motion, and hand mechanics should be gated on `handedness !== "none"`.
20
+ - `@realitycollective/babylon-interactions` - a Babylon.js adapter, the fourth engine. It reads a `WebXRDefaultExperience` (controllers, motion controller trigger and squeeze, hand-tracking joints, session manager) for input, with `scene.onPointerObservable` as the desktop fallback, haptics through the motion controller's `pulse`, and presence over the visuals Babylon builds - motion controller root meshes and hand meshes. Babylon picks the visual per input source, so there is no modality switch. Hit-testing is a sphere test over registered nodes, with an optional `pickWithRay` hook for mesh-accurate targeting; the transform port moves, rotates and scales Babylon nodes from rest. Structurally typed against the Babylon API rather than importing `@babylonjs/core`, as the XR Blocks adapter is, so an upstream release cannot break the install - and so the whole adapter is covered headlessly by structural fakes. Written against the documented Babylon 7 API and not yet exercised against a real Babylon runtime.
21
+ - Tests for `@realitycollective/iwsdk-interactions`, which had none. `@iwsdk/core` imports headlessly in node, so the provider is covered against a structural fake world with no module mocking, and a parity suite instantiates all three providers and checks each one implements the whole `InputProvider` contract. The package's coverage ratchet moves off zero, and the other three ratchets rise to the floor they now measure.
16
22
 
17
23
  ### Changed
18
24
 
25
+ - `@realitycollective/iwsdk-interactions` - `pulse` resolves the side from the handedness recorded while sampling, rather than parsing the source id. Parsing the id remains the fallback for an id the provider did not produce.
26
+
19
27
  - `@realitycollective/iwsdk-interactions` targets **IWSDK 0.5.x**. Its peer range is now `>=0.5.0 <0.6.0` and it is developed and tested against `@iwsdk/core` 0.5.3. The adapter is source-compatible with 0.4.x and needed no code change, but 0.5 is the only line exercised, so it is the only line supported. Every symbol and runtime member the provider uses is present in both, and it already reads `world.input.xr.gamepads` rather than the accessor 0.5 deprecates.
20
28
  - `demos/playground` no longer compiles UIKitML at build time. `@iwsdk/vite-plugin-uikitml` was discontinued at 0.4.2 and only ever did `JSON.stringify(parse(source))`, so the panel is served as `.uikitml` from `public/ui/` and parsed on load. The demo drops that plugin, and its station markup uses `rgba()` rather than `background-opacity`, which the 0.5 parser removed.
21
29
 
22
30
  ### Notes
23
31
 
24
- - All four packages depend on `@realitycollective/webxr-input`, released independently from the [WebXR-Input](https://github.com/realitycollective/WebXR-Input) repository. That package must be published before this one.
32
+ - All five packages depend on `@realitycollective/webxr-input`, released independently from the [WebXR-Input](https://github.com/realitycollective/WebXR-Input) repository. That package must be published before this one.
25
33
  - The interaction packages themselves carry no IWSDK coupling beyond the adapter: `webxr-interactions` is engine-free, and `threejs-interactions` and `xrblocks-interactions` peer only on three.js.
26
34
 
27
35
  [0.1.0]: https://github.com/realitycollective/WebXR-Interactions/commits/main
@@ -29,3 +29,20 @@ export type FeedbackListener = (intent: FeedbackIntent) => void;
29
29
  * Returns the unsubscribe. Audio remains entirely the client's concern.
30
30
  */
31
31
  export declare function routeHapticsToProvider(onFeedback: (listener: FeedbackListener) => Unsubscribe, provider: InputProvider, scale?: number): Unsubscribe;
32
+ /**
33
+ * Whatever the client uses to play a sound. Deliberately tiny: an id the
34
+ * client resolved to a buffer/sample of its own, and a gain. The core
35
+ * never loads, decodes or owns audio.
36
+ */
37
+ export interface FeedbackAudioSink {
38
+ play(cueId: string, options?: {
39
+ gain?: number;
40
+ }): void;
41
+ }
42
+ /**
43
+ * Client opt-in: play a sound for the feedback intents named in `cueMap`.
44
+ * Cues absent from the map are ignored, so an app sonifies only the
45
+ * moments it has sounds for. Gain is the intent's intensity scaled by
46
+ * `gainScale`, capped at 1. Returns the unsubscribe.
47
+ */
48
+ export declare function routeAudioToSink(onFeedback: (listener: FeedbackListener) => Unsubscribe, sink: FeedbackAudioSink, cueMap: Partial<Record<FeedbackCue, string>>, gainScale?: number): Unsubscribe;
package/dist/feedback.js CHANGED
@@ -12,4 +12,18 @@ export function routeHapticsToProvider(onFeedback, provider, scale = 1) {
12
12
  provider.pulse?.(intent.sourceId, Math.min(1, intent.intensity * scale), intent.durationMs);
13
13
  });
14
14
  }
15
+ /**
16
+ * Client opt-in: play a sound for the feedback intents named in `cueMap`.
17
+ * Cues absent from the map are ignored, so an app sonifies only the
18
+ * moments it has sounds for. Gain is the intent's intensity scaled by
19
+ * `gainScale`, capped at 1. Returns the unsubscribe.
20
+ */
21
+ export function routeAudioToSink(onFeedback, sink, cueMap, gainScale = 1) {
22
+ return onFeedback((intent) => {
23
+ const cueId = cueMap[intent.cue];
24
+ if (cueId === undefined)
25
+ return;
26
+ sink.play(cueId, { gain: Math.min(1, intent.intensity * gainScale) });
27
+ });
28
+ }
15
29
  //# sourceMappingURL=feedback.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAuD,EACvD,QAAuB,EACvB,KAAK,GAAG,CAAC;IAET,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,EAAE;QAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,OAAO;QAC7B,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO;YAAE,OAAO;QAChD,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Feedback hooks - haptics/audio stay OUT of the framework; the client\n * implements them. Behaviours emit *intents* describing the physical\n * moment (\"this press just actuated, a short strong pulse would fit\");\n * the client subscribes and realises them however it likes (haptic pulse\n * on controllers, an audio cue, both, neither).\n *\n * `routeHapticsToProvider` is the one convenience the framework offers -\n * an explicit opt-in the client calls, never an automatic behaviour.\n */\nimport type { InputProvider, Unsubscribe } from \"@realitycollective/webxr-input\";\n\n/** A named moment a client may want to sonify/hapticise. */\nexport type FeedbackCue =\n | \"hover\"\n | \"press\"\n | \"actuate\"\n | \"release\"\n | \"grab\"\n | \"drop\"\n | \"score\"\n | \"dwellComplete\"\n | \"valueTick\"; // discrete steps on analog controls (dial detents etc.)\n\nexport interface FeedbackIntent {\n cue: FeedbackCue;\n interactableId: string;\n behaviourKind?: string;\n /** The input source that caused the moment - haptics target this. */\n sourceId?: string;\n /** Suggested haptic intensity 0..1 (clients may remap freely). */\n intensity: number;\n /** Suggested haptic duration in ms. */\n durationMs: number;\n}\n\nexport type FeedbackListener = (intent: FeedbackIntent) => void;\n\n/**\n * Client opt-in: forward the haptic half of feedback intents to the input\n * provider's `pulse` (which no-ops on sources without actuators - hands).\n * Returns the unsubscribe. Audio remains entirely the client's concern.\n */\nexport function routeHapticsToProvider(\n onFeedback: (listener: FeedbackListener) => Unsubscribe,\n provider: InputProvider,\n scale = 1,\n): Unsubscribe {\n return onFeedback((intent) => {\n if (!intent.sourceId) return;\n if (!provider.getCapabilities().haptics) return;\n provider.pulse?.(intent.sourceId, Math.min(1, intent.intensity * scale), intent.durationMs);\n });\n}\n"]}
1
+ {"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAuD,EACvD,QAAuB,EACvB,KAAK,GAAG,CAAC;IAET,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,EAAE;QAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,OAAO;QAC7B,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO;YAAE,OAAO;QAChD,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC;AAWD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAuD,EACvD,IAAuB,EACvB,MAA4C,EAC5C,SAAS,GAAG,CAAC;IAEb,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAChC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Feedback hooks - haptics/audio stay OUT of the framework; the client\n * implements them. Behaviours emit *intents* describing the physical\n * moment (\"this press just actuated, a short strong pulse would fit\");\n * the client subscribes and realises them however it likes (haptic pulse\n * on controllers, an audio cue, both, neither).\n *\n * `routeHapticsToProvider` is the one convenience the framework offers -\n * an explicit opt-in the client calls, never an automatic behaviour.\n */\nimport type { InputProvider, Unsubscribe } from \"@realitycollective/webxr-input\";\n\n/** A named moment a client may want to sonify/hapticise. */\nexport type FeedbackCue =\n | \"hover\"\n | \"press\"\n | \"actuate\"\n | \"release\"\n | \"grab\"\n | \"drop\"\n | \"score\"\n | \"dwellComplete\"\n | \"valueTick\"; // discrete steps on analog controls (dial detents etc.)\n\nexport interface FeedbackIntent {\n cue: FeedbackCue;\n interactableId: string;\n behaviourKind?: string;\n /** The input source that caused the moment - haptics target this. */\n sourceId?: string;\n /** Suggested haptic intensity 0..1 (clients may remap freely). */\n intensity: number;\n /** Suggested haptic duration in ms. */\n durationMs: number;\n}\n\nexport type FeedbackListener = (intent: FeedbackIntent) => void;\n\n/**\n * Client opt-in: forward the haptic half of feedback intents to the input\n * provider's `pulse` (which no-ops on sources without actuators - hands).\n * Returns the unsubscribe. Audio remains entirely the client's concern.\n */\nexport function routeHapticsToProvider(\n onFeedback: (listener: FeedbackListener) => Unsubscribe,\n provider: InputProvider,\n scale = 1,\n): Unsubscribe {\n return onFeedback((intent) => {\n if (!intent.sourceId) return;\n if (!provider.getCapabilities().haptics) return;\n provider.pulse?.(intent.sourceId, Math.min(1, intent.intensity * scale), intent.durationMs);\n });\n}\n\n/**\n * Whatever the client uses to play a sound. Deliberately tiny: an id the\n * client resolved to a buffer/sample of its own, and a gain. The core\n * never loads, decodes or owns audio.\n */\nexport interface FeedbackAudioSink {\n play(cueId: string, options?: { gain?: number }): void;\n}\n\n/**\n * Client opt-in: play a sound for the feedback intents named in `cueMap`.\n * Cues absent from the map are ignored, so an app sonifies only the\n * moments it has sounds for. Gain is the intent's intensity scaled by\n * `gainScale`, capped at 1. Returns the unsubscribe.\n */\nexport function routeAudioToSink(\n onFeedback: (listener: FeedbackListener) => Unsubscribe,\n sink: FeedbackAudioSink,\n cueMap: Partial<Record<FeedbackCue, string>>,\n gainScale = 1,\n): Unsubscribe {\n return onFeedback((intent) => {\n const cueId = cueMap[intent.cue];\n if (cueId === undefined) return;\n sink.play(cueId, { gain: Math.min(1, intent.intensity * gainScale) });\n });\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./feedback.js";
21
21
  export * from "./ports.js";
22
22
  export * from "./gaze.js";
23
23
  export * from "./descriptor.js";
24
+ export * from "./velocity-tracker.js";
24
25
  export * from "./runtime.js";
25
26
  export * from "./pointer-bridge.js";
26
27
  export * from "./behaviours/behaviour.js";
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ export * from "./feedback.js";
21
21
  export * from "./ports.js";
22
22
  export * from "./gaze.js";
23
23
  export * from "./descriptor.js";
24
+ export * from "./velocity-tracker.js";
24
25
  export * from "./runtime.js";
25
26
  export * from "./pointer-bridge.js";
26
27
  export * from "./behaviours/behaviour.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,gCAAgC,CAAC;AAE/C,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC","sourcesContent":["/**\n * @realitycollective/webxr-interactions - the engine-free interaction core.\n *\n * Layers (each strictly on the one below):\n * - behaviours (press, pulse, hinge, dial, slide, grab, tossScore) -\n * pure state machines driven by `update(dt)`;\n * - the runtime/binder - targeting, press/grab transitions, gaze gating\n * + dwell, capability negotiation, events, feedback intents;\n * - the shared input contracts, re-exported from\n * `@realitycollective/webxr-input`.\n *\n * Pair with an engine adapter: `@realitycollective/threejs-interactions`\n * (standalone WebXR + three.js), `@realitycollective/iwsdk-interactions`\n * (Meta IWSDK) or `@realitycollective/xrblocks-interactions` (Google XR\n * Blocks, experimental).\n */\nexport * from \"@realitycollective/webxr-input\";\n\nexport * from \"./math.js\";\nexport * from \"./events.js\";\nexport * from \"./feedback.js\";\nexport * from \"./ports.js\";\nexport * from \"./gaze.js\";\nexport * from \"./descriptor.js\";\nexport * from \"./runtime.js\";\nexport * from \"./pointer-bridge.js\";\n\nexport * from \"./behaviours/behaviour.js\";\nexport * from \"./behaviours/press.js\";\nexport * from \"./behaviours/pulse.js\";\nexport * from \"./behaviours/hinge.js\";\nexport * from \"./behaviours/dial.js\";\nexport * from \"./behaviours/slide.js\";\nexport * from \"./behaviours/grab.js\";\nexport * from \"./behaviours/toss-score.js\";\nexport * from \"./behaviours/factory.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,gCAAgC,CAAC;AAE/C,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC","sourcesContent":["/**\n * @realitycollective/webxr-interactions - the engine-free interaction core.\n *\n * Layers (each strictly on the one below):\n * - behaviours (press, pulse, hinge, dial, slide, grab, tossScore) -\n * pure state machines driven by `update(dt)`;\n * - the runtime/binder - targeting, press/grab transitions, gaze gating\n * + dwell, capability negotiation, events, feedback intents;\n * - the shared input contracts, re-exported from\n * `@realitycollective/webxr-input`.\n *\n * Pair with an engine adapter: `@realitycollective/threejs-interactions`\n * (standalone WebXR + three.js), `@realitycollective/iwsdk-interactions`\n * (Meta IWSDK) or `@realitycollective/xrblocks-interactions` (Google XR\n * Blocks, experimental).\n */\nexport * from \"@realitycollective/webxr-input\";\n\nexport * from \"./math.js\";\nexport * from \"./events.js\";\nexport * from \"./feedback.js\";\nexport * from \"./ports.js\";\nexport * from \"./gaze.js\";\nexport * from \"./descriptor.js\";\nexport * from \"./velocity-tracker.js\";\nexport * from \"./runtime.js\";\nexport * from \"./pointer-bridge.js\";\n\nexport * from \"./behaviours/behaviour.js\";\nexport * from \"./behaviours/press.js\";\nexport * from \"./behaviours/pulse.js\";\nexport * from \"./behaviours/hinge.js\";\nexport * from \"./behaviours/dial.js\";\nexport * from \"./behaviours/slide.js\";\nexport * from \"./behaviours/grab.js\";\nexport * from \"./behaviours/toss-score.js\";\nexport * from \"./behaviours/factory.js\";\n"]}
package/dist/math.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * plus the tuple vector/quaternion helpers the binder and behaviours need.
5
5
  * No engine imports; everything is unit-tested headless.
6
6
  */
7
- import type { QuatTuple, RayTuple, Vec3Tuple } from "@realitycollective/webxr-input";
7
+ import type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from "@realitycollective/webxr-input";
8
8
  /** Clamp to the unit interval. */
9
9
  export declare function clamp01(t: number): number;
10
10
  /**
@@ -66,3 +66,19 @@ export declare function rayPointDistance(ray: RayTuple, point: Vec3Tuple): {
66
66
  };
67
67
  /** Angular offset (radians) between a ray direction and the direction to a point. */
68
68
  export declare function rayAngleTo(ray: RayTuple, point: Vec3Tuple): number;
69
+ /**
70
+ * Linear and angular velocity between two poses, in metres per second and
71
+ * radians per second. Linear is the position delta over `dtSeconds`;
72
+ * angular comes from the delta quaternion `next * conj(prev)`, taken along
73
+ * the shortest arc, as `axis * angle / dtSeconds`. Returns zeros when
74
+ * `dtSeconds` is zero, negative or not finite.
75
+ *
76
+ * Mirrors velocityBetween in @realitycollective/webxr-input 0.1.1; switch
77
+ * to the import once that ships.
78
+ */
79
+ export declare function poseVelocity(prev: PoseTuple, next: PoseTuple, dtSeconds: number): {
80
+ linear: Vec3Tuple;
81
+ angular: Vec3Tuple;
82
+ };
83
+ /** Zero out a signal below `threshold`, leaving larger values untouched. */
84
+ export declare function clampDeadzone(value: number, threshold: number): number;
package/dist/math.js CHANGED
@@ -161,4 +161,34 @@ export function rayAngleTo(ray, point) {
161
161
  const d = vDot(vNormalize(ray.direction), dir);
162
162
  return Math.acos(Math.min(1, Math.max(-1, d)));
163
163
  }
164
+ /**
165
+ * Linear and angular velocity between two poses, in metres per second and
166
+ * radians per second. Linear is the position delta over `dtSeconds`;
167
+ * angular comes from the delta quaternion `next * conj(prev)`, taken along
168
+ * the shortest arc, as `axis * angle / dtSeconds`. Returns zeros when
169
+ * `dtSeconds` is zero, negative or not finite.
170
+ *
171
+ * Mirrors velocityBetween in @realitycollective/webxr-input 0.1.1; switch
172
+ * to the import once that ships.
173
+ */
174
+ export function poseVelocity(prev, next, dtSeconds) {
175
+ if (!Number.isFinite(dtSeconds) || dtSeconds <= 0) {
176
+ return { linear: [0, 0, 0], angular: [0, 0, 0] };
177
+ }
178
+ const linear = vScale(vSub(next.position, prev.position), 1 / dtSeconds);
179
+ let delta = quatMultiply(next.quaternion, quatConjugate(prev.quaternion));
180
+ // Shortest arc: q and -q are the same rotation, so flip to the hemisphere
181
+ // where the angle is under half a turn.
182
+ if (delta[3] < 0)
183
+ delta = [-delta[0], -delta[1], -delta[2], -delta[3]];
184
+ const w = delta[3] < -1 ? -1 : delta[3] > 1 ? 1 : delta[3];
185
+ const angle = 2 * Math.acos(w);
186
+ // vNormalize guards the near-zero axis (no rotation this frame).
187
+ const axis = vNormalize([delta[0], delta[1], delta[2]]);
188
+ return { linear, angular: vScale(axis, angle / dtSeconds) };
189
+ }
190
+ /** Zero out a signal below `threshold`, leaving larger values untouched. */
191
+ export function clampDeadzone(value, threshold) {
192
+ return Math.abs(value) < threshold ? 0 : value;
193
+ }
164
194
  //# sourceMappingURL=math.js.map
package/dist/math.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"math.js","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure interaction math - the proven motion functions extracted from the\n * Pale Signal research (springs, hinge/dial normalisation, hoop scoring),\n * plus the tuple vector/quaternion helpers the binder and behaviours need.\n * No engine imports; everything is unit-tested headless.\n */\nimport type { QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\n\n/** Clamp to the unit interval. */\nexport function clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n\n/**\n * Normalize a measured displacement/angle against a signed range to 0..1.\n * Sign-agnostic: a range of `-π/2` maps `-π/2 → 1`.\n */\nexport function normalize01(value: number, range: number): number {\n if (range === 0) return 0;\n return clamp01(value / range);\n}\n\n/**\n * Did a tossed object pass *through* a hoop this frame? True when it crossed\n * the hoop plane in the scoring direction (downward - `prev` above, `curr`\n * at/below) while inside the ring (`radial <= radius`).\n */\nexport function passedThroughHoop(\n prevAlong: number,\n currAlong: number,\n planeAlong: number,\n radial: number,\n radius: number,\n): boolean {\n const crossedDown = prevAlong > planeAlong && currAlong <= planeAlong;\n return crossedDown && radial <= radius;\n}\n\n/**\n * One step of a damped harmonic oscillator (semi-implicit Euler).\n * `out[0]` = next value, `out[1]` = next velocity. An under-damped spring\n * (damping ratio ≈ 0.3-0.5) is what gives a pressed button its bounce.\n * `dt` is clamped to 1/30 s so a long frame can't blow the integrator up.\n */\nexport function springStep(\n value: number,\n velocity: number,\n target: number,\n stiffness: number,\n damping: number,\n dt: number,\n out: Float32Array = new Float32Array(2),\n): Float32Array {\n const h = Math.min(dt, 1 / 30);\n const accel = -stiffness * (value - target) - damping * velocity;\n const nextVelocity = velocity + accel * h;\n out[0] = value + nextVelocity * h;\n out[1] = nextVelocity;\n return out;\n}\n\n/** Exponential decay of `value` toward 0 over `dt` at `ratePerSec`. */\nexport function decay(value: number, ratePerSec: number, dt: number): number {\n const next = value * Math.exp(-ratePerSec * Math.max(dt, 0));\n return next < 1e-4 ? 0 : next;\n}\n\n/** Penner's ease-out-bounce on `t ∈ [0,1]`. */\nexport function easeOutBounce(t: number): number {\n const n1 = 7.5625;\n const d1 = 2.75;\n let x = clamp01(t);\n if (x < 1 / d1) return n1 * x * x;\n if (x < 2 / d1) return n1 * (x -= 1.5 / d1) * x + 0.75;\n if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + 0.9375;\n return n1 * (x -= 2.625 / d1) * x + 0.984375;\n}\n\n/** Clamp a value to the symmetric range `[-|mag|, +|mag|]`. */\nexport function clampSym(x: number, mag: number): number {\n const m = Math.abs(mag);\n return x < -m ? -m : x > m ? m : x;\n}\n\n/**\n * Hinge angle for a position-driven lever: given the hand's offset from the\n * pivot resolved into the hinge plane - `alongRest` (toward the arm's rest\n * direction) and `alongSwing` (the in-plane perpendicular) - the angle the\n * arm should take to point at the hand, clamped to `±|maxMag|`.\n */\nexport function hingeAngleToHand(alongRest: number, alongSwing: number, maxMag: number): number {\n return clampSym(Math.atan2(alongSwing, alongRest), maxMag);\n}\n\n/**\n * Map a centered hinge angle in `[-|mag|, +|mag|]` to a 0..1 analog value\n * with the rest (0) at 0.5 - a bidirectional lever as one normalized value.\n */\nexport function centeredUnit(angle: number, mag: number): number {\n const m = Math.abs(mag);\n if (m < 1e-6) return 0.5;\n return clamp01(0.5 + (0.5 * angle) / m);\n}\n\n// ---------------------------------------------------------------------------\n// Tuple vector / quaternion helpers (allocation-light, no engine types).\n// ---------------------------------------------------------------------------\n\nexport function vSub(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\n}\n\nexport function vAdd(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];\n}\n\nexport function vScale(a: Vec3Tuple, s: number): Vec3Tuple {\n return [a[0] * s, a[1] * s, a[2] * s];\n}\n\nexport function vDot(a: Vec3Tuple, b: Vec3Tuple): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\nexport function vCross(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n ];\n}\n\nexport function vLength(a: Vec3Tuple): number {\n return Math.hypot(a[0], a[1], a[2]);\n}\n\nexport function vDistance(a: Vec3Tuple, b: Vec3Tuple): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n}\n\nexport function vNormalize(a: Vec3Tuple): Vec3Tuple {\n const l = vLength(a);\n return l < 1e-9 ? [0, 0, 0] : [a[0] / l, a[1] / l, a[2] / l];\n}\n\n/** Quaternion from an axis (normalised) and an angle in radians. */\nexport function quatFromAxisAngle(axis: Vec3Tuple, angle: number): QuatTuple {\n const half = angle / 2;\n const s = Math.sin(half);\n return [axis[0] * s, axis[1] * s, axis[2] * s, Math.cos(half)];\n}\n\nexport function quatMultiply(a: QuatTuple, b: QuatTuple): QuatTuple {\n const [ax, ay, az, aw] = a;\n const [bx, by, bz, bw] = b;\n return [\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n aw * bw - ax * bx - ay * by - az * bz,\n ];\n}\n\nexport function quatConjugate(q: QuatTuple): QuatTuple {\n return [-q[0], -q[1], -q[2], q[3]];\n}\n\n/** Rotate a vector by a quaternion. */\nexport function vApplyQuat(v: Vec3Tuple, q: QuatTuple): Vec3Tuple {\n const [x, y, z] = v;\n const [qx, qy, qz, qw] = q;\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (qy * z - qz * y);\n const ty = 2 * (qz * x - qx * z);\n const tz = 2 * (qx * y - qy * x);\n // v + qw * t + cross(q.xyz, t)\n return [\n x + qw * tx + qy * tz - qz * ty,\n y + qw * ty + qz * tx - qx * tz,\n z + qw * tz + qx * ty - qy * tx,\n ];\n}\n\n/** Transform a world-space point into the local frame of `pose`. */\nexport function worldToLocal(\n point: Vec3Tuple,\n posePosition: Vec3Tuple,\n poseQuaternion: QuatTuple,\n): Vec3Tuple {\n return vApplyQuat(vSub(point, posePosition), quatConjugate(poseQuaternion));\n}\n\n/** Closest-approach distance from a ray to a point, and the along-ray t. */\nexport function rayPointDistance(ray: RayTuple, point: Vec3Tuple): { distance: number; t: number } {\n const toPoint = vSub(point, ray.origin);\n const t = vDot(toPoint, ray.direction);\n if (t <= 0) return { distance: vLength(toPoint), t: 0 };\n const closest = vAdd(ray.origin, vScale(ray.direction, t));\n return { distance: vDistance(closest, point), t };\n}\n\n/** Angular offset (radians) between a ray direction and the direction to a point. */\nexport function rayAngleTo(ray: RayTuple, point: Vec3Tuple): number {\n const dir = vNormalize(vSub(point, ray.origin));\n const d = vDot(vNormalize(ray.direction), dir);\n return Math.acos(Math.min(1, Math.max(-1, d)));\n}\n"]}
1
+ {"version":3,"file":"math.js","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure interaction math - the proven motion functions extracted from the\n * Pale Signal research (springs, hinge/dial normalisation, hoop scoring),\n * plus the tuple vector/quaternion helpers the binder and behaviours need.\n * No engine imports; everything is unit-tested headless.\n */\nimport type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\n\n/** Clamp to the unit interval. */\nexport function clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n\n/**\n * Normalize a measured displacement/angle against a signed range to 0..1.\n * Sign-agnostic: a range of `-π/2` maps `-π/2 → 1`.\n */\nexport function normalize01(value: number, range: number): number {\n if (range === 0) return 0;\n return clamp01(value / range);\n}\n\n/**\n * Did a tossed object pass *through* a hoop this frame? True when it crossed\n * the hoop plane in the scoring direction (downward - `prev` above, `curr`\n * at/below) while inside the ring (`radial <= radius`).\n */\nexport function passedThroughHoop(\n prevAlong: number,\n currAlong: number,\n planeAlong: number,\n radial: number,\n radius: number,\n): boolean {\n const crossedDown = prevAlong > planeAlong && currAlong <= planeAlong;\n return crossedDown && radial <= radius;\n}\n\n/**\n * One step of a damped harmonic oscillator (semi-implicit Euler).\n * `out[0]` = next value, `out[1]` = next velocity. An under-damped spring\n * (damping ratio ≈ 0.3-0.5) is what gives a pressed button its bounce.\n * `dt` is clamped to 1/30 s so a long frame can't blow the integrator up.\n */\nexport function springStep(\n value: number,\n velocity: number,\n target: number,\n stiffness: number,\n damping: number,\n dt: number,\n out: Float32Array = new Float32Array(2),\n): Float32Array {\n const h = Math.min(dt, 1 / 30);\n const accel = -stiffness * (value - target) - damping * velocity;\n const nextVelocity = velocity + accel * h;\n out[0] = value + nextVelocity * h;\n out[1] = nextVelocity;\n return out;\n}\n\n/** Exponential decay of `value` toward 0 over `dt` at `ratePerSec`. */\nexport function decay(value: number, ratePerSec: number, dt: number): number {\n const next = value * Math.exp(-ratePerSec * Math.max(dt, 0));\n return next < 1e-4 ? 0 : next;\n}\n\n/** Penner's ease-out-bounce on `t ∈ [0,1]`. */\nexport function easeOutBounce(t: number): number {\n const n1 = 7.5625;\n const d1 = 2.75;\n let x = clamp01(t);\n if (x < 1 / d1) return n1 * x * x;\n if (x < 2 / d1) return n1 * (x -= 1.5 / d1) * x + 0.75;\n if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + 0.9375;\n return n1 * (x -= 2.625 / d1) * x + 0.984375;\n}\n\n/** Clamp a value to the symmetric range `[-|mag|, +|mag|]`. */\nexport function clampSym(x: number, mag: number): number {\n const m = Math.abs(mag);\n return x < -m ? -m : x > m ? m : x;\n}\n\n/**\n * Hinge angle for a position-driven lever: given the hand's offset from the\n * pivot resolved into the hinge plane - `alongRest` (toward the arm's rest\n * direction) and `alongSwing` (the in-plane perpendicular) - the angle the\n * arm should take to point at the hand, clamped to `±|maxMag|`.\n */\nexport function hingeAngleToHand(alongRest: number, alongSwing: number, maxMag: number): number {\n return clampSym(Math.atan2(alongSwing, alongRest), maxMag);\n}\n\n/**\n * Map a centered hinge angle in `[-|mag|, +|mag|]` to a 0..1 analog value\n * with the rest (0) at 0.5 - a bidirectional lever as one normalized value.\n */\nexport function centeredUnit(angle: number, mag: number): number {\n const m = Math.abs(mag);\n if (m < 1e-6) return 0.5;\n return clamp01(0.5 + (0.5 * angle) / m);\n}\n\n// ---------------------------------------------------------------------------\n// Tuple vector / quaternion helpers (allocation-light, no engine types).\n// ---------------------------------------------------------------------------\n\nexport function vSub(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\n}\n\nexport function vAdd(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];\n}\n\nexport function vScale(a: Vec3Tuple, s: number): Vec3Tuple {\n return [a[0] * s, a[1] * s, a[2] * s];\n}\n\nexport function vDot(a: Vec3Tuple, b: Vec3Tuple): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\nexport function vCross(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n ];\n}\n\nexport function vLength(a: Vec3Tuple): number {\n return Math.hypot(a[0], a[1], a[2]);\n}\n\nexport function vDistance(a: Vec3Tuple, b: Vec3Tuple): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n}\n\nexport function vNormalize(a: Vec3Tuple): Vec3Tuple {\n const l = vLength(a);\n return l < 1e-9 ? [0, 0, 0] : [a[0] / l, a[1] / l, a[2] / l];\n}\n\n/** Quaternion from an axis (normalised) and an angle in radians. */\nexport function quatFromAxisAngle(axis: Vec3Tuple, angle: number): QuatTuple {\n const half = angle / 2;\n const s = Math.sin(half);\n return [axis[0] * s, axis[1] * s, axis[2] * s, Math.cos(half)];\n}\n\nexport function quatMultiply(a: QuatTuple, b: QuatTuple): QuatTuple {\n const [ax, ay, az, aw] = a;\n const [bx, by, bz, bw] = b;\n return [\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n aw * bw - ax * bx - ay * by - az * bz,\n ];\n}\n\nexport function quatConjugate(q: QuatTuple): QuatTuple {\n return [-q[0], -q[1], -q[2], q[3]];\n}\n\n/** Rotate a vector by a quaternion. */\nexport function vApplyQuat(v: Vec3Tuple, q: QuatTuple): Vec3Tuple {\n const [x, y, z] = v;\n const [qx, qy, qz, qw] = q;\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (qy * z - qz * y);\n const ty = 2 * (qz * x - qx * z);\n const tz = 2 * (qx * y - qy * x);\n // v + qw * t + cross(q.xyz, t)\n return [\n x + qw * tx + qy * tz - qz * ty,\n y + qw * ty + qz * tx - qx * tz,\n z + qw * tz + qx * ty - qy * tx,\n ];\n}\n\n/** Transform a world-space point into the local frame of `pose`. */\nexport function worldToLocal(\n point: Vec3Tuple,\n posePosition: Vec3Tuple,\n poseQuaternion: QuatTuple,\n): Vec3Tuple {\n return vApplyQuat(vSub(point, posePosition), quatConjugate(poseQuaternion));\n}\n\n/** Closest-approach distance from a ray to a point, and the along-ray t. */\nexport function rayPointDistance(ray: RayTuple, point: Vec3Tuple): { distance: number; t: number } {\n const toPoint = vSub(point, ray.origin);\n const t = vDot(toPoint, ray.direction);\n if (t <= 0) return { distance: vLength(toPoint), t: 0 };\n const closest = vAdd(ray.origin, vScale(ray.direction, t));\n return { distance: vDistance(closest, point), t };\n}\n\n/** Angular offset (radians) between a ray direction and the direction to a point. */\nexport function rayAngleTo(ray: RayTuple, point: Vec3Tuple): number {\n const dir = vNormalize(vSub(point, ray.origin));\n const d = vDot(vNormalize(ray.direction), dir);\n return Math.acos(Math.min(1, Math.max(-1, d)));\n}\n\n/**\n * Linear and angular velocity between two poses, in metres per second and\n * radians per second. Linear is the position delta over `dtSeconds`;\n * angular comes from the delta quaternion `next * conj(prev)`, taken along\n * the shortest arc, as `axis * angle / dtSeconds`. Returns zeros when\n * `dtSeconds` is zero, negative or not finite.\n *\n * Mirrors velocityBetween in @realitycollective/webxr-input 0.1.1; switch\n * to the import once that ships.\n */\nexport function poseVelocity(\n prev: PoseTuple,\n next: PoseTuple,\n dtSeconds: number,\n): { linear: Vec3Tuple; angular: Vec3Tuple } {\n if (!Number.isFinite(dtSeconds) || dtSeconds <= 0) {\n return { linear: [0, 0, 0], angular: [0, 0, 0] };\n }\n const linear = vScale(vSub(next.position, prev.position), 1 / dtSeconds);\n let delta = quatMultiply(next.quaternion, quatConjugate(prev.quaternion));\n // Shortest arc: q and -q are the same rotation, so flip to the hemisphere\n // where the angle is under half a turn.\n if (delta[3] < 0) delta = [-delta[0], -delta[1], -delta[2], -delta[3]];\n const w = delta[3] < -1 ? -1 : delta[3] > 1 ? 1 : delta[3];\n const angle = 2 * Math.acos(w);\n // vNormalize guards the near-zero axis (no rotation this frame).\n const axis = vNormalize([delta[0], delta[1], delta[2]]);\n return { linear, angular: vScale(axis, angle / dtSeconds) };\n}\n\n/** Zero out a signal below `threshold`, leaving larger values untouched. */\nexport function clampDeadzone(value: number, threshold: number): number {\n return Math.abs(value) < threshold ? 0 : value;\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -20,6 +20,7 @@ import { type InteractionEventListener } from "./events.js";
20
20
  import type { FeedbackListener } from "./feedback.js";
21
21
  import { type DwellConfig } from "./gaze.js";
22
22
  import type { HitTester, TransformPort } from "./ports.js";
23
+ import { type InputSourceSnapshotWithVelocity, type VelocityTrackerOptions } from "./velocity-tracker.js";
23
24
  /** Interactor id used when gaze is synthesized from the head pose. */
24
25
  export declare const HEAD_GAZE_INTERACTOR_ID = "head-gaze";
25
26
  export interface InteractableState {
@@ -38,6 +39,12 @@ export interface InteractionRuntimeOptions {
38
39
  provider: InputProvider;
39
40
  hitTester?: HitTester;
40
41
  dwellDefaults?: DwellConfig;
42
+ /**
43
+ * Per-source velocity tracking. On by default with no smoothing; pass
44
+ * `false` to skip it, or options to smooth. The tracker only fills in
45
+ * what the provider did not supply.
46
+ */
47
+ velocity?: VelocityTrackerOptions | false;
41
48
  }
42
49
  export interface RegisterPorts {
43
50
  transform?: TransformPort;
@@ -51,6 +58,8 @@ export declare class InteractionRuntime {
51
58
  private readonly events;
52
59
  private readonly feedbackEmitter;
53
60
  private readonly afterSample;
61
+ private readonly velocityTracker;
62
+ private lastSources;
54
63
  private capabilities;
55
64
  private readonly unsubscribeCaps;
56
65
  private disposed;
@@ -67,6 +76,16 @@ export declare class InteractionRuntime {
67
76
  onFeedback(listener: FeedbackListener): Unsubscribe;
68
77
  /** Sampled sources for this frame - the UI Extensions pointer bridge hook. */
69
78
  onSourcesSampled(listener: (sources: readonly InputSourceSnapshot[]) => void): Unsubscribe;
79
+ /**
80
+ * This frame's sources, velocity included. The same stream as
81
+ * {@link onSourcesSampled} with the velocity fields declared.
82
+ */
83
+ onSample(listener: (sources: readonly InputSourceSnapshotWithVelocity[]) => void): Unsubscribe;
84
+ /**
85
+ * The last sample of one source, velocity included. Undefined before the
86
+ * first update, and once the source stops reporting.
87
+ */
88
+ getSource(id: string): InputSourceSnapshotWithVelocity | undefined;
70
89
  getCapabilities(): InputCapabilities;
71
90
  getProvider(): InputProvider;
72
91
  update(dt: number): void;
package/dist/runtime.js CHANGED
@@ -19,6 +19,7 @@ import { GrabBehaviour } from "./behaviours/grab.js";
19
19
  import { Emitter } from "./events.js";
20
20
  import { DwellState, resolveDwellConfig, DWELL_DEFAULTS } from "./gaze.js";
21
21
  import { vApplyQuat } from "./math.js";
22
+ import { VelocityTracker, } from "./velocity-tracker.js";
22
23
  const DEFAULT_POKE_RADIUS = 0.05;
23
24
  /** Interactor id used when gaze is synthesized from the head pose. */
24
25
  export const HEAD_GAZE_INTERACTOR_ID = "head-gaze";
@@ -31,6 +32,8 @@ export class InteractionRuntime {
31
32
  events = new Emitter();
32
33
  feedbackEmitter = new Emitter();
33
34
  afterSample = new Emitter();
35
+ velocityTracker;
36
+ lastSources = new Map();
34
37
  capabilities;
35
38
  unsubscribeCaps;
36
39
  disposed = false;
@@ -43,6 +46,8 @@ export class InteractionRuntime {
43
46
  decayFactor: d?.decayFactor ?? DWELL_DEFAULTS.decayFactor,
44
47
  rearmBelow: d?.rearmBelow ?? DWELL_DEFAULTS.rearmBelow,
45
48
  };
49
+ this.velocityTracker =
50
+ options.velocity === false ? null : new VelocityTracker(options.velocity ?? {});
46
51
  this.capabilities = this.provider.getCapabilities();
47
52
  this.unsubscribeCaps = this.provider.onCapabilitiesChanged((caps) => {
48
53
  this.capabilities = caps;
@@ -136,6 +141,20 @@ export class InteractionRuntime {
136
141
  onSourcesSampled(listener) {
137
142
  return this.afterSample.subscribe(listener);
138
143
  }
144
+ /**
145
+ * This frame's sources, velocity included. The same stream as
146
+ * {@link onSourcesSampled} with the velocity fields declared.
147
+ */
148
+ onSample(listener) {
149
+ return this.afterSample.subscribe(listener);
150
+ }
151
+ /**
152
+ * The last sample of one source, velocity included. Undefined before the
153
+ * first update, and once the source stops reporting.
154
+ */
155
+ getSource(id) {
156
+ return this.lastSources.get(id);
157
+ }
139
158
  getCapabilities() {
140
159
  return this.capabilities;
141
160
  }
@@ -146,8 +165,10 @@ export class InteractionRuntime {
146
165
  update(dt) {
147
166
  if (this.disposed)
148
167
  return;
149
- const sources = this.provider.sample();
168
+ const sampled = this.provider.sample();
169
+ const sources = this.velocityTracker ? this.velocityTracker.update(sampled, dt) : sampled;
150
170
  const hints = this.provider.sampleHints?.() ?? [];
171
+ this.lastSources = new Map(sources.map((source) => [source.id, source]));
151
172
  this.afterSample.emit(sources);
152
173
  const hintBySource = new Map();
153
174
  for (const hint of hints) {
@@ -177,6 +198,8 @@ export class InteractionRuntime {
177
198
  this.unsubscribeCaps();
178
199
  this.interactables.clear();
179
200
  this.sourceStates.clear();
201
+ this.lastSources.clear();
202
+ this.velocityTracker?.reset();
180
203
  }
181
204
  // -- internals ---------------------------------------------------------------
182
205
  sourceState(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * InteractionRuntime - the engine-free orchestrator.\n *\n * Once per host frame the runtime pulls the input provider, resolves\n * targeting (provider hints first - provider power wins - then poke\n * proximity, then ray hits), applies press/grab transitions with\n * hysteresis, runs gaze gating + dwell, ticks every behaviour, and emits\n * interaction events and feedback intents. It never touches an engine:\n * space queries and transform writes go through the adapter-implemented\n * {@link HitTester} and {@link TransformPort}.\n *\n * Lifecycle is first-class (the pale-signal gap): interactables register,\n * unregister, enable and disable; behaviours disabled by capability\n * negotiation announce themselves with a `behaviourDisabled` event.\n */\nimport {\n SELECT_PRESS_THRESHOLD,\n SELECT_RELEASE_THRESHOLD,\n unmetRequirements,\n type InputCapabilities,\n type InputHitHint,\n type InputProvider,\n type InputSourceSnapshot,\n type PoseTuple,\n type RayTuple,\n type Unsubscribe,\n} from \"@realitycollective/webxr-input\";\nimport type { Behaviour, InteractorInfo } from \"./behaviours/behaviour.js\";\nimport {\n createBehaviour,\n type BehaviourConfig,\n type BehaviourFactoryContext,\n} from \"./behaviours/factory.js\";\nimport { GrabBehaviour } from \"./behaviours/grab.js\";\nimport type { InteractableDescriptor } from \"./descriptor.js\";\nimport { Emitter, type InteractionEvent, type InteractionEventListener } from \"./events.js\";\nimport type { FeedbackIntent, FeedbackListener } from \"./feedback.js\";\nimport { DwellState, resolveDwellConfig, DWELL_DEFAULTS, type DwellConfig } from \"./gaze.js\";\nimport { vApplyQuat } from \"./math.js\";\nimport type { HitTester, TransformPort } from \"./ports.js\";\n\nconst DEFAULT_POKE_RADIUS = 0.05;\n/** Interactor id used when gaze is synthesized from the head pose. */\nexport const HEAD_GAZE_INTERACTOR_ID = \"head-gaze\";\n\nexport interface InteractableState {\n id: string;\n enabled: boolean;\n hovered: boolean;\n gazeHovered: boolean;\n pressed: boolean;\n grabbed: boolean;\n /** Primary behaviour's logical value. */\n value: number;\n /** Gaze-dwell meter 0..1 (0 when dwell is not enabled). */\n dwell: number;\n}\n\ninterface BehaviourSlot {\n instance: Behaviour;\n config: BehaviourConfig;\n disabled: boolean;\n disabledReason: string;\n}\n\ninterface Registered {\n def: InteractableDescriptor;\n behaviours: BehaviourSlot[];\n transform: TransformPort | undefined;\n enabled: boolean;\n pokeRadius: number;\n gazeRequired: boolean;\n dwellConfig: Required<DwellConfig> | null;\n dwell: DwellState;\n gazeHovered: boolean;\n hoveredBy: Set<string>;\n pressedBy: Set<string>;\n grabbedBy: string | null;\n}\n\ninterface SourceRuntimeState {\n hoverTarget: string | null;\n pressed: boolean;\n pressTarget: string | null;\n grabbed: boolean;\n grabTarget: string | null;\n}\n\nexport interface InteractionRuntimeOptions {\n provider: InputProvider;\n hitTester?: HitTester;\n dwellDefaults?: DwellConfig;\n}\n\nexport interface RegisterPorts {\n transform?: TransformPort;\n}\n\nexport class InteractionRuntime {\n private readonly provider: InputProvider;\n private hitTester: HitTester | null;\n private readonly dwellDefaults: Required<DwellConfig>;\n\n private readonly interactables = new Map<string, Registered>();\n private readonly sourceStates = new Map<string, SourceRuntimeState>();\n private readonly events = new Emitter<InteractionEvent>();\n private readonly feedbackEmitter = new Emitter<FeedbackIntent>();\n private readonly afterSample = new Emitter<readonly InputSourceSnapshot[]>();\n private capabilities: InputCapabilities;\n private readonly unsubscribeCaps: Unsubscribe;\n private disposed = false;\n\n constructor(options: InteractionRuntimeOptions) {\n this.provider = options.provider;\n this.hitTester = options.hitTester ?? null;\n const d = options.dwellDefaults;\n this.dwellDefaults = {\n holdSeconds: d?.holdSeconds ?? DWELL_DEFAULTS.holdSeconds,\n decayFactor: d?.decayFactor ?? DWELL_DEFAULTS.decayFactor,\n rearmBelow: d?.rearmBelow ?? DWELL_DEFAULTS.rearmBelow,\n };\n this.capabilities = this.provider.getCapabilities();\n this.unsubscribeCaps = this.provider.onCapabilitiesChanged((caps) => {\n this.capabilities = caps;\n this.renegotiateAll();\n });\n }\n\n // -- registry -------------------------------------------------------------\n\n registerInteractable(def: InteractableDescriptor, ports: RegisterPorts = {}): void {\n if (this.interactables.has(def.id)) {\n throw new Error(`Interactable \"${def.id}\" is already registered`);\n }\n const context: BehaviourFactoryContext = {\n grabFulfilment: this.capabilities.grabs === \"native\" ? \"native\" : \"poseOnly\",\n };\n const registered: Registered = {\n def,\n behaviours: def.behaviours.map((config) => ({\n instance: createBehaviour(config, context),\n config,\n disabled: false,\n disabledReason: \"\",\n })),\n transform: ports.transform,\n enabled: def.enabled !== false,\n pokeRadius: def.pokeRadius ?? DEFAULT_POKE_RADIUS,\n gazeRequired: def.gaze?.required === true,\n dwellConfig: resolveDwellConfig(def.gaze?.dwell, this.dwellDefaults),\n dwell: new DwellState(),\n gazeHovered: false,\n hoveredBy: new Set(),\n pressedBy: new Set(),\n grabbedBy: null,\n };\n this.interactables.set(def.id, registered);\n this.renegotiate(def.id, registered);\n }\n\n unregisterInteractable(id: string): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n this.releaseAllHolds(id);\n this.interactables.delete(id);\n }\n\n setInteractableEnabled(id: string, enabled: boolean): void {\n const registered = this.interactables.get(id);\n if (!registered || registered.enabled === enabled) return;\n registered.enabled = enabled;\n if (!enabled) this.releaseAllHolds(id);\n this.events.emit({\n type: enabled ? \"interactableEnabled\" : \"interactableDisabled\",\n interactableId: id,\n });\n }\n\n setHitTester(hitTester: HitTester | null): void {\n this.hitTester = hitTester;\n }\n\n /** Reach a live behaviour instance for tuning (playground steppers). */\n getBehaviour<T extends Behaviour = Behaviour>(interactableId: string, kind: string): T | undefined {\n const registered = this.interactables.get(interactableId);\n return registered?.behaviours.find((b) => b.instance.kind === kind)?.instance as T | undefined;\n }\n\n getState(id: string): InteractableState | undefined {\n const r = this.interactables.get(id);\n if (!r) return undefined;\n const primary = r.behaviours.find((b) => !b.disabled)?.instance;\n return {\n id,\n enabled: r.enabled,\n hovered: r.hoveredBy.size > 0 || r.gazeHovered,\n gazeHovered: r.gazeHovered,\n pressed: r.pressedBy.size > 0,\n grabbed: r.grabbedBy !== null,\n value: primary?.getValue() ?? 0,\n dwell: r.dwell.getProgress(),\n };\n }\n\n getInteractableIds(): string[] {\n return [...this.interactables.keys()];\n }\n\n // -- subscriptions ----------------------------------------------------------\n\n onEvent(listener: InteractionEventListener): Unsubscribe {\n return this.events.subscribe(listener);\n }\n\n onFeedback(listener: FeedbackListener): Unsubscribe {\n return this.feedbackEmitter.subscribe(listener);\n }\n\n /** Sampled sources for this frame - the UI Extensions pointer bridge hook. */\n onSourcesSampled(listener: (sources: readonly InputSourceSnapshot[]) => void): Unsubscribe {\n return this.afterSample.subscribe(listener);\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n getProvider(): InputProvider {\n return this.provider;\n }\n\n // -- frame ------------------------------------------------------------------\n\n update(dt: number): void {\n if (this.disposed) return;\n const sources = this.provider.sample();\n const hints = this.provider.sampleHints?.() ?? [];\n this.afterSample.emit(sources);\n\n const hintBySource = new Map<string, InputHitHint[]>();\n for (const hint of hints) {\n const list = hintBySource.get(hint.sourceId);\n if (list) list.push(hint);\n else hintBySource.set(hint.sourceId, [hint]);\n }\n\n const seen = new Set<string>();\n for (const source of sources) {\n seen.add(source.id);\n this.updateSource(source, hintBySource.get(source.id) ?? []);\n }\n // Sources that vanished mid-hold release everything they held.\n for (const [id, state] of this.sourceStates) {\n if (!seen.has(id)) {\n this.forceRelease(id, state);\n this.sourceStates.delete(id);\n }\n }\n\n this.updateGaze(sources, dt);\n this.tickBehaviours(sources, dt);\n }\n\n dispose(): void {\n this.disposed = true;\n this.unsubscribeCaps();\n this.interactables.clear();\n this.sourceStates.clear();\n }\n\n // -- internals ---------------------------------------------------------------\n\n private sourceState(id: string): SourceRuntimeState {\n let state = this.sourceStates.get(id);\n if (!state) {\n state = { hoverTarget: null, pressed: false, pressTarget: null, grabbed: false, grabTarget: null };\n this.sourceStates.set(id, state);\n }\n return state;\n }\n\n private interactorInfo(source: InputSourceSnapshot): InteractorInfo {\n const info: InteractorInfo = {\n id: source.id,\n kind: source.kind,\n select: source.select,\n squeeze: source.squeeze,\n };\n if (source.ray) info.ray = source.ray;\n if (source.gripPose) info.gripPose = source.gripPose;\n if (source.indexTip) info.indexTip = source.indexTip;\n return info;\n }\n\n /** Resolve this source's target: hints beat poke beats ray. */\n private resolveTarget(source: InputSourceSnapshot, hints: InputHitHint[]): string | null {\n const hint =\n hints.find((h) => h.state === \"grab\") ??\n hints.find((h) => h.state === \"press\") ??\n hints.find((h) => h.state === \"hover\");\n if (hint && this.targetable(hint.targetId)) return hint.targetId;\n if (source.indexTip && this.hitTester) {\n const poke = this.hitTester.hitProximity(source.indexTip, DEFAULT_POKE_RADIUS);\n if (poke && this.targetable(poke.interactableId)) return poke.interactableId;\n }\n if (source.ray && this.hitTester) {\n const hit = this.hitTester.hitRay(source.ray);\n if (hit && this.targetable(hit.interactableId)) return hit.interactableId;\n }\n return null;\n }\n\n private targetable(id: string): boolean {\n const registered = this.interactables.get(id);\n return registered !== undefined && registered.enabled;\n }\n\n private flags(registered: Registered): { pressable: boolean; grabbable: boolean } {\n let pressable = false;\n let grabbable = false;\n for (const slot of registered.behaviours) {\n if (slot.disabled) continue;\n if (slot.instance.onPressStart) pressable = true;\n if (slot.instance.grabbable) grabbable = true;\n }\n return { pressable, grabbable };\n }\n\n private updateSource(source: InputSourceSnapshot, hints: InputHitHint[]): void {\n const state = this.sourceState(source.id);\n const target = this.resolveTarget(source, hints);\n\n // Hover transitions.\n if (state.hoverTarget !== target) {\n if (state.hoverTarget) this.setHover(state.hoverTarget, source.id, false);\n if (target) this.setHover(target, source.id, true);\n state.hoverTarget = target;\n }\n\n const registered = target ? this.interactables.get(target) : undefined;\n const roles = registered ? this.flags(registered) : { pressable: false, grabbable: false };\n\n // Signal resolution. A pinching hand (or a 2D pointer click) acts as a\n // grab when the target is grab-only; controllers keep trigger=press,\n // squeeze=grab.\n const selectAsGrab = roles.grabbable && !roles.pressable && source.kind !== \"controller\";\n const pressSignal = selectAsGrab ? 0 : source.select;\n const grabSignal = Math.max(source.squeeze, selectAsGrab ? source.select : 0);\n\n const hintPress = hints.some((h) => h.state === \"press\");\n const hintGrab = hints.some((h) => h.state === \"grab\") || source.nativeGrabbing === true;\n\n // Press with hysteresis (hints override).\n const pressActive = hintPress || (state.pressed\n ? pressSignal > SELECT_RELEASE_THRESHOLD\n : pressSignal >= SELECT_PRESS_THRESHOLD);\n if (pressActive && !state.pressed) {\n state.pressed = true;\n if (target && registered && roles.pressable && this.gazeAllows(registered)) {\n state.pressTarget = target;\n this.routePressStart(target, registered, this.interactorInfo(source));\n }\n } else if (!pressActive && state.pressed) {\n state.pressed = false;\n if (state.pressTarget) {\n this.routePressEnd(state.pressTarget, source.id, this.interactorInfo(source));\n state.pressTarget = null;\n }\n }\n\n // Grab with hysteresis (hints / native grabbing override).\n const grabActive = hintGrab || (state.grabbed\n ? grabSignal > SELECT_RELEASE_THRESHOLD\n : grabSignal >= SELECT_PRESS_THRESHOLD);\n if (grabActive && !state.grabbed) {\n state.grabbed = true;\n const hintTargetId = hints.find((h) => h.state === \"grab\")?.targetId;\n const grabTargetId = hintTargetId && this.targetable(hintTargetId) ? hintTargetId : target;\n const grabTarget = grabTargetId ? this.interactables.get(grabTargetId) : undefined;\n if (\n grabTargetId &&\n grabTarget &&\n this.flags(grabTarget).grabbable &&\n grabTarget.grabbedBy === null &&\n this.gazeAllows(grabTarget)\n ) {\n state.grabTarget = grabTargetId;\n grabTarget.grabbedBy = source.id;\n const info = this.interactorInfo(source);\n for (const slot of grabTarget.behaviours) {\n if (!slot.disabled && slot.instance.grabbable) {\n slot.instance.onGrabStart?.(this.contextFor(grabTargetId, grabTarget, 0), info);\n }\n }\n }\n } else if (!grabActive && state.grabbed) {\n state.grabbed = false;\n if (state.grabTarget) {\n this.endGrab(state.grabTarget, source.id, this.interactorInfo(source));\n state.grabTarget = null;\n }\n }\n }\n\n private gazeAllows(registered: Registered): boolean {\n return !registered.gazeRequired || registered.gazeHovered;\n }\n\n private setHover(id: string, sourceId: string, hovered: boolean): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n if (hovered) {\n registered.hoveredBy.add(sourceId);\n if (registered.hoveredBy.size === 1) {\n this.events.emit({ type: \"hoverEnter\", interactableId: id, interactorId: sourceId });\n this.feedbackEmitter.emit({\n cue: \"hover\",\n interactableId: id,\n sourceId,\n intensity: 0.1,\n durationMs: 10,\n });\n }\n } else {\n registered.hoveredBy.delete(sourceId);\n if (registered.hoveredBy.size === 0) {\n this.events.emit({ type: \"hoverExit\", interactableId: id, interactorId: sourceId });\n }\n }\n }\n\n private routePressStart(id: string, registered: Registered, info: InteractorInfo): void {\n registered.pressedBy.add(info.id);\n this.events.emit({ type: \"pressStart\", interactableId: id, interactorId: info.id });\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled) slot.instance.onPressStart?.(ctx, info);\n }\n }\n\n private routePressEnd(id: string, sourceId: string, info: InteractorInfo): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n registered.pressedBy.delete(sourceId);\n this.events.emit({ type: \"pressEnd\", interactableId: id, interactorId: sourceId });\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled) slot.instance.onPressEnd?.(ctx, info);\n }\n }\n\n private endGrab(id: string, sourceId: string, info: InteractorInfo): void {\n const registered = this.interactables.get(id);\n if (!registered || registered.grabbedBy !== sourceId) return;\n registered.grabbedBy = null;\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled && slot.instance.grabbable) slot.instance.onGrabEnd?.(ctx, info);\n }\n }\n\n private forceRelease(sourceId: string, state: SourceRuntimeState): void {\n const info: InteractorInfo = { id: sourceId, kind: \"other\", select: 0, squeeze: 0 };\n if (state.pressTarget) this.routePressEnd(state.pressTarget, sourceId, info);\n if (state.grabTarget) this.endGrab(state.grabTarget, sourceId, info);\n if (state.hoverTarget) this.setHover(state.hoverTarget, sourceId, false);\n }\n\n private releaseAllHolds(id: string): void {\n for (const [sourceId, state] of this.sourceStates) {\n if (state.pressTarget === id) {\n this.routePressEnd(id, sourceId, { id: sourceId, kind: \"other\", select: 0, squeeze: 0 });\n state.pressTarget = null;\n state.pressed = false;\n }\n if (state.grabTarget === id) {\n this.endGrab(id, sourceId, { id: sourceId, kind: \"other\", select: 0, squeeze: 0 });\n state.grabTarget = null;\n state.grabbed = false;\n }\n if (state.hoverTarget === id) {\n this.setHover(id, sourceId, false);\n state.hoverTarget = null;\n }\n }\n }\n\n private updateGaze(sources: readonly InputSourceSnapshot[], dt: number): void {\n let gazeRay: RayTuple | null = null;\n let gazeId = HEAD_GAZE_INTERACTOR_ID;\n const gazeSource = sources.find((s) => s.kind === \"gaze\");\n if (gazeSource?.ray) {\n gazeRay = gazeSource.ray;\n gazeId = gazeSource.id;\n } else if (this.capabilities.headPose && this.provider.getHeadPose) {\n const head: PoseTuple = this.provider.getHeadPose();\n gazeRay = {\n origin: head.position,\n direction: vApplyQuat([0, 0, -1], head.quaternion),\n };\n }\n\n const gazeTarget =\n gazeRay && this.hitTester ? this.hitTester.hitRay(gazeRay)?.interactableId ?? null : null;\n\n for (const [id, registered] of this.interactables) {\n const hovered = registered.enabled && gazeTarget === id;\n if (hovered !== registered.gazeHovered) {\n registered.gazeHovered = hovered;\n this.events.emit({\n type: hovered ? \"hoverEnter\" : \"hoverExit\",\n interactableId: id,\n interactorId: gazeId,\n });\n }\n if (registered.dwellConfig && registered.enabled) {\n const tick = registered.dwell.update(hovered, dt, registered.dwellConfig);\n if (tick.changed) {\n this.events.emit({\n type: \"dwellProgress\",\n interactableId: id,\n interactorId: gazeId,\n value: tick.progress,\n });\n }\n if (tick.fired) {\n // A completed dwell is a synthesized click: press + release.\n const info: InteractorInfo = { id: gazeId, kind: \"gaze\", select: 1, squeeze: 0 };\n this.routePressStart(id, registered, info);\n this.routePressEnd(id, gazeId, { ...info, select: 0 });\n this.feedbackEmitter.emit({\n cue: \"dwellComplete\",\n interactableId: id,\n intensity: 0.6,\n durationMs: 50,\n });\n }\n }\n }\n }\n\n private tickBehaviours(sources: readonly InputSourceSnapshot[], dt: number): void {\n const bySourceId = new Map(sources.map((s) => [s.id, s]));\n for (const [id, registered] of this.interactables) {\n if (!registered.enabled) continue;\n const holderSource = registered.grabbedBy ? bySourceId.get(registered.grabbedBy) : undefined;\n const holder = holderSource ? this.interactorInfo(holderSource) : undefined;\n const ctx = this.contextFor(id, registered, dt);\n for (const slot of registered.behaviours) {\n if (slot.disabled) continue;\n slot.instance.update(ctx, slot.instance.grabbable ? holder : undefined);\n }\n }\n }\n\n private contextFor(id: string, registered: Registered, dt: number) {\n return {\n dt,\n interactableId: id,\n transform: registered.transform,\n emit: (event: Omit<InteractionEvent, \"interactableId\">) =>\n this.events.emit({ ...event, interactableId: id }),\n feedback: (intent: Omit<FeedbackIntent, \"interactableId\">) =>\n this.feedbackEmitter.emit({ ...intent, interactableId: id }),\n getWorldPose: (otherId: string) =>\n this.interactables.get(otherId)?.transform?.getWorldPose(),\n };\n }\n\n private renegotiate(id: string, registered: Registered): void {\n const fulfilment = this.capabilities.grabs === \"native\" ? \"native\" : \"poseOnly\";\n for (const slot of registered.behaviours) {\n if (slot.instance instanceof GrabBehaviour) {\n slot.instance.setFulfilment(fulfilment);\n }\n const unmet = [...unmetRequirements(this.capabilities, slot.instance.requires)];\n for (const group of slot.instance.requiresAnyOf ?? []) {\n if (group.length > 0 && unmetRequirements(this.capabilities, group).length === group.length) {\n unmet.push(group[0]!);\n }\n }\n const disabled = unmet.length > 0;\n if (disabled !== slot.disabled) {\n slot.disabled = disabled;\n slot.disabledReason = disabled ? `unmet input capabilities: ${unmet.join(\", \")}` : \"\";\n this.events.emit({\n type: disabled ? \"behaviourDisabled\" : \"behaviourEnabled\",\n interactableId: id,\n behaviourKind: slot.instance.kind,\n ...(disabled ? { reason: slot.disabledReason } : {}),\n });\n }\n }\n }\n\n private renegotiateAll(): void {\n for (const [id, registered] of this.interactables) {\n this.renegotiate(id, registered);\n }\n }\n}\n\n/** Build a runtime's interactables from a portable descriptor. */\nexport function registerDescriptor(\n runtime: InteractionRuntime,\n descriptor: { interactables: InteractableDescriptor[] },\n portsFor: (id: string) => RegisterPorts = () => ({}),\n): void {\n for (const interactable of descriptor.interactables) {\n runtime.registerInteractable(interactable, portsFor(interactable.id));\n }\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * InteractionRuntime - the engine-free orchestrator.\n *\n * Once per host frame the runtime pulls the input provider, resolves\n * targeting (provider hints first - provider power wins - then poke\n * proximity, then ray hits), applies press/grab transitions with\n * hysteresis, runs gaze gating + dwell, ticks every behaviour, and emits\n * interaction events and feedback intents. It never touches an engine:\n * space queries and transform writes go through the adapter-implemented\n * {@link HitTester} and {@link TransformPort}.\n *\n * Lifecycle is first-class (the pale-signal gap): interactables register,\n * unregister, enable and disable; behaviours disabled by capability\n * negotiation announce themselves with a `behaviourDisabled` event.\n */\nimport {\n SELECT_PRESS_THRESHOLD,\n SELECT_RELEASE_THRESHOLD,\n unmetRequirements,\n type InputCapabilities,\n type InputHitHint,\n type InputProvider,\n type InputSourceSnapshot,\n type PoseTuple,\n type RayTuple,\n type Unsubscribe,\n} from \"@realitycollective/webxr-input\";\nimport type { Behaviour, InteractorInfo } from \"./behaviours/behaviour.js\";\nimport {\n createBehaviour,\n type BehaviourConfig,\n type BehaviourFactoryContext,\n} from \"./behaviours/factory.js\";\nimport { GrabBehaviour } from \"./behaviours/grab.js\";\nimport type { InteractableDescriptor } from \"./descriptor.js\";\nimport { Emitter, type InteractionEvent, type InteractionEventListener } from \"./events.js\";\nimport type { FeedbackIntent, FeedbackListener } from \"./feedback.js\";\nimport { DwellState, resolveDwellConfig, DWELL_DEFAULTS, type DwellConfig } from \"./gaze.js\";\nimport { vApplyQuat } from \"./math.js\";\nimport type { HitTester, TransformPort } from \"./ports.js\";\nimport {\n VelocityTracker,\n type InputSourceSnapshotWithVelocity,\n type VelocityTrackerOptions,\n} from \"./velocity-tracker.js\";\n\nconst DEFAULT_POKE_RADIUS = 0.05;\n/** Interactor id used when gaze is synthesized from the head pose. */\nexport const HEAD_GAZE_INTERACTOR_ID = \"head-gaze\";\n\nexport interface InteractableState {\n id: string;\n enabled: boolean;\n hovered: boolean;\n gazeHovered: boolean;\n pressed: boolean;\n grabbed: boolean;\n /** Primary behaviour's logical value. */\n value: number;\n /** Gaze-dwell meter 0..1 (0 when dwell is not enabled). */\n dwell: number;\n}\n\ninterface BehaviourSlot {\n instance: Behaviour;\n config: BehaviourConfig;\n disabled: boolean;\n disabledReason: string;\n}\n\ninterface Registered {\n def: InteractableDescriptor;\n behaviours: BehaviourSlot[];\n transform: TransformPort | undefined;\n enabled: boolean;\n pokeRadius: number;\n gazeRequired: boolean;\n dwellConfig: Required<DwellConfig> | null;\n dwell: DwellState;\n gazeHovered: boolean;\n hoveredBy: Set<string>;\n pressedBy: Set<string>;\n grabbedBy: string | null;\n}\n\ninterface SourceRuntimeState {\n hoverTarget: string | null;\n pressed: boolean;\n pressTarget: string | null;\n grabbed: boolean;\n grabTarget: string | null;\n}\n\nexport interface InteractionRuntimeOptions {\n provider: InputProvider;\n hitTester?: HitTester;\n dwellDefaults?: DwellConfig;\n /**\n * Per-source velocity tracking. On by default with no smoothing; pass\n * `false` to skip it, or options to smooth. The tracker only fills in\n * what the provider did not supply.\n */\n velocity?: VelocityTrackerOptions | false;\n}\n\nexport interface RegisterPorts {\n transform?: TransformPort;\n}\n\nexport class InteractionRuntime {\n private readonly provider: InputProvider;\n private hitTester: HitTester | null;\n private readonly dwellDefaults: Required<DwellConfig>;\n\n private readonly interactables = new Map<string, Registered>();\n private readonly sourceStates = new Map<string, SourceRuntimeState>();\n private readonly events = new Emitter<InteractionEvent>();\n private readonly feedbackEmitter = new Emitter<FeedbackIntent>();\n private readonly afterSample = new Emitter<readonly InputSourceSnapshotWithVelocity[]>();\n private readonly velocityTracker: VelocityTracker | null;\n private lastSources = new Map<string, InputSourceSnapshotWithVelocity>();\n private capabilities: InputCapabilities;\n private readonly unsubscribeCaps: Unsubscribe;\n private disposed = false;\n\n constructor(options: InteractionRuntimeOptions) {\n this.provider = options.provider;\n this.hitTester = options.hitTester ?? null;\n const d = options.dwellDefaults;\n this.dwellDefaults = {\n holdSeconds: d?.holdSeconds ?? DWELL_DEFAULTS.holdSeconds,\n decayFactor: d?.decayFactor ?? DWELL_DEFAULTS.decayFactor,\n rearmBelow: d?.rearmBelow ?? DWELL_DEFAULTS.rearmBelow,\n };\n this.velocityTracker =\n options.velocity === false ? null : new VelocityTracker(options.velocity ?? {});\n this.capabilities = this.provider.getCapabilities();\n this.unsubscribeCaps = this.provider.onCapabilitiesChanged((caps) => {\n this.capabilities = caps;\n this.renegotiateAll();\n });\n }\n\n // -- registry -------------------------------------------------------------\n\n registerInteractable(def: InteractableDescriptor, ports: RegisterPorts = {}): void {\n if (this.interactables.has(def.id)) {\n throw new Error(`Interactable \"${def.id}\" is already registered`);\n }\n const context: BehaviourFactoryContext = {\n grabFulfilment: this.capabilities.grabs === \"native\" ? \"native\" : \"poseOnly\",\n };\n const registered: Registered = {\n def,\n behaviours: def.behaviours.map((config) => ({\n instance: createBehaviour(config, context),\n config,\n disabled: false,\n disabledReason: \"\",\n })),\n transform: ports.transform,\n enabled: def.enabled !== false,\n pokeRadius: def.pokeRadius ?? DEFAULT_POKE_RADIUS,\n gazeRequired: def.gaze?.required === true,\n dwellConfig: resolveDwellConfig(def.gaze?.dwell, this.dwellDefaults),\n dwell: new DwellState(),\n gazeHovered: false,\n hoveredBy: new Set(),\n pressedBy: new Set(),\n grabbedBy: null,\n };\n this.interactables.set(def.id, registered);\n this.renegotiate(def.id, registered);\n }\n\n unregisterInteractable(id: string): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n this.releaseAllHolds(id);\n this.interactables.delete(id);\n }\n\n setInteractableEnabled(id: string, enabled: boolean): void {\n const registered = this.interactables.get(id);\n if (!registered || registered.enabled === enabled) return;\n registered.enabled = enabled;\n if (!enabled) this.releaseAllHolds(id);\n this.events.emit({\n type: enabled ? \"interactableEnabled\" : \"interactableDisabled\",\n interactableId: id,\n });\n }\n\n setHitTester(hitTester: HitTester | null): void {\n this.hitTester = hitTester;\n }\n\n /** Reach a live behaviour instance for tuning (playground steppers). */\n getBehaviour<T extends Behaviour = Behaviour>(interactableId: string, kind: string): T | undefined {\n const registered = this.interactables.get(interactableId);\n return registered?.behaviours.find((b) => b.instance.kind === kind)?.instance as T | undefined;\n }\n\n getState(id: string): InteractableState | undefined {\n const r = this.interactables.get(id);\n if (!r) return undefined;\n const primary = r.behaviours.find((b) => !b.disabled)?.instance;\n return {\n id,\n enabled: r.enabled,\n hovered: r.hoveredBy.size > 0 || r.gazeHovered,\n gazeHovered: r.gazeHovered,\n pressed: r.pressedBy.size > 0,\n grabbed: r.grabbedBy !== null,\n value: primary?.getValue() ?? 0,\n dwell: r.dwell.getProgress(),\n };\n }\n\n getInteractableIds(): string[] {\n return [...this.interactables.keys()];\n }\n\n // -- subscriptions ----------------------------------------------------------\n\n onEvent(listener: InteractionEventListener): Unsubscribe {\n return this.events.subscribe(listener);\n }\n\n onFeedback(listener: FeedbackListener): Unsubscribe {\n return this.feedbackEmitter.subscribe(listener);\n }\n\n /** Sampled sources for this frame - the UI Extensions pointer bridge hook. */\n onSourcesSampled(listener: (sources: readonly InputSourceSnapshot[]) => void): Unsubscribe {\n return this.afterSample.subscribe(listener);\n }\n\n /**\n * This frame's sources, velocity included. The same stream as\n * {@link onSourcesSampled} with the velocity fields declared.\n */\n onSample(listener: (sources: readonly InputSourceSnapshotWithVelocity[]) => void): Unsubscribe {\n return this.afterSample.subscribe(listener);\n }\n\n /**\n * The last sample of one source, velocity included. Undefined before the\n * first update, and once the source stops reporting.\n */\n getSource(id: string): InputSourceSnapshotWithVelocity | undefined {\n return this.lastSources.get(id);\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n getProvider(): InputProvider {\n return this.provider;\n }\n\n // -- frame ------------------------------------------------------------------\n\n update(dt: number): void {\n if (this.disposed) return;\n const sampled = this.provider.sample();\n const sources = this.velocityTracker ? this.velocityTracker.update(sampled, dt) : sampled;\n const hints = this.provider.sampleHints?.() ?? [];\n this.lastSources = new Map(sources.map((source) => [source.id, source]));\n this.afterSample.emit(sources);\n\n const hintBySource = new Map<string, InputHitHint[]>();\n for (const hint of hints) {\n const list = hintBySource.get(hint.sourceId);\n if (list) list.push(hint);\n else hintBySource.set(hint.sourceId, [hint]);\n }\n\n const seen = new Set<string>();\n for (const source of sources) {\n seen.add(source.id);\n this.updateSource(source, hintBySource.get(source.id) ?? []);\n }\n // Sources that vanished mid-hold release everything they held.\n for (const [id, state] of this.sourceStates) {\n if (!seen.has(id)) {\n this.forceRelease(id, state);\n this.sourceStates.delete(id);\n }\n }\n\n this.updateGaze(sources, dt);\n this.tickBehaviours(sources, dt);\n }\n\n dispose(): void {\n this.disposed = true;\n this.unsubscribeCaps();\n this.interactables.clear();\n this.sourceStates.clear();\n this.lastSources.clear();\n this.velocityTracker?.reset();\n }\n\n // -- internals ---------------------------------------------------------------\n\n private sourceState(id: string): SourceRuntimeState {\n let state = this.sourceStates.get(id);\n if (!state) {\n state = { hoverTarget: null, pressed: false, pressTarget: null, grabbed: false, grabTarget: null };\n this.sourceStates.set(id, state);\n }\n return state;\n }\n\n private interactorInfo(source: InputSourceSnapshot): InteractorInfo {\n const info: InteractorInfo = {\n id: source.id,\n kind: source.kind,\n select: source.select,\n squeeze: source.squeeze,\n };\n if (source.ray) info.ray = source.ray;\n if (source.gripPose) info.gripPose = source.gripPose;\n if (source.indexTip) info.indexTip = source.indexTip;\n return info;\n }\n\n /** Resolve this source's target: hints beat poke beats ray. */\n private resolveTarget(source: InputSourceSnapshot, hints: InputHitHint[]): string | null {\n const hint =\n hints.find((h) => h.state === \"grab\") ??\n hints.find((h) => h.state === \"press\") ??\n hints.find((h) => h.state === \"hover\");\n if (hint && this.targetable(hint.targetId)) return hint.targetId;\n if (source.indexTip && this.hitTester) {\n const poke = this.hitTester.hitProximity(source.indexTip, DEFAULT_POKE_RADIUS);\n if (poke && this.targetable(poke.interactableId)) return poke.interactableId;\n }\n if (source.ray && this.hitTester) {\n const hit = this.hitTester.hitRay(source.ray);\n if (hit && this.targetable(hit.interactableId)) return hit.interactableId;\n }\n return null;\n }\n\n private targetable(id: string): boolean {\n const registered = this.interactables.get(id);\n return registered !== undefined && registered.enabled;\n }\n\n private flags(registered: Registered): { pressable: boolean; grabbable: boolean } {\n let pressable = false;\n let grabbable = false;\n for (const slot of registered.behaviours) {\n if (slot.disabled) continue;\n if (slot.instance.onPressStart) pressable = true;\n if (slot.instance.grabbable) grabbable = true;\n }\n return { pressable, grabbable };\n }\n\n private updateSource(source: InputSourceSnapshot, hints: InputHitHint[]): void {\n const state = this.sourceState(source.id);\n const target = this.resolveTarget(source, hints);\n\n // Hover transitions.\n if (state.hoverTarget !== target) {\n if (state.hoverTarget) this.setHover(state.hoverTarget, source.id, false);\n if (target) this.setHover(target, source.id, true);\n state.hoverTarget = target;\n }\n\n const registered = target ? this.interactables.get(target) : undefined;\n const roles = registered ? this.flags(registered) : { pressable: false, grabbable: false };\n\n // Signal resolution. A pinching hand (or a 2D pointer click) acts as a\n // grab when the target is grab-only; controllers keep trigger=press,\n // squeeze=grab.\n const selectAsGrab = roles.grabbable && !roles.pressable && source.kind !== \"controller\";\n const pressSignal = selectAsGrab ? 0 : source.select;\n const grabSignal = Math.max(source.squeeze, selectAsGrab ? source.select : 0);\n\n const hintPress = hints.some((h) => h.state === \"press\");\n const hintGrab = hints.some((h) => h.state === \"grab\") || source.nativeGrabbing === true;\n\n // Press with hysteresis (hints override).\n const pressActive = hintPress || (state.pressed\n ? pressSignal > SELECT_RELEASE_THRESHOLD\n : pressSignal >= SELECT_PRESS_THRESHOLD);\n if (pressActive && !state.pressed) {\n state.pressed = true;\n if (target && registered && roles.pressable && this.gazeAllows(registered)) {\n state.pressTarget = target;\n this.routePressStart(target, registered, this.interactorInfo(source));\n }\n } else if (!pressActive && state.pressed) {\n state.pressed = false;\n if (state.pressTarget) {\n this.routePressEnd(state.pressTarget, source.id, this.interactorInfo(source));\n state.pressTarget = null;\n }\n }\n\n // Grab with hysteresis (hints / native grabbing override).\n const grabActive = hintGrab || (state.grabbed\n ? grabSignal > SELECT_RELEASE_THRESHOLD\n : grabSignal >= SELECT_PRESS_THRESHOLD);\n if (grabActive && !state.grabbed) {\n state.grabbed = true;\n const hintTargetId = hints.find((h) => h.state === \"grab\")?.targetId;\n const grabTargetId = hintTargetId && this.targetable(hintTargetId) ? hintTargetId : target;\n const grabTarget = grabTargetId ? this.interactables.get(grabTargetId) : undefined;\n if (\n grabTargetId &&\n grabTarget &&\n this.flags(grabTarget).grabbable &&\n grabTarget.grabbedBy === null &&\n this.gazeAllows(grabTarget)\n ) {\n state.grabTarget = grabTargetId;\n grabTarget.grabbedBy = source.id;\n const info = this.interactorInfo(source);\n for (const slot of grabTarget.behaviours) {\n if (!slot.disabled && slot.instance.grabbable) {\n slot.instance.onGrabStart?.(this.contextFor(grabTargetId, grabTarget, 0), info);\n }\n }\n }\n } else if (!grabActive && state.grabbed) {\n state.grabbed = false;\n if (state.grabTarget) {\n this.endGrab(state.grabTarget, source.id, this.interactorInfo(source));\n state.grabTarget = null;\n }\n }\n }\n\n private gazeAllows(registered: Registered): boolean {\n return !registered.gazeRequired || registered.gazeHovered;\n }\n\n private setHover(id: string, sourceId: string, hovered: boolean): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n if (hovered) {\n registered.hoveredBy.add(sourceId);\n if (registered.hoveredBy.size === 1) {\n this.events.emit({ type: \"hoverEnter\", interactableId: id, interactorId: sourceId });\n this.feedbackEmitter.emit({\n cue: \"hover\",\n interactableId: id,\n sourceId,\n intensity: 0.1,\n durationMs: 10,\n });\n }\n } else {\n registered.hoveredBy.delete(sourceId);\n if (registered.hoveredBy.size === 0) {\n this.events.emit({ type: \"hoverExit\", interactableId: id, interactorId: sourceId });\n }\n }\n }\n\n private routePressStart(id: string, registered: Registered, info: InteractorInfo): void {\n registered.pressedBy.add(info.id);\n this.events.emit({ type: \"pressStart\", interactableId: id, interactorId: info.id });\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled) slot.instance.onPressStart?.(ctx, info);\n }\n }\n\n private routePressEnd(id: string, sourceId: string, info: InteractorInfo): void {\n const registered = this.interactables.get(id);\n if (!registered) return;\n registered.pressedBy.delete(sourceId);\n this.events.emit({ type: \"pressEnd\", interactableId: id, interactorId: sourceId });\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled) slot.instance.onPressEnd?.(ctx, info);\n }\n }\n\n private endGrab(id: string, sourceId: string, info: InteractorInfo): void {\n const registered = this.interactables.get(id);\n if (!registered || registered.grabbedBy !== sourceId) return;\n registered.grabbedBy = null;\n const ctx = this.contextFor(id, registered, 0);\n for (const slot of registered.behaviours) {\n if (!slot.disabled && slot.instance.grabbable) slot.instance.onGrabEnd?.(ctx, info);\n }\n }\n\n private forceRelease(sourceId: string, state: SourceRuntimeState): void {\n const info: InteractorInfo = { id: sourceId, kind: \"other\", select: 0, squeeze: 0 };\n if (state.pressTarget) this.routePressEnd(state.pressTarget, sourceId, info);\n if (state.grabTarget) this.endGrab(state.grabTarget, sourceId, info);\n if (state.hoverTarget) this.setHover(state.hoverTarget, sourceId, false);\n }\n\n private releaseAllHolds(id: string): void {\n for (const [sourceId, state] of this.sourceStates) {\n if (state.pressTarget === id) {\n this.routePressEnd(id, sourceId, { id: sourceId, kind: \"other\", select: 0, squeeze: 0 });\n state.pressTarget = null;\n state.pressed = false;\n }\n if (state.grabTarget === id) {\n this.endGrab(id, sourceId, { id: sourceId, kind: \"other\", select: 0, squeeze: 0 });\n state.grabTarget = null;\n state.grabbed = false;\n }\n if (state.hoverTarget === id) {\n this.setHover(id, sourceId, false);\n state.hoverTarget = null;\n }\n }\n }\n\n private updateGaze(sources: readonly InputSourceSnapshot[], dt: number): void {\n let gazeRay: RayTuple | null = null;\n let gazeId = HEAD_GAZE_INTERACTOR_ID;\n const gazeSource = sources.find((s) => s.kind === \"gaze\");\n if (gazeSource?.ray) {\n gazeRay = gazeSource.ray;\n gazeId = gazeSource.id;\n } else if (this.capabilities.headPose && this.provider.getHeadPose) {\n const head: PoseTuple = this.provider.getHeadPose();\n gazeRay = {\n origin: head.position,\n direction: vApplyQuat([0, 0, -1], head.quaternion),\n };\n }\n\n const gazeTarget =\n gazeRay && this.hitTester ? this.hitTester.hitRay(gazeRay)?.interactableId ?? null : null;\n\n for (const [id, registered] of this.interactables) {\n const hovered = registered.enabled && gazeTarget === id;\n if (hovered !== registered.gazeHovered) {\n registered.gazeHovered = hovered;\n this.events.emit({\n type: hovered ? \"hoverEnter\" : \"hoverExit\",\n interactableId: id,\n interactorId: gazeId,\n });\n }\n if (registered.dwellConfig && registered.enabled) {\n const tick = registered.dwell.update(hovered, dt, registered.dwellConfig);\n if (tick.changed) {\n this.events.emit({\n type: \"dwellProgress\",\n interactableId: id,\n interactorId: gazeId,\n value: tick.progress,\n });\n }\n if (tick.fired) {\n // A completed dwell is a synthesized click: press + release.\n const info: InteractorInfo = { id: gazeId, kind: \"gaze\", select: 1, squeeze: 0 };\n this.routePressStart(id, registered, info);\n this.routePressEnd(id, gazeId, { ...info, select: 0 });\n this.feedbackEmitter.emit({\n cue: \"dwellComplete\",\n interactableId: id,\n intensity: 0.6,\n durationMs: 50,\n });\n }\n }\n }\n }\n\n private tickBehaviours(sources: readonly InputSourceSnapshot[], dt: number): void {\n const bySourceId = new Map(sources.map((s) => [s.id, s]));\n for (const [id, registered] of this.interactables) {\n if (!registered.enabled) continue;\n const holderSource = registered.grabbedBy ? bySourceId.get(registered.grabbedBy) : undefined;\n const holder = holderSource ? this.interactorInfo(holderSource) : undefined;\n const ctx = this.contextFor(id, registered, dt);\n for (const slot of registered.behaviours) {\n if (slot.disabled) continue;\n slot.instance.update(ctx, slot.instance.grabbable ? holder : undefined);\n }\n }\n }\n\n private contextFor(id: string, registered: Registered, dt: number) {\n return {\n dt,\n interactableId: id,\n transform: registered.transform,\n emit: (event: Omit<InteractionEvent, \"interactableId\">) =>\n this.events.emit({ ...event, interactableId: id }),\n feedback: (intent: Omit<FeedbackIntent, \"interactableId\">) =>\n this.feedbackEmitter.emit({ ...intent, interactableId: id }),\n getWorldPose: (otherId: string) =>\n this.interactables.get(otherId)?.transform?.getWorldPose(),\n };\n }\n\n private renegotiate(id: string, registered: Registered): void {\n const fulfilment = this.capabilities.grabs === \"native\" ? \"native\" : \"poseOnly\";\n for (const slot of registered.behaviours) {\n if (slot.instance instanceof GrabBehaviour) {\n slot.instance.setFulfilment(fulfilment);\n }\n const unmet = [...unmetRequirements(this.capabilities, slot.instance.requires)];\n for (const group of slot.instance.requiresAnyOf ?? []) {\n if (group.length > 0 && unmetRequirements(this.capabilities, group).length === group.length) {\n unmet.push(group[0]!);\n }\n }\n const disabled = unmet.length > 0;\n if (disabled !== slot.disabled) {\n slot.disabled = disabled;\n slot.disabledReason = disabled ? `unmet input capabilities: ${unmet.join(\", \")}` : \"\";\n this.events.emit({\n type: disabled ? \"behaviourDisabled\" : \"behaviourEnabled\",\n interactableId: id,\n behaviourKind: slot.instance.kind,\n ...(disabled ? { reason: slot.disabledReason } : {}),\n });\n }\n }\n }\n\n private renegotiateAll(): void {\n for (const [id, registered] of this.interactables) {\n this.renegotiate(id, registered);\n }\n }\n}\n\n/** Build a runtime's interactables from a portable descriptor. */\nexport function registerDescriptor(\n runtime: InteractionRuntime,\n descriptor: { interactables: InteractableDescriptor[] },\n portsFor: (id: string) => RegisterPorts = () => ({}),\n): void {\n for (const interactable of descriptor.interactables) {\n runtime.registerInteractable(interactable, portsFor(interactable.id));\n }\n}\n"]}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * VelocityTracker - per-source linear and angular velocity, derived from
3
+ * consecutive grip poses.
4
+ *
5
+ * Throw, flick and swipe all need to know how fast a hand was moving at
6
+ * the moment it let go. WebXR does not report that, and most engines do
7
+ * not either, so the core differentiates the poses it already samples.
8
+ * A provider that CAN report velocity keeps its own numbers - the tracker
9
+ * only fills gaps.
10
+ *
11
+ * The tracker is deliberately conservative. A source seen for the first
12
+ * time (or seen again after dropping out of tracking) gets no velocity
13
+ * that frame, so a reconnecting controller cannot report a metres-per-
14
+ * second jump from wherever it was last seen.
15
+ */
16
+ import type { InputSourceSnapshot, Vec3Tuple } from "@realitycollective/webxr-input";
17
+ /**
18
+ * A sampled source carrying the velocity the tracker measured.
19
+ * Collapses into `InputSourceSnapshot` in
20
+ * `@realitycollective/webxr-input` 0.1.1, which adds these two fields.
21
+ */
22
+ export type InputSourceSnapshotWithVelocity = InputSourceSnapshot & {
23
+ /** Metres per second, world space. */
24
+ linearVelocity?: Vec3Tuple;
25
+ /** Radians per second about each world axis. */
26
+ angularVelocity?: Vec3Tuple;
27
+ };
28
+ export interface VelocityTrackerOptions {
29
+ /**
30
+ * Exponential moving average factor in `(0, 1]`. 1 (the default) is no
31
+ * smoothing - every frame reports the raw difference. Lower values trade
32
+ * responsiveness for steadiness; 0.3 is a reasonable starting point when
33
+ * the pose stream is noisy.
34
+ */
35
+ smoothing?: number;
36
+ }
37
+ export declare class VelocityTracker {
38
+ private readonly smoothing;
39
+ private readonly tracked;
40
+ constructor(options?: VelocityTrackerOptions);
41
+ /**
42
+ * Measure this frame's sources. Returns the same sources, with velocity
43
+ * added to those the provider left without it. Sources with no grip pose
44
+ * pass through untouched and hold no state.
45
+ */
46
+ update(sources: readonly InputSourceSnapshot[], dt: number): readonly InputSourceSnapshotWithVelocity[];
47
+ /** Forget every tracked source (session end, teleport, scene change). */
48
+ reset(): void;
49
+ }
@@ -0,0 +1,80 @@
1
+ import { poseVelocity } from "./math.js";
2
+ const DEFAULT_SMOOTHING = 1;
3
+ export class VelocityTracker {
4
+ smoothing;
5
+ tracked = new Map();
6
+ constructor(options = {}) {
7
+ const smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
8
+ this.smoothing = smoothing > 0 && smoothing <= 1 ? smoothing : DEFAULT_SMOOTHING;
9
+ }
10
+ /**
11
+ * Measure this frame's sources. Returns the same sources, with velocity
12
+ * added to those the provider left without it. Sources with no grip pose
13
+ * pass through untouched and hold no state.
14
+ */
15
+ update(sources, dt) {
16
+ const seen = new Set();
17
+ const out = [];
18
+ for (const source of sources) {
19
+ const pose = source.gripPose;
20
+ if (!pose) {
21
+ out.push(source);
22
+ continue;
23
+ }
24
+ seen.add(source.id);
25
+ const previous = this.tracked.get(source.id);
26
+ const stored = {
27
+ pose: { position: pose.position, quaternion: pose.quaternion },
28
+ linear: previous?.linear ?? null,
29
+ angular: previous?.angular ?? null,
30
+ };
31
+ // First sighting, or a frame with no elapsed time: store the pose and
32
+ // report nothing. Differentiating either would be a fabricated number.
33
+ if (previous === undefined || !Number.isFinite(dt) || dt <= 0) {
34
+ this.tracked.set(source.id, stored);
35
+ out.push(source);
36
+ continue;
37
+ }
38
+ const raw = poseVelocity(previous.pose, pose, dt);
39
+ const linear = smooth(previous.linear, raw.linear, this.smoothing);
40
+ const angular = smooth(previous.angular, raw.angular, this.smoothing);
41
+ stored.linear = linear;
42
+ stored.angular = angular;
43
+ this.tracked.set(source.id, stored);
44
+ // The provider's own numbers always win - it may have them from the
45
+ // platform, which knows better than a difference of two samples.
46
+ const supplied = source;
47
+ const needsLinear = supplied.linearVelocity === undefined;
48
+ const needsAngular = supplied.angularVelocity === undefined;
49
+ if (!needsLinear && !needsAngular) {
50
+ out.push(source);
51
+ continue;
52
+ }
53
+ out.push({
54
+ ...supplied,
55
+ ...(needsLinear ? { linearVelocity: linear } : {}),
56
+ ...(needsAngular ? { angularVelocity: angular } : {}),
57
+ });
58
+ }
59
+ // A source that stopped reporting starts fresh when it comes back.
60
+ for (const id of [...this.tracked.keys()]) {
61
+ if (!seen.has(id))
62
+ this.tracked.delete(id);
63
+ }
64
+ return out;
65
+ }
66
+ /** Forget every tracked source (session end, teleport, scene change). */
67
+ reset() {
68
+ this.tracked.clear();
69
+ }
70
+ }
71
+ function smooth(previous, raw, factor) {
72
+ if (previous === null || factor >= 1)
73
+ return raw;
74
+ return [
75
+ previous[0] + (raw[0] - previous[0]) * factor,
76
+ previous[1] + (raw[1] - previous[1]) * factor,
77
+ previous[2] + (raw[2] - previous[2]) * factor,
78
+ ];
79
+ }
80
+ //# sourceMappingURL=velocity-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"velocity-tracker.js","sourceRoot":"","sources":["../src/velocity-tracker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * VelocityTracker - per-source linear and angular velocity, derived from\n * consecutive grip poses.\n *\n * Throw, flick and swipe all need to know how fast a hand was moving at\n * the moment it let go. WebXR does not report that, and most engines do\n * not either, so the core differentiates the poses it already samples.\n * A provider that CAN report velocity keeps its own numbers - the tracker\n * only fills gaps.\n *\n * The tracker is deliberately conservative. A source seen for the first\n * time (or seen again after dropping out of tracking) gets no velocity\n * that frame, so a reconnecting controller cannot report a metres-per-\n * second jump from wherever it was last seen.\n */\nimport type { InputSourceSnapshot, PoseTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { poseVelocity } from \"./math.js\";\n\n/**\n * A sampled source carrying the velocity the tracker measured.\n * Collapses into `InputSourceSnapshot` in\n * `@realitycollective/webxr-input` 0.1.1, which adds these two fields.\n */\nexport type InputSourceSnapshotWithVelocity = InputSourceSnapshot & {\n /** Metres per second, world space. */\n linearVelocity?: Vec3Tuple;\n /** Radians per second about each world axis. */\n angularVelocity?: Vec3Tuple;\n};\n\nexport interface VelocityTrackerOptions {\n /**\n * Exponential moving average factor in `(0, 1]`. 1 (the default) is no\n * smoothing - every frame reports the raw difference. Lower values trade\n * responsiveness for steadiness; 0.3 is a reasonable starting point when\n * the pose stream is noisy.\n */\n smoothing?: number;\n}\n\ninterface Tracked {\n pose: PoseTuple;\n linear: Vec3Tuple | null;\n angular: Vec3Tuple | null;\n}\n\nconst DEFAULT_SMOOTHING = 1;\n\nexport class VelocityTracker {\n private readonly smoothing: number;\n private readonly tracked = new Map<string, Tracked>();\n\n constructor(options: VelocityTrackerOptions = {}) {\n const smoothing = options.smoothing ?? DEFAULT_SMOOTHING;\n this.smoothing = smoothing > 0 && smoothing <= 1 ? smoothing : DEFAULT_SMOOTHING;\n }\n\n /**\n * Measure this frame's sources. Returns the same sources, with velocity\n * added to those the provider left without it. Sources with no grip pose\n * pass through untouched and hold no state.\n */\n update(\n sources: readonly InputSourceSnapshot[],\n dt: number,\n ): readonly InputSourceSnapshotWithVelocity[] {\n const seen = new Set<string>();\n const out: InputSourceSnapshotWithVelocity[] = [];\n\n for (const source of sources) {\n const pose = source.gripPose;\n if (!pose) {\n out.push(source);\n continue;\n }\n seen.add(source.id);\n const previous = this.tracked.get(source.id);\n const stored: Tracked = {\n pose: { position: pose.position, quaternion: pose.quaternion },\n linear: previous?.linear ?? null,\n angular: previous?.angular ?? null,\n };\n\n // First sighting, or a frame with no elapsed time: store the pose and\n // report nothing. Differentiating either would be a fabricated number.\n if (previous === undefined || !Number.isFinite(dt) || dt <= 0) {\n this.tracked.set(source.id, stored);\n out.push(source);\n continue;\n }\n\n const raw = poseVelocity(previous.pose, pose, dt);\n const linear = smooth(previous.linear, raw.linear, this.smoothing);\n const angular = smooth(previous.angular, raw.angular, this.smoothing);\n stored.linear = linear;\n stored.angular = angular;\n this.tracked.set(source.id, stored);\n\n // The provider's own numbers always win - it may have them from the\n // platform, which knows better than a difference of two samples.\n const supplied = source as InputSourceSnapshotWithVelocity;\n const needsLinear = supplied.linearVelocity === undefined;\n const needsAngular = supplied.angularVelocity === undefined;\n if (!needsLinear && !needsAngular) {\n out.push(source);\n continue;\n }\n out.push({\n ...supplied,\n ...(needsLinear ? { linearVelocity: linear } : {}),\n ...(needsAngular ? { angularVelocity: angular } : {}),\n });\n }\n\n // A source that stopped reporting starts fresh when it comes back.\n for (const id of [...this.tracked.keys()]) {\n if (!seen.has(id)) this.tracked.delete(id);\n }\n return out;\n }\n\n /** Forget every tracked source (session end, teleport, scene change). */\n reset(): void {\n this.tracked.clear();\n }\n}\n\nfunction smooth(previous: Vec3Tuple | null, raw: Vec3Tuple, factor: number): Vec3Tuple {\n if (previous === null || factor >= 1) return raw;\n return [\n previous[0] + (raw[0] - previous[0]) * factor,\n previous[1] + (raw[1] - previous[1]) * factor,\n previous[2] + (raw[2] - previous[2]) * factor,\n ];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/webxr-interactions",
3
- "version": "0.1.0-preview.1",
3
+ "version": "0.1.0-preview.2",
4
4
  "description": "Engine-free core of the Reality Collective Interaction Extensions: interactables, interactors, behaviours (press, hinge, dial, slide, grab, toss-scoring, pulse, gaze-dwell), capability negotiation and feedback hooks - driven through the shared @realitycollective/webxr-input contracts. Pair with an engine adapter: threejs-interactions, iwsdk-interactions or xrblocks-interactions.",
5
5
  "keywords": [
6
6
  "realitycollective",