@realitycollective/webxr-interactions 0.1.1-preview.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/README.md +17 -2
  3. package/dist/behaviours/behaviour.d.ts +17 -1
  4. package/dist/behaviours/behaviour.js +12 -2
  5. package/dist/behaviours/behaviour.js.map +1 -1
  6. package/dist/behaviours/grab.d.ts +1 -0
  7. package/dist/behaviours/grab.js +10 -4
  8. package/dist/behaviours/grab.js.map +1 -1
  9. package/dist/contract-cases.js +84 -0
  10. package/dist/contract-cases.js.map +1 -1
  11. package/dist/descriptor.d.ts +7 -1
  12. package/dist/descriptor.js.map +1 -1
  13. package/dist/gaze.d.ts +50 -0
  14. package/dist/gaze.js +162 -0
  15. package/dist/gaze.js.map +1 -1
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.js +9 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/math.d.ts +8 -0
  20. package/dist/math.js +17 -0
  21. package/dist/math.js.map +1 -1
  22. package/dist/memory-physics.d.ts +41 -0
  23. package/dist/memory-physics.js +168 -0
  24. package/dist/memory-physics.js.map +1 -0
  25. package/dist/memory-ports.d.ts +55 -0
  26. package/dist/memory-ports.js +128 -0
  27. package/dist/memory-ports.js.map +1 -0
  28. package/dist/near-pointer-contract-cases.d.ts +16 -0
  29. package/dist/near-pointer-contract-cases.js +283 -0
  30. package/dist/near-pointer-contract-cases.js.map +1 -0
  31. package/dist/near-pointer.d.ts +88 -0
  32. package/dist/near-pointer.js +114 -0
  33. package/dist/near-pointer.js.map +1 -0
  34. package/dist/physics-contract-cases.d.ts +11 -0
  35. package/dist/physics-contract-cases.js +229 -0
  36. package/dist/physics-contract-cases.js.map +1 -0
  37. package/dist/physics.d.ts +148 -0
  38. package/dist/physics.js +35 -0
  39. package/dist/physics.js.map +1 -0
  40. package/dist/ports.d.ts +15 -0
  41. package/dist/ports.js.map +1 -1
  42. package/dist/release-velocity.d.ts +58 -0
  43. package/dist/release-velocity.js +89 -0
  44. package/dist/release-velocity.js.map +1 -0
  45. package/dist/runtime.d.ts +129 -4
  46. package/dist/runtime.js +362 -40
  47. package/dist/runtime.js.map +1 -1
  48. package/dist/trace-synthetic.d.ts +12 -0
  49. package/dist/trace-synthetic.js +194 -0
  50. package/dist/trace-synthetic.js.map +1 -0
  51. package/dist/trace.d.ts +215 -0
  52. package/dist/trace.js +350 -0
  53. package/dist/trace.js.map +1 -0
  54. package/package.json +8 -4
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The release velocity of a throw - the core rule, restated from what Meta
3
+ * IWSDK 1.0.0 does with a held object so every platform's throw carries the
4
+ * same speed on the same input.
5
+ *
6
+ * On the web a thrown object is carried by IWSDK's grab system and Havok.
7
+ * While the object is `Grabbed`, `physics-system.js` sends the body a
8
+ * `SetTargetTransform` command every frame with the object's pose (the pose
9
+ * the grab pointer moved it to), so Havok drives the body kinematically to
10
+ * that target at each 1/60 s step (`PHYSICS_DEFAULTS.stepsPerSecond`). When
11
+ * the grab ends the tag is removed and nothing resets the body: it keeps the
12
+ * velocity the engine gave it to reach the last target, the displacement of
13
+ * the OBJECT over the last physics step divided by the step. So IWSDK's
14
+ * throw is:
15
+ *
16
+ * - the velocity of the held OBJECT, not of the grip: for a rigid carry the
17
+ * two agree, but the rule is stated on the object, which every platform
18
+ * has, rather than on a runtime-reported hand velocity, which most do not;
19
+ * - measured over the last physics step, `RELEASE_WINDOW_SECONDS` (1/60 s):
20
+ * at 72 Hz that is one frame, at 90 Hz two, at 120 Hz two;
21
+ * - angular velocity from the orientation delta over the same window.
22
+ *
23
+ * `ReleaseVelocityTracker` keeps the poses the carry wrote and their frame
24
+ * times; `release()` differentiates over the most recent frames that cover
25
+ * the window, or over the last frame when only one has been seen. A hold
26
+ * that never moved (or was released the frame it started) throws nothing:
27
+ * zeros, so the object rests where it was let go. A synthesised release
28
+ * (source lost, target unregistered, runtime disposed) never reads this: the
29
+ * grab behaviour hands the port zeros, as `grab.ts` says.
30
+ */
31
+ import { velocityBetween } from "@realitycollective/webxr-input";
32
+ /** IWSDK's physics step, 1/60 s: the window a throw's velocity is measured over. */
33
+ export const RELEASE_WINDOW_SECONDS = 1 / 60;
34
+ /** How many past frames the tracker keeps: enough to cover the window at any refresh rate down to 30 Hz. */
35
+ const MAX_SAMPLES = 8;
36
+ export class ReleaseVelocityTracker {
37
+ window;
38
+ samples = [];
39
+ constructor(options = {}) {
40
+ const window = options.windowSeconds ?? RELEASE_WINDOW_SECONDS;
41
+ if (!Number.isFinite(window) || window <= 0) {
42
+ throw new Error(`[webxr-interactions] release window must be a positive number of seconds, got ${String(window)}`);
43
+ }
44
+ this.window = window;
45
+ }
46
+ /** Forget every pose: the hold starts afresh. */
47
+ reset() {
48
+ this.samples.length = 0;
49
+ }
50
+ /**
51
+ * Record the pose the carry wrote this frame, `dt` seconds after the last
52
+ * one. The tuples are copied, so the caller may reuse them.
53
+ */
54
+ record(pose, dt) {
55
+ const copy = {
56
+ position: [pose.position[0], pose.position[1], pose.position[2]],
57
+ quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]],
58
+ };
59
+ const step = this.samples.length === 0 || !Number.isFinite(dt) || dt < 0 ? 0 : dt;
60
+ this.samples.push({ pose: copy, dt: step });
61
+ if (this.samples.length > MAX_SAMPLES)
62
+ this.samples.shift();
63
+ }
64
+ /** How many poses are recorded. */
65
+ get size() {
66
+ return this.samples.length;
67
+ }
68
+ /**
69
+ * The velocity the object carries at release: linear m/s and angular
70
+ * rad/s (axis scaled), world space, over the most recent frames that span
71
+ * at least the window. Zeros with fewer than two poses, or no elapsed time.
72
+ */
73
+ release() {
74
+ const n = this.samples.length;
75
+ if (n < 2)
76
+ return { linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0] };
77
+ let elapsed = 0;
78
+ let start = n - 1;
79
+ while (start > 0 && elapsed < this.window) {
80
+ elapsed += this.samples[start].dt;
81
+ start -= 1;
82
+ }
83
+ if (elapsed <= 0)
84
+ return { linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0] };
85
+ const velocity = velocityBetween(this.samples[start].pose, this.samples[n - 1].pose, elapsed);
86
+ return { linearVelocity: velocity.linear, angularVelocity: velocity.angular };
87
+ }
88
+ }
89
+ //# sourceMappingURL=release-velocity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-velocity.js","sourceRoot":"","sources":["../src/release-velocity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The release velocity of a throw - the core rule, restated from what Meta\n * IWSDK 1.0.0 does with a held object so every platform's throw carries the\n * same speed on the same input.\n *\n * On the web a thrown object is carried by IWSDK's grab system and Havok.\n * While the object is `Grabbed`, `physics-system.js` sends the body a\n * `SetTargetTransform` command every frame with the object's pose (the pose\n * the grab pointer moved it to), so Havok drives the body kinematically to\n * that target at each 1/60 s step (`PHYSICS_DEFAULTS.stepsPerSecond`). When\n * the grab ends the tag is removed and nothing resets the body: it keeps the\n * velocity the engine gave it to reach the last target, the displacement of\n * the OBJECT over the last physics step divided by the step. So IWSDK's\n * throw is:\n *\n * - the velocity of the held OBJECT, not of the grip: for a rigid carry the\n * two agree, but the rule is stated on the object, which every platform\n * has, rather than on a runtime-reported hand velocity, which most do not;\n * - measured over the last physics step, `RELEASE_WINDOW_SECONDS` (1/60 s):\n * at 72 Hz that is one frame, at 90 Hz two, at 120 Hz two;\n * - angular velocity from the orientation delta over the same window.\n *\n * `ReleaseVelocityTracker` keeps the poses the carry wrote and their frame\n * times; `release()` differentiates over the most recent frames that cover\n * the window, or over the last frame when only one has been seen. A hold\n * that never moved (or was released the frame it started) throws nothing:\n * zeros, so the object rests where it was let go. A synthesised release\n * (source lost, target unregistered, runtime disposed) never reads this: the\n * grab behaviour hands the port zeros, as `grab.ts` says.\n */\nimport { velocityBetween, type PoseTuple } from \"@realitycollective/webxr-input\";\nimport type { HoldRelease } from \"./ports.js\";\n\n/** IWSDK's physics step, 1/60 s: the window a throw's velocity is measured over. */\nexport const RELEASE_WINDOW_SECONDS = 1 / 60;\n\n/** How many past frames the tracker keeps: enough to cover the window at any refresh rate down to 30 Hz. */\nconst MAX_SAMPLES = 8;\n\ninterface Sample {\n pose: PoseTuple;\n /** Seconds between this pose and the one before it (0 for the first). */\n dt: number;\n}\n\nexport interface ReleaseVelocityTrackerOptions {\n /** Seconds the release velocity is measured over. Default `RELEASE_WINDOW_SECONDS`. */\n windowSeconds?: number;\n}\n\nexport class ReleaseVelocityTracker {\n private readonly window: number;\n private readonly samples: Sample[] = [];\n\n constructor(options: ReleaseVelocityTrackerOptions = {}) {\n const window = options.windowSeconds ?? RELEASE_WINDOW_SECONDS;\n if (!Number.isFinite(window) || window <= 0) {\n throw new Error(`[webxr-interactions] release window must be a positive number of seconds, got ${String(window)}`);\n }\n this.window = window;\n }\n\n /** Forget every pose: the hold starts afresh. */\n reset(): void {\n this.samples.length = 0;\n }\n\n /**\n * Record the pose the carry wrote this frame, `dt` seconds after the last\n * one. The tuples are copied, so the caller may reuse them.\n */\n record(pose: PoseTuple, dt: number): void {\n const copy: PoseTuple = {\n position: [pose.position[0], pose.position[1], pose.position[2]],\n quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]],\n };\n const step = this.samples.length === 0 || !Number.isFinite(dt) || dt < 0 ? 0 : dt;\n this.samples.push({ pose: copy, dt: step });\n if (this.samples.length > MAX_SAMPLES) this.samples.shift();\n }\n\n /** How many poses are recorded. */\n get size(): number {\n return this.samples.length;\n }\n\n /**\n * The velocity the object carries at release: linear m/s and angular\n * rad/s (axis scaled), world space, over the most recent frames that span\n * at least the window. Zeros with fewer than two poses, or no elapsed time.\n */\n release(): HoldRelease {\n const n = this.samples.length;\n if (n < 2) return { linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0] };\n let elapsed = 0;\n let start = n - 1;\n while (start > 0 && elapsed < this.window) {\n elapsed += this.samples[start]!.dt;\n start -= 1;\n }\n if (elapsed <= 0) return { linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0] };\n const velocity = velocityBetween(this.samples[start]!.pose, this.samples[n - 1]!.pose, elapsed);\n return { linearVelocity: velocity.linear, angularVelocity: velocity.angular };\n }\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -13,13 +13,14 @@
13
13
  * unregister, enable and disable; behaviours disabled by capability
