@realitycollective/native-interactions 0.1.1-preview.2 → 0.1.1-preview.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/provider.js CHANGED
@@ -8,7 +8,8 @@
8
8
  * the WebXR session: `rays`, `pokes` and grabs from immersion, `handJoints`
9
9
  * and `pinch` from hand tracking or a tracked hand, `buttonsAxes` and
10
10
  * `haptics` from the sources present, `gaze` and `headPose` from a head
11
- * pose, `presence` from the host's presence member. They re-derive, and
11
+ * pose, `eyeGaze` from the eye-tracking fact and a host that can pose the
12
+ * gaze, `presence` from the host's presence member. They re-derive, and
12
13
  * `onCapabilitiesChanged` fires, whenever the facts or the sources change.
13
14
  * - Presence is decided here, as `applyPresence` decides it: the requested
14
15
  * visibility per side, and the modality, with `"auto"` showing hands while
@@ -16,15 +17,21 @@
16
17
  * draw per side.
17
18
  * - No sources are sampled while the session lacks focus, as IWSDK samples
18
19
  * none unless its visibility state is `Visible`.
20
+ * - The eye-gaze rule (`@realitycollective/webxr-input` `eye-gaze.ts`) runs
21
+ * here over the host's raw snapshots and its gaze pose, as IWSDK's
22
+ * `GazePointer` runs it: far rays drop once gaze has a valid pose, a pinch
23
+ * selects, and the pinching hand's ray pose travels as `selectorPose`. A
24
+ * host reports only `eyeTracking` and `getEyeGazePose()`.
19
25
  * - A pulse's intensity is clamped to 0..1 before it reaches the host.
20
26
  *
21
27
  * Every snapshot, and every tuple inside it, is copied before it leaves this
22
28
  * class, so a host that reuses its own sample buffers still meets the
23
29
  * ownership rule the shared contract suite checks.
24
30
  */
25
- import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
31
+ import { EyeGazeInput, NO_CAPABILITIES, rayPoseFromRay, } from "@realitycollective/webxr-input";
26
32
  import { copyPose, copySnapshot, resolveHostSlice, } from "./native-types.js";
27
33
  const SIDES = ["left", "right"];
34
+ const DEFAULT_FRAME_SECONDS = 1 / 60;
28
35
  function clampUnit(value) {
29
36
  return Math.min(1, Math.max(0, value));
30
37
  }
@@ -39,6 +46,8 @@ export class NativeInputProvider {
39
46
  presenceVisible = { left: true, right: true };
40
47
  presenceModality = "auto";
41
48
  presenceApplied = { left: null, right: null };
49
+ eyeGaze;
50
+ frameSeconds;
42
51
  getHeadPose;
43
52
  sampleHints;
44
53
  pulse;
@@ -47,6 +56,8 @@ export class NativeInputProvider {
47
56
  constructor(options = {}) {
48
57
  this.host = resolveHostSlice("input", options.input);
49
58
  this.nativeGrab = options.nativeGrab ?? false;
59
+ this.eyeGaze = new EyeGazeInput(options.eyeGaze);
60
+ this.frameSeconds = options.frameSeconds ?? DEFAULT_FRAME_SECONDS;
50
61
  this.facts = { ...this.host.getFacts() };
51
62
  this.capabilities = this.derive();
52
63
  const host = this.host;
@@ -90,10 +101,19 @@ export class NativeInputProvider {
90
101
  this.sourceListeners.add(listener);
91
102
  return () => this.sourceListeners.delete(listener);
92
103
  }
104
+ /** The seconds the next `sample()` integrates over; `NativeInteractions.update(dt)` sets it each frame. */
105
+ setFrameDelta(seconds) {
106
+ this.frameSeconds = seconds;
107
+ }
93
108
  sample() {
94
- if (!this.facts.focused)
109
+ if (!this.facts.focused) {
110
+ this.eyeGaze.reset();
95
111
  return [];
96
- return this.host.sample().map(copySnapshot);
112
+ }
113
+ const sources = this.host.sample().map(copySnapshot);
114
+ if (!this.capabilities.eyeGaze)
115
+ return sources;
116
+ return this.eyeGaze.update(this.eyeGazeFrame(sources), sources, this.frameSeconds);
97
117
  }
98
118
  /** Release the host subscriptions and every listener. */
99
119
  dispose() {
@@ -102,6 +122,24 @@ export class NativeInputProvider {
102
122
  this.disposers.length = 0;
103
123
  this.capsListeners.clear();
104
124
  this.sourceListeners.clear();
125
+ this.eyeGaze.reset();
126
+ }
127
+ /**
128
+ * The eye-gaze reading for this frame: the host's raw pose (copied), and
129
+ * each hand's ray-space pose. A host reports a hand's ray, not its
130
+ * ray-space orientation, so the pose is built from the ray with world up
131
+ * (`rayPoseFromRay`): a gaze-started drag follows the hand's position and
132
+ * direction, not its roll about the ray.
133
+ */
134
+ eyeGazeFrame(sources) {
135
+ const raw = this.host.getEyeGazePose?.() ?? null;
136
+ const rayPoses = {};
137
+ for (const source of sources) {
138
+ if ((source.handedness === "left" || source.handedness === "right") && source.ray && !rayPoses[source.handedness]) {
139
+ rayPoses[source.handedness] = rayPoseFromRay(source.ray);
140
+ }
141
+ }
142
+ return { present: this.facts.eyeTracking, pose: raw ? copyPose(raw) : null, rayPoses };
105
143
  }
106
144
  /** Re-read the facts and re-derive; publish and re-apply presence on a change. */
107
145
  refresh() {
@@ -109,6 +147,8 @@ export class NativeInputProvider {
109
147
  const next = this.derive();
110
148
  if (JSON.stringify(next) !== JSON.stringify(this.capabilities)) {
111
149
  this.capabilities = next;
150
+ if (!next.eyeGaze)
151
+ this.eyeGaze.reset();
112
152
  for (const listener of [...this.capsListeners])
113
153
  listener(next);
114
154
  }
@@ -116,7 +156,7 @@ export class NativeInputProvider {
116
156
  }
117
157
  /** `IWSDKInputProvider.refreshCapabilities`, over the host's facts and sources. */
118
158
  derive() {
119
- const { immersive, handTracking } = this.facts;
159
+ const { immersive, handTracking, eyeTracking } = this.facts;
120
160
  const sources = immersive ? this.host.sample() : [];
121
161
  const handTracked = sources.some((source) => source.kind === "hand");
122
162
  const hands = immersive && (handTracking || handTracked);
@@ -131,6 +171,9 @@ export class NativeInputProvider {
131
171
  pinch: hands,
132
172
  buttonsAxes: sources.some((source) => source.kind === "controller"),
133
173
  gaze: head,
174
+ // IWSDK: the session reports a gaze input source. The host must also be
175
+ // able to pose it, or the fact is worth nothing.
176
+ eyeGaze: immersive && eyeTracking === true && typeof this.host.getEyeGazePose === "function",
134
177
  headPose: head,
135
178
  haptics: typeof this.host.pulse === "function" && sources.some((source) => source.hapticsAvailable === true),
136
179
  presence: typeof this.host.applyPresence === "function",
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativeInputProvider - the native host's input provider.\n *\n * It applies the rules `IWSDKInputProvider` applies, over the facts the\n * native app's `input` slice reports, so the host is handed results:\n *\n * - Capabilities are DERIVED here, as `refreshCapabilities` derives them from\n * the WebXR session: `rays`, `pokes` and grabs from immersion, `handJoints`\n * and `pinch` from hand tracking or a tracked hand, `buttonsAxes` and\n * `haptics` from the sources present, `gaze` and `headPose` from a head\n * pose, `presence` from the host's presence member. They re-derive, and\n * `onCapabilitiesChanged` fires, whenever the facts or the sources change.\n * - Presence is decided here, as `applyPresence` decides it: the requested\n * visibility per side, and the modality, with `\"auto\"` showing hands while\n * hand joints are live and controllers otherwise. The host is told what to\n * draw per side.\n * - No sources are sampled while the session lacks focus, as IWSDK samples\n * none unless its visibility state is `Visible`.\n * - A pulse's intensity is clamped to 0..1 before it reaches the host.\n *\n * Every snapshot, and every tuple inside it, is copied before it leaves this\n * class, so a host that reuses its own sample buffers still meets the\n * ownership rule the shared contract suite checks.\n */\nimport {\n NO_CAPABILITIES,\n type Handedness,\n type HeadPose,\n type InputCapabilities,\n type InputHitHint,\n type InputProvider,\n type InputSourceSnapshot,\n type PresenceModality,\n type Unsubscribe,\n} from \"@realitycollective/webxr-input\";\nimport {\n copyPose,\n copySnapshot,\n resolveHostSlice,\n type NativeInputFacts,\n type NativeInputHost,\n type NativePresenceShown,\n} from \"./native-types.js\";\n\nexport interface NativeInputProviderOptions {\n /** The `input` slice. Omit to read `globalThis.__rcHost.input`. */\n input?: NativeInputHost;\n /**\n * The native app fulfils grabs with its own physics, so grabs report\n * `\"native\"` and the runtime never carries the object. Default `false`:\n * `\"poseOnly\"`, the runtime carries it. The same option as IWSDK's\n * `IWSDKProviderOptions.nativeGrab`.\n */\n nativeGrab?: boolean;\n}\n\ntype Side = \"left\" | \"right\";\nconst SIDES: readonly Side[] = [\"left\", \"right\"];\n\nfunction clampUnit(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\nexport class NativeInputProvider implements InputProvider {\n private readonly host: NativeInputHost;\n private readonly nativeGrab: boolean;\n private capabilities: InputCapabilities;\n private facts: NativeInputFacts;\n private readonly capsListeners = new Set<(c: InputCapabilities) => void>();\n private readonly sourceListeners = new Set<() => void>();\n private readonly disposers: Unsubscribe[] = [];\n private readonly presenceVisible: Record<Side, boolean> = { left: true, right: true };\n private presenceModality: PresenceModality = \"auto\";\n private readonly presenceApplied: Record<Side, NativePresenceShown | null> = { left: null, right: null };\n\n readonly getHeadPose?: () => HeadPose;\n readonly sampleHints?: () => readonly InputHitHint[];\n readonly pulse?: (sourceId: string, intensity: number, durationMs: number) => boolean;\n readonly setPresenceVisible?: (target: Handedness | \"all\", visible: boolean) => boolean;\n readonly setPresenceModality?: (mode: PresenceModality) => boolean;\n\n constructor(options: NativeInputProviderOptions = {}) {\n this.host = resolveHostSlice(\"input\", options.input);\n this.nativeGrab = options.nativeGrab ?? false;\n this.facts = { ...this.host.getFacts() };\n this.capabilities = this.derive();\n\n const host = this.host;\n if (host.getHeadPose) {\n this.getHeadPose = () => copyPose(host.getHeadPose!());\n }\n if (host.sampleHints) {\n this.sampleHints = () => host.sampleHints!().map((hint) => ({ ...hint }));\n }\n if (host.pulse) {\n this.pulse = (sourceId, intensity, durationMs) =>\n host.pulse!(sourceId, clampUnit(intensity), durationMs);\n }\n if (host.applyPresence) {\n this.setPresenceVisible = (target, visible) => {\n for (const side of SIDES) {\n if (target === \"all\" || target === side) this.presenceVisible[side] = visible;\n }\n return this.applyPresence();\n };\n this.setPresenceModality = (mode) => {\n this.presenceModality = mode;\n return this.applyPresence();\n };\n }\n\n this.disposers.push(\n host.onFactsChanged(() => this.refresh()),\n host.onSourcesChanged(() => {\n this.refresh();\n for (const listener of [...this.sourceListeners]) listener();\n }),\n );\n this.applyPresence();\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(listener: (capabilities: InputCapabilities) => void): Unsubscribe {\n this.capsListeners.add(listener);\n return () => this.capsListeners.delete(listener);\n }\n\n onSourcesChanged(listener: () => void): Unsubscribe {\n this.sourceListeners.add(listener);\n return () => this.sourceListeners.delete(listener);\n }\n\n sample(): readonly InputSourceSnapshot[] {\n if (!this.facts.focused) return [];\n return this.host.sample().map(copySnapshot);\n }\n\n /** Release the host subscriptions and every listener. */\n dispose(): void {\n for (const dispose of this.disposers) dispose();\n this.disposers.length = 0;\n this.capsListeners.clear();\n this.sourceListeners.clear();\n }\n\n /** Re-read the facts and re-derive; publish and re-apply presence on a change. */\n private refresh(): void {\n this.facts = { ...this.host.getFacts() };\n const next = this.derive();\n if (JSON.stringify(next) !== JSON.stringify(this.capabilities)) {\n this.capabilities = next;\n for (const listener of [...this.capsListeners]) listener(next);\n }\n this.applyPresence();\n }\n\n /** `IWSDKInputProvider.refreshCapabilities`, over the host's facts and sources. */\n private derive(): InputCapabilities {\n const { immersive, handTracking } = this.facts;\n const sources = immersive ? this.host.sample() : [];\n const handTracked = sources.some((source) => source.kind === \"hand\");\n const hands = immersive && (handTracking || handTracked);\n const head = typeof this.host.getHeadPose === \"function\";\n return {\n ...NO_CAPABILITIES,\n rays: immersive,\n // A controller's index tip is its ray origin, so pokes need no hand.\n pokes: immersive,\n grabs: immersive ? (this.nativeGrab ? \"native\" : \"poseOnly\") : \"none\",\n handJoints: hands,\n pinch: hands,\n buttonsAxes: sources.some((source) => source.kind === \"controller\"),\n gaze: head,\n headPose: head,\n haptics: typeof this.host.pulse === \"function\" && sources.some((source) => source.hapticsAvailable === true),\n presence: typeof this.host.applyPresence === \"function\",\n };\n }\n\n /**\n * `IWSDKInputProvider.applyPresence`: each side shows the family the\n * modality picks, while that side is visible. Only a changed side is sent.\n * Reports whether presence reached a live session, as IWSDK reports false\n * with no session to draw in; the request is kept and applied on entry.\n */\n private applyPresence(): boolean {\n const apply = this.host.applyPresence;\n if (!apply) return false;\n const handJoints = this.capabilities.handJoints;\n const modality = this.presenceModality;\n const hands = modality === \"hands\" || (modality === \"auto\" && handJoints);\n const controllers = modality === \"controllers\" || (modality === \"auto\" && !handJoints);\n for (const side of SIDES) {\n const wanted = this.presenceVisible[side];\n const shown: NativePresenceShown = { hand: wanted && hands, controller: wanted && controllers };\n const last = this.presenceApplied[side];\n if (last && last.hand === shown.hand && last.controller === shown.controller) continue;\n this.presenceApplied[side] = shown;\n apply.call(this.host, side, { ...shown });\n }\n return this.facts.immersive;\n }\n}\n"]}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativeInputProvider - the native host's input provider.\n *\n * It applies the rules `IWSDKInputProvider` applies, over the facts the\n * native app's `input` slice reports, so the host is handed results:\n *\n * - Capabilities are DERIVED here, as `refreshCapabilities` derives them from\n * the WebXR session: `rays`, `pokes` and grabs from immersion, `handJoints`\n * and `pinch` from hand tracking or a tracked hand, `buttonsAxes` and\n * `haptics` from the sources present, `gaze` and `headPose` from a head\n * pose, `eyeGaze` from the eye-tracking fact and a host that can pose the\n * gaze, `presence` from the host's presence member. They re-derive, and\n * `onCapabilitiesChanged` fires, whenever the facts or the sources change.\n * - Presence is decided here, as `applyPresence` decides it: the requested\n * visibility per side, and the modality, with `\"auto\"` showing hands while\n * hand joints are live and controllers otherwise. The host is told what to\n * draw per side.\n * - No sources are sampled while the session lacks focus, as IWSDK samples\n * none unless its visibility state is `Visible`.\n * - The eye-gaze rule (`@realitycollective/webxr-input` `eye-gaze.ts`) runs\n * here over the host's raw snapshots and its gaze pose, as IWSDK's\n * `GazePointer` runs it: far rays drop once gaze has a valid pose, a pinch\n * selects, and the pinching hand's ray pose travels as `selectorPose`. A\n * host reports only `eyeTracking` and `getEyeGazePose()`.\n * - A pulse's intensity is clamped to 0..1 before it reaches the host.\n *\n * Every snapshot, and every tuple inside it, is copied before it leaves this\n * class, so a host that reuses its own sample buffers still meets the\n * ownership rule the shared contract suite checks.\n */\nimport {\n EyeGazeInput,\n NO_CAPABILITIES,\n rayPoseFromRay,\n type EyeGazeFrame,\n type EyeGazeOptions,\n type Handedness,\n type HeadPose,\n type InputCapabilities,\n type InputHitHint,\n type InputProvider,\n type InputSourceSnapshot,\n type PoseTuple,\n type PresenceModality,\n type Unsubscribe,\n} from \"@realitycollective/webxr-input\";\nimport {\n copyPose,\n copySnapshot,\n resolveHostSlice,\n type NativeInputFacts,\n type NativeInputHost,\n type NativePresenceShown,\n} from \"./native-types.js\";\n\nexport interface NativeInputProviderOptions {\n /** The `input` slice. Omit to read `globalThis.__rcHost.input`. */\n input?: NativeInputHost;\n /**\n * The native app fulfils grabs with its own physics, so grabs report\n * `\"native\"` and the runtime never carries the object. Default `false`:\n * `\"poseOnly\"`, the runtime carries it. The same option as IWSDK's\n * `IWSDKProviderOptions.nativeGrab`.\n */\n nativeGrab?: boolean;\n /**\n * Tuning for the eye-gaze rule: the filter and the tracking-loss grace.\n * Defaults are IWSDK 1.0.0's (`EYE_GAZE_DEFAULTS`).\n */\n eyeGaze?: EyeGazeOptions;\n /**\n * Seconds between two `sample()` calls, for the eye-gaze filter and grace,\n * when the host cannot say. Default 1/60. `NativeInteractions` passes the\n * real frame delta through `setFrameDelta`.\n */\n frameSeconds?: number;\n}\n\ntype Side = \"left\" | \"right\";\nconst SIDES: readonly Side[] = [\"left\", \"right\"];\nconst DEFAULT_FRAME_SECONDS = 1 / 60;\n\nfunction clampUnit(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\nexport class NativeInputProvider implements InputProvider {\n private readonly host: NativeInputHost;\n private readonly nativeGrab: boolean;\n private capabilities: InputCapabilities;\n private facts: NativeInputFacts;\n private readonly capsListeners = new Set<(c: InputCapabilities) => void>();\n private readonly sourceListeners = new Set<() => void>();\n private readonly disposers: Unsubscribe[] = [];\n private readonly presenceVisible: Record<Side, boolean> = { left: true, right: true };\n private presenceModality: PresenceModality = \"auto\";\n private readonly presenceApplied: Record<Side, NativePresenceShown | null> = { left: null, right: null };\n private readonly eyeGaze: EyeGazeInput;\n private frameSeconds: number;\n\n readonly getHeadPose?: () => HeadPose;\n readonly sampleHints?: () => readonly InputHitHint[];\n readonly pulse?: (sourceId: string, intensity: number, durationMs: number) => boolean;\n readonly setPresenceVisible?: (target: Handedness | \"all\", visible: boolean) => boolean;\n readonly setPresenceModality?: (mode: PresenceModality) => boolean;\n\n constructor(options: NativeInputProviderOptions = {}) {\n this.host = resolveHostSlice(\"input\", options.input);\n this.nativeGrab = options.nativeGrab ?? false;\n this.eyeGaze = new EyeGazeInput(options.eyeGaze);\n this.frameSeconds = options.frameSeconds ?? DEFAULT_FRAME_SECONDS;\n this.facts = { ...this.host.getFacts() };\n this.capabilities = this.derive();\n\n const host = this.host;\n if (host.getHeadPose) {\n this.getHeadPose = () => copyPose(host.getHeadPose!());\n }\n if (host.sampleHints) {\n this.sampleHints = () => host.sampleHints!().map((hint) => ({ ...hint }));\n }\n if (host.pulse) {\n this.pulse = (sourceId, intensity, durationMs) =>\n host.pulse!(sourceId, clampUnit(intensity), durationMs);\n }\n if (host.applyPresence) {\n this.setPresenceVisible = (target, visible) => {\n for (const side of SIDES) {\n if (target === \"all\" || target === side) this.presenceVisible[side] = visible;\n }\n return this.applyPresence();\n };\n this.setPresenceModality = (mode) => {\n this.presenceModality = mode;\n return this.applyPresence();\n };\n }\n\n this.disposers.push(\n host.onFactsChanged(() => this.refresh()),\n host.onSourcesChanged(() => {\n this.refresh();\n for (const listener of [...this.sourceListeners]) listener();\n }),\n );\n this.applyPresence();\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(listener: (capabilities: InputCapabilities) => void): Unsubscribe {\n this.capsListeners.add(listener);\n return () => this.capsListeners.delete(listener);\n }\n\n onSourcesChanged(listener: () => void): Unsubscribe {\n this.sourceListeners.add(listener);\n return () => this.sourceListeners.delete(listener);\n }\n\n /** The seconds the next `sample()` integrates over; `NativeInteractions.update(dt)` sets it each frame. */\n setFrameDelta(seconds: number): void {\n this.frameSeconds = seconds;\n }\n\n sample(): readonly InputSourceSnapshot[] {\n if (!this.facts.focused) {\n this.eyeGaze.reset();\n return [];\n }\n const sources = this.host.sample().map(copySnapshot);\n if (!this.capabilities.eyeGaze) return sources;\n return this.eyeGaze.update(this.eyeGazeFrame(sources), sources, this.frameSeconds);\n }\n\n /** Release the host subscriptions and every listener. */\n dispose(): void {\n for (const dispose of this.disposers) dispose();\n this.disposers.length = 0;\n this.capsListeners.clear();\n this.sourceListeners.clear();\n this.eyeGaze.reset();\n }\n\n /**\n * The eye-gaze reading for this frame: the host's raw pose (copied), and\n * each hand's ray-space pose. A host reports a hand's ray, not its\n * ray-space orientation, so the pose is built from the ray with world up\n * (`rayPoseFromRay`): a gaze-started drag follows the hand's position and\n * direction, not its roll about the ray.\n */\n private eyeGazeFrame(sources: readonly InputSourceSnapshot[]): EyeGazeFrame {\n const raw = this.host.getEyeGazePose?.() ?? null;\n const rayPoses: Partial<Record<Side, PoseTuple>> = {};\n for (const source of sources) {\n if ((source.handedness === \"left\" || source.handedness === \"right\") && source.ray && !rayPoses[source.handedness]) {\n rayPoses[source.handedness] = rayPoseFromRay(source.ray);\n }\n }\n return { present: this.facts.eyeTracking, pose: raw ? copyPose(raw) : null, rayPoses };\n }\n\n /** Re-read the facts and re-derive; publish and re-apply presence on a change. */\n private refresh(): void {\n this.facts = { ...this.host.getFacts() };\n const next = this.derive();\n if (JSON.stringify(next) !== JSON.stringify(this.capabilities)) {\n this.capabilities = next;\n if (!next.eyeGaze) this.eyeGaze.reset();\n for (const listener of [...this.capsListeners]) listener(next);\n }\n this.applyPresence();\n }\n\n /** `IWSDKInputProvider.refreshCapabilities`, over the host's facts and sources. */\n private derive(): InputCapabilities {\n const { immersive, handTracking, eyeTracking } = this.facts;\n const sources = immersive ? this.host.sample() : [];\n const handTracked = sources.some((source) => source.kind === \"hand\");\n const hands = immersive && (handTracking || handTracked);\n const head = typeof this.host.getHeadPose === \"function\";\n return {\n ...NO_CAPABILITIES,\n rays: immersive,\n // A controller's index tip is its ray origin, so pokes need no hand.\n pokes: immersive,\n grabs: immersive ? (this.nativeGrab ? \"native\" : \"poseOnly\") : \"none\",\n handJoints: hands,\n pinch: hands,\n buttonsAxes: sources.some((source) => source.kind === \"controller\"),\n gaze: head,\n // IWSDK: the session reports a gaze input source. The host must also be\n // able to pose it, or the fact is worth nothing.\n eyeGaze: immersive && eyeTracking === true && typeof this.host.getEyeGazePose === \"function\",\n headPose: head,\n haptics: typeof this.host.pulse === \"function\" && sources.some((source) => source.hapticsAvailable === true),\n presence: typeof this.host.applyPresence === \"function\",\n };\n }\n\n /**\n * `IWSDKInputProvider.applyPresence`: each side shows the family the\n * modality picks, while that side is visible. Only a changed side is sent.\n * Reports whether presence reached a live session, as IWSDK reports false\n * with no session to draw in; the request is kept and applied on entry.\n */\n private applyPresence(): boolean {\n const apply = this.host.applyPresence;\n if (!apply) return false;\n const handJoints = this.capabilities.handJoints;\n const modality = this.presenceModality;\n const hands = modality === \"hands\" || (modality === \"auto\" && handJoints);\n const controllers = modality === \"controllers\" || (modality === \"auto\" && !handJoints);\n for (const side of SIDES) {\n const wanted = this.presenceVisible[side];\n const shown: NativePresenceShown = { hand: wanted && hands, controller: wanted && controllers };\n const last = this.presenceApplied[side];\n if (last && last.hand === shown.hand && last.controller === shown.controller) continue;\n this.presenceApplied[side] = shown;\n apply.call(this.host, side, { ...shown });\n }\n return this.facts.immersive;\n }\n}\n"]}
@@ -13,16 +13,27 @@
13
13
  * `beginHold` and `endHold` are always present. A pose-only grab calls them
14
14
  * on every grab and release, and a host that silently lacked them used to
15
15
  * drop every throw on the floor (the release velocity never reached it). A
16
- * host without them now fails here, at construction, naming both. A target
17
- * with no physics body simply rests where it is released; the host decides
18
- * that from its own scene, as IWSDK does.
16
+ * host without them now fails here, at construction, naming both.
17
+ *
18
+ * With `options.physics` (the platform facility over the host's `physics`
19
+ * slice, and the target's body id in it), the hold goes through the core
20
+ * physics contract: `beginHold` suspends the body, `endHold` resumes it with
21
+ * the release velocity, and `setWorldPose` teleports it with velocity
22
+ * cleared while not held. The `interactions` slice's own `beginHold` and
23
+ * `endHold` are then not called for this target. Without it, a target with
24
+ * no body rests where it is released, as the `interactions` slice states.
19
25
  */
20
26
  import type { PoseTuple, QuatTuple, Vec3Tuple } from "@realitycollective/webxr-input";
21
- import type { HoldRelease, TransformPort } from "@realitycollective/webxr-interactions";
27
+ import type { HoldRelease, PhysicsFacility, TransformPort } from "@realitycollective/webxr-interactions";
22
28
  import { type NativeInteractionHost } from "./native-types.js";
23
29
  export interface NativeTransformPortOptions {
24
30
  /** The `interactions` slice. Omit to read `globalThis.__rcHost.interactions`. */
25
31
  interactions?: NativeInteractionHost;
32
+ /** The target's physics body: the facility over the `physics` slice, and the body's id. */
33
+ physics?: {
34
+ facility: PhysicsFacility;
35
+ bodyId: string;
36
+ };
26
37
  }
27
38
  export declare class NativeTransformPort implements TransformPort {
28
39
  private readonly host;
@@ -9,7 +9,17 @@ export class NativeTransformPort {
9
9
  constructor(targetId, options = {}) {
10
10
  this.targetId = targetId;
11
11
  this.host = resolveHostSlice("interactions", options.interactions);
12
- if (this.host.setWorldPose) {
12
+ const physics = options.physics;
13
+ if (physics) {
14
+ // The facility owns the body's pose and velocity; the host's own
15
+ // world-pose write, when it has one, keeps the drawn object in step.
16
+ const host = this.host;
17
+ this.setWorldPose = (pose) => {
18
+ physics.facility.setBodyPose(physics.bodyId, pose);
19
+ host.setWorldPose?.(targetId, copyPose(pose));
20
+ };
21
+ }
22
+ else if (this.host.setWorldPose) {
13
23
  const host = this.host;
14
24
  this.setWorldPose = (pose) => host.setWorldPose(targetId, copyPose(pose));
15
25
  }
@@ -21,6 +31,11 @@ export class NativeTransformPort {
21
31
  throw new Error("@realitycollective/native-interactions: the interactions slice must implement beginHold(targetId) and endHold(targetId, release). A pose-only grab calls both, and a host without them drops every release velocity. A host with no physics implements them and lets the object rest.");
22
32
  }
23
33
  const host = this.host;
34
+ if (physics) {
35
+ this.beginHold = () => physics.facility.suspend(physics.bodyId);
36
+ this.endHold = (release) => physics.facility.resume(physics.bodyId, release);
37
+ return;
38
+ }
24
39
  this.beginHold = () => host.beginHold(targetId);
25
40
  this.endHold = (release) => host.endHold(targetId, {
26
41
  linearVelocity: copyVec3(release.linearVelocity),
@@ -1 +1 @@
1
- {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"AAqBA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAO3B,MAAM,OAAO,mBAAmB;IACb,IAAI,CAAwB;IAC5B,QAAQ,CAAS;IAEzB,YAAY,CAA6B;IACzC,SAAS,CAA2D;IACpE,SAAS,CAAa;IACtB,OAAO,CAAiC;IAEjD,YAAY,QAAgB,EAAE,UAAsC,EAAE;QACpE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QAEnE,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACzF,MAAM,IAAI,KAAK,CACb,uRAAuR,CACxR,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,EAAE,CACzB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YACrB,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;YAChD,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC;SACnD,CAAC,CAAC;IACP,CAAC;IAED,YAAY;QACV,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,gBAAgB;QACd,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,cAAc;QACZ,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,cAAc,CAAC,MAAiB;QAC9B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,gBAAgB,CAAC,UAAqB;QACpC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAClE,CAAC;CACF","sourcesContent":["/**\n * NativeTransformPort - the read/write surface of ONE interactable, over the\n * native host's `interactions` slice. The host serves every registered\n * object through one set of methods, so this port is a thin binding of one\n * `targetId` onto them - the same idea as `BabylonTransformPort` binding to\n * one node, but the object itself lives on the native side.\n *\n * `setWorldPose` and `setEffect` are optional on `TransformPort`, and are\n * only ever assigned here when the host itself carries the matching member,\n * so an app whose native host cannot follow a world pose, or has no effect\n * hook, sees a port without one rather than a method that silently no-ops.\n *\n * `beginHold` and `endHold` are always present. A pose-only grab calls them\n * on every grab and release, and a host that silently lacked them used to\n * drop every throw on the floor (the release velocity never reached it). A\n * host without them now fails here, at construction, naming both. A target\n * with no physics body simply rests where it is released; the host decides\n * that from its own scene, as IWSDK does.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { HoldRelease, TransformPort } from \"@realitycollective/webxr-interactions\";\nimport {\n copyPose,\n copyQuat,\n copyVec3,\n resolveHostSlice,\n type NativeInteractionHost,\n} from \"./native-types.js\";\n\nexport interface NativeTransformPortOptions {\n /** The `interactions` slice. Omit to read `globalThis.__rcHost.interactions`. */\n interactions?: NativeInteractionHost;\n}\n\nexport class NativeTransformPort implements TransformPort {\n private readonly host: NativeInteractionHost;\n private readonly targetId: string;\n\n readonly setWorldPose?: (pose: PoseTuple) => void;\n readonly setEffect?: (effect: { scale?: number; emissive?: number }) => void;\n readonly beginHold: () => void;\n readonly endHold: (release: HoldRelease) => void;\n\n constructor(targetId: string, options: NativeTransformPortOptions = {}) {\n this.targetId = targetId;\n this.host = resolveHostSlice(\"interactions\", options.interactions);\n\n if (this.host.setWorldPose) {\n const host = this.host;\n this.setWorldPose = (pose) => host.setWorldPose!(targetId, copyPose(pose));\n }\n if (this.host.setEffect) {\n const host = this.host;\n this.setEffect = (effect) => host.setEffect!(targetId, effect);\n }\n if (typeof this.host.beginHold !== \"function\" || typeof this.host.endHold !== \"function\") {\n throw new Error(\n \"@realitycollective/native-interactions: the interactions slice must implement beginHold(targetId) and endHold(targetId, release). A pose-only grab calls both, and a host without them drops every release velocity. A host with no physics implements them and lets the object rest.\",\n );\n }\n const host = this.host;\n this.beginHold = () => host.beginHold(targetId);\n this.endHold = (release) =>\n host.endHold(targetId, {\n linearVelocity: copyVec3(release.linearVelocity),\n angularVelocity: copyVec3(release.angularVelocity),\n });\n }\n\n getWorldPose(): PoseTuple {\n return copyPose(this.host.getWorldPose(this.targetId));\n }\n\n getRestWorldPose(): PoseTuple {\n return copyPose(this.host.getRestWorldPose(this.targetId));\n }\n\n getLocalOffset(): Vec3Tuple {\n return copyVec3(this.host.getLocalOffset(this.targetId));\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.host.setLocalOffset(this.targetId, copyVec3(offset));\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.host.setLocalRotation(this.targetId, copyQuat(quaternion));\n }\n}\n"]}
1
+ {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativeTransformPort - the read/write surface of ONE interactable, over the\n * native host's `interactions` slice. The host serves every registered\n * object through one set of methods, so this port is a thin binding of one\n * `targetId` onto them - the same idea as `BabylonTransformPort` binding to\n * one node, but the object itself lives on the native side.\n *\n * `setWorldPose` and `setEffect` are optional on `TransformPort`, and are\n * only ever assigned here when the host itself carries the matching member,\n * so an app whose native host cannot follow a world pose, or has no effect\n * hook, sees a port without one rather than a method that silently no-ops.\n *\n * `beginHold` and `endHold` are always present. A pose-only grab calls them\n * on every grab and release, and a host that silently lacked them used to\n * drop every throw on the floor (the release velocity never reached it). A\n * host without them now fails here, at construction, naming both.\n *\n * With `options.physics` (the platform facility over the host's `physics`\n * slice, and the target's body id in it), the hold goes through the core\n * physics contract: `beginHold` suspends the body, `endHold` resumes it with\n * the release velocity, and `setWorldPose` teleports it with velocity\n * cleared while not held. The `interactions` slice's own `beginHold` and\n * `endHold` are then not called for this target. Without it, a target with\n * no body rests where it is released, as the `interactions` slice states.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { HoldRelease, PhysicsFacility, TransformPort } from \"@realitycollective/webxr-interactions\";\nimport {\n copyPose,\n copyQuat,\n copyVec3,\n resolveHostSlice,\n type NativeInteractionHost,\n} from \"./native-types.js\";\n\nexport interface NativeTransformPortOptions {\n /** The `interactions` slice. Omit to read `globalThis.__rcHost.interactions`. */\n interactions?: NativeInteractionHost;\n /** The target's physics body: the facility over the `physics` slice, and the body's id. */\n physics?: { facility: PhysicsFacility; bodyId: string };\n}\n\nexport class NativeTransformPort implements TransformPort {\n private readonly host: NativeInteractionHost;\n private readonly targetId: string;\n\n readonly setWorldPose?: (pose: PoseTuple) => void;\n readonly setEffect?: (effect: { scale?: number; emissive?: number }) => void;\n readonly beginHold: () => void;\n readonly endHold: (release: HoldRelease) => void;\n\n constructor(targetId: string, options: NativeTransformPortOptions = {}) {\n this.targetId = targetId;\n this.host = resolveHostSlice(\"interactions\", options.interactions);\n\n const physics = options.physics;\n if (physics) {\n // The facility owns the body's pose and velocity; the host's own\n // world-pose write, when it has one, keeps the drawn object in step.\n const host = this.host;\n this.setWorldPose = (pose) => {\n physics.facility.setBodyPose(physics.bodyId, pose);\n host.setWorldPose?.(targetId, copyPose(pose));\n };\n } else if (this.host.setWorldPose) {\n const host = this.host;\n this.setWorldPose = (pose) => host.setWorldPose!(targetId, copyPose(pose));\n }\n if (this.host.setEffect) {\n const host = this.host;\n this.setEffect = (effect) => host.setEffect!(targetId, effect);\n }\n if (typeof this.host.beginHold !== \"function\" || typeof this.host.endHold !== \"function\") {\n throw new Error(\n \"@realitycollective/native-interactions: the interactions slice must implement beginHold(targetId) and endHold(targetId, release). A pose-only grab calls both, and a host without them drops every release velocity. A host with no physics implements them and lets the object rest.\",\n );\n }\n const host = this.host;\n if (physics) {\n this.beginHold = () => physics.facility.suspend(physics.bodyId);\n this.endHold = (release) => physics.facility.resume(physics.bodyId, release);\n return;\n }\n this.beginHold = () => host.beginHold(targetId);\n this.endHold = (release) =>\n host.endHold(targetId, {\n linearVelocity: copyVec3(release.linearVelocity),\n angularVelocity: copyVec3(release.angularVelocity),\n });\n }\n\n getWorldPose(): PoseTuple {\n return copyPose(this.host.getWorldPose(this.targetId));\n }\n\n getRestWorldPose(): PoseTuple {\n return copyPose(this.host.getRestWorldPose(this.targetId));\n }\n\n getLocalOffset(): Vec3Tuple {\n return copyVec3(this.host.getLocalOffset(this.targetId));\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.host.setLocalOffset(this.targetId, copyVec3(offset));\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.host.setLocalRotation(this.targetId, copyQuat(quaternion));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/native-interactions",
3
- "version": "0.1.1-preview.2",
3
+ "version": "0.1.1-preview.3",
4
4
  "description": "Native host adapter for the Reality Collective Interaction Extensions - reads the `input` and `interactions` slices a native app (OpenXR, visionOS) installs on `globalThis.__rcHost`, or an injected fake, and maps them into the engine-free @realitycollective/webxr-interactions core. No engine dependency: assets and rendering stay in the native app. Re-exports the core.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -34,8 +34,8 @@
34
34
  "typecheck": "tsc -p tsconfig.json --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@realitycollective/webxr-input": "^0.1.4",
38
- "@realitycollective/webxr-interactions": "^0.1.1-preview.2"
37
+ "@realitycollective/webxr-input": "^0.1.7",
38
+ "@realitycollective/webxr-interactions": "^0.1.1-preview.3"
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",