14
14
  * negotiation announce themselves with a `behaviourDisabled` event.
15
15
  */
16
- import { type InputCapabilities, type InputProvider, type InputSourceSnapshot, type Unsubscribe } from "@realitycollective/webxr-input";
16
+ import { PointerArbiter, PointerDisplay, type ActivePointerKind, type EyeGazeOptions, type InputCapabilities, type InputProvider, type InputSourceSnapshot, type PointerDisplayConfig, type PointerDrawing, type PointerVisuals, type Unsubscribe } from "@realitycollective/webxr-input";
17
17
  import type { Behaviour } from "./behaviours/behaviour.js";
18
18
  import type { InteractableDescriptor } from "./descriptor.js";
19
19
  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 NearPointerOptions } from "./near-pointer.js";
23
24
  import { type VelocityTrackerOptions } from "./velocity-tracker.js";
24
25
  /** Interactor id used when gaze is synthesized from the head pose. */
25
26
  export declare const HEAD_GAZE_INTERACTOR_ID = "head-gaze";
@@ -39,13 +40,43 @@ export interface InteractionRuntimeOptions {
39
40
  provider: InputProvider;
40
41
  hitTester?: HitTester;
41
42
  dwellDefaults?: DwellConfig;
43
+ /**
44
+ * Eye-gaze targeting, when the provider reports `capabilities.eyeGaze`:
45
+ * the selection cone, dwell window, near-pointer suppression and whether a
46
+ * gaze-started drag follows the pinching hand. Defaults are IWSDK 1.0.0's
47
+ * (`EYE_GAZE_DEFAULTS`).
48
+ */
49
+ eyeGaze?: EyeGazeOptions;
42
50
  /**
43
51
  * Per-source velocity tracking. On by default with no smoothing; pass
44
52
  * `false` to skip it, or options to smooth. The tracker only fills in
45
53
  * what the provider did not supply.
46
54
  */
47
55
  velocity?: VelocityTrackerOptions | false;
56
+ /**
57
+ * The near-pointer rule's distances: touch hover enter and exit, touch
58
+ * press, grip grab radius. Defaults are IWSDK 1.0.0's
59
+ * (`NEAR_POINTER_DEFAULTS`). An interactable's own `pokeRadius` overrides
60
+ * the touch hover enter distance for that target alone.
61
+ */
62
+ nearPointer?: Partial<NearPointerOptions>;
63
+ /**
64
+ * The pointer arbiter shared with everything else a pointer can reach (the
65
+ * UI Extensions window hosts), so one decision per source covers panels
66
+ * and interactables alike, as IWSDK's `MultiPointer` does. Omit for a
67
+ * runtime used on its own: it makes one, and decides over its own targets.
68
+ */
69
+ pointers?: PointerArbiter;
70
+ /**
71
+ * The app's pointer display settings (ray never, always or while hitting;
72
+ * cursors on objects and on panels; the ray and cursor look), or a
73
+ * `PointerDisplay` to share. Defaults are IWSDK 1.0.0's. Settable at run
74
+ * time through `getPointerDisplay().set(...)`.
75
+ */
76
+ pointerDisplay?: PointerDisplay | Partial<PointerDisplayConfig>;
48
77
  }
78
+ /** The target set this runtime registers with its arbiter. */
79
+ export declare const INTERACTIONS_POINTER_SET = "interactions";
49
80
  export interface RegisterPorts {
50
81
  transform?: TransformPort;
51
82
  }
@@ -58,11 +89,26 @@ export declare class InteractionRuntime {
58
89
  private readonly events;
59
90
  private readonly feedbackEmitter;
60
91
  private readonly afterSample;
92
+ private readonly visualsEmitter;
93
+ private readonly drawingEmitter;
94
+ private readonly nearPointer;
95
+ private readonly pointers;
96
+ private readonly targetSet;
97
+ private readonly pointerDisplay;
98
+ private lastVisuals;
99
+ private lastDrawings;
61
100
  private readonly velocityTracker;
62
101
  private lastSources;
63
102
  private capabilities;
64
103
  private readonly unsubscribeCaps;
65
104
  private disposed;
105
+ private readonly eyeGaze;
106
+ private readonly gazeConsensus;
107
+ /** The eye-gaze source's target this frame: cone, consensus and suppression applied. */
108
+ private eyeGazeTarget;
109
+ /** Where the eye-gaze ray last aimed at each target, for the pinching hand to aim at. */
110
+ private readonly gazePoints;
111
+ private gazeHold;
66
112
  constructor(options: InteractionRuntimeOptions);
67
113
  registerInteractable(def: InteractableDescriptor, ports?: RegisterPorts): void;
68
114
  unregisterInteractable(id: string): void;
@@ -104,15 +150,94 @@ export declare class InteractionRuntime {
104
150
  getSource(id: string): InputSourceSnapshot | undefined;
105
151
  getCapabilities(): InputCapabilities;
106
152
  getProvider(): InputProvider;
153
+ /** The near-pointer distances in force (the defaults, or the options given). */
154
+ getNearPointerOptions(): Readonly<NearPointerOptions>;
155
+ /**
156
+ * The pointer that owns a source this frame: `"touch"`, `"grab"`, `"ray"`
157
+ * or `"gaze"`, or null when no pointer has a candidate (or the source is
158
+ * unknown). Decided by the core rule in `near-pointer.ts`, on every
159
+ * platform; a provider hint names the target but never the pointer.
160
+ */
161
+ getActivePointer(sourceId: string): ActivePointerKind | null;
162
+ /**
163
+ * What to draw for every source this frame: the ray only while the ray is
164
+ * the active pointer or nothing is, the cursor at the active pointer's
165
+ * candidate. Published once per `update`, after the sources; a binding
166
+ * applies it to its ray and cursor visuals, and a native binding hands it
167
+ * to the host as `applyPointerVisuals`. The list and its entries are fresh
168
+ * each frame and never written to again.
169
+ */
170
+ onPointerVisuals(listener: (visuals: readonly PointerVisuals[]) => void): Unsubscribe;
171
+ /** The visuals last published for a source, or undefined before its first frame. */
172
+ getPointerVisuals(sourceId: string): PointerVisuals | undefined;
173
+ /**
174
+ * What to draw for every source this frame under the app's pointer display
175
+ * settings: the visuals above resolved through `pointerDrawing`. Published
176
+ * right after `onPointerVisuals`. A binding that draws its own ray and
177
+ * cursor applies exactly this; a native binding hands it to the host.
178
+ */
179
+ onPointerDrawing(listener: (drawings: readonly PointerDrawing[]) => void): Unsubscribe;
180
+ /** The drawing last published for a source, or undefined before its first frame. */
181
+ getPointerDrawing(sourceId: string): PointerDrawing | undefined;
182
+ /** The arbiter deciding which pointer owns each source, shared with the UI Extensions hosts when the app passed one. */
183
+ getPointerArbiter(): PointerArbiter;
184
+ /** The app's pointer display settings; `set` changes them at run time. */
185
+ getPointerDisplay(): PointerDisplay;
186
+ /** Whether a source is pressing or grabbing this frame (the selection that recolours its pointer). */
187
+ isSelecting(sourceId: string): boolean;
107
188
  update(dt: number): void;
108
189
  dispose(): void;
190
+ /**
191
+ * The targeting half of the eye-gaze rule (`@realitycollective/webxr-input`
192
+ * `eye-gaze.ts` states the whole of it): the cone's best candidate this
193
+ * frame, then the dwell consensus over the window, and nothing at all while
194
+ * a hand's near pointer is hovering or selecting (IWSDK
195
+ * `suppressWhenDirectPointerActive`). A frame with no valid gaze ray clears
196
+ * the record, as IWSDK resets its conecaster, unless a selection is held.
197
+ */
198
+ private resolveEyeGaze;
199
+ /** A hand's near pointer is hovering or selecting: touch or grab owns the hand, or a grab is held. */
200
+ private directPointerActive;
201
+ /**
202
+ * Once a gaze selection is held, the pinching hand drives the pointer
203
+ * (IWSDK `pointerTransformFollowsHand`): the frame the hold starts, a ray
204
+ * from the hand's ray space is aimed at the gaze hit and its orientation
205
+ * relative to the hand recorded; every later frame the gaze snapshot's ray
206
+ * is that hand's ray space turned by the same amount. When the hand could
207
+ * not reach the hit, or the option is off, the ray stays where the
208
+ * selection was made. Consumers of `onSample` and `getSource`, and every
209
+ * behaviour, see the followed ray.
210
+ */
211
+ private followHand;
212
+ /** Start, keep or end the gaze hold from the gaze source's press and grab state this frame. */
213
+ private trackGazeHold;
109
214
  private sourceState;
110
215
  private interactorInfo;
111
- /** Resolve this source's target: hints beat poke beats ray. */
216
+ /**
217
+ * Resolve this source's target and its active pointer. The pointer is the
218
+ * core rule (`near-pointer.ts`): touch, then grab, then ray, the first with
219
+ * a candidate, locked while it presses or grabs. The target is a provider
220
+ * hint when there is one (provider power: IWSDK's mesh-accurate tags), else
221
+ * the active pointer's candidate. An eye-gaze source targets through the
222
+ * cone and consensus instead.
223
+ */
112
224
  private resolveTarget;
225
+ /**
226
+ * The touch pointer's candidate this frame, advancing the source's touch
227
+ * machine: the nearest target within the widest touch band, hovering by
228
+ * that target's own enter and exit distances. One proximity query at the
229
+ * largest radius any interactable asked for, then the nearest hit must be
230
+ * inside ITS OWN band; the trade is that a farther target with a wider
231
+ * band is not found behind a nearer one with a narrower band.
232
+ */
233
+ private touchCandidate;
234
+ /** The grab pointer's candidate: the nearest target whose surface the grip sphere reaches. */
235
+ private grabCandidate;
236
+ /** The ray pointer's candidate: the nearest target along the ray. */
237
+ private rayCandidate;
113
238
  private targetable;
114
- /** The largest poke radius among enabled interactables; the default when none is registered. */
115
- private maxPokeRadius;
239
+ /** The widest touch exit band among enabled interactables; the default exit distance when none is registered. */
240
+ private touchQueryRadius;
116
241
  private flags;
117
242
  private updateSource;
118
243
  private gazeAllows;