@realitycollective/native-interactions 0.1.1-preview.1 → 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/host.d.ts CHANGED
@@ -14,13 +14,26 @@
14
14
  * app's own frame callback. Without it, call `update(dt)` yourself with
15
15
  * seconds.
16
16
  */
17
- import { InteractionRuntime, type DwellConfig, type InteractableDescriptor } from "@realitycollective/webxr-interactions";
17
+ import { InteractionRuntime, type DwellConfig, type InteractableDescriptor, type NearPointerOptions, type PhysicsBodySpec, type PhysicsShapeSpec } from "@realitycollective/webxr-interactions";
18
18
  import { NativeHitTester, type NativeHitTesterOptions } from "./hit-tester.js";
19
+ import { NativePhysicsFacility } from "./physics-facility.js";
19
20
  import { NativeInputProvider, type NativeInputProviderOptions } from "./provider.js";
20
21
  import { NativeTransformPort } from "./transform-port.js";
21
- import { type NativeFrameSource } from "./native-types.js";
22
+ import { type NativeFrameSource, type NativePhysicsHost } from "./native-types.js";
22
23
  export interface NativeInteractionsOptions extends NativeInputProviderOptions, NativeHitTesterOptions {
23
24
  dwellDefaults?: DwellConfig;
25
+ /** The near-pointer distances (touch hover, touch press, grab radius). Defaults are IWSDK 1.0.0's. */
26
+ nearPointer?: Partial<NearPointerOptions>;
27
+ /**
28
+ * The `physics` slice: the host's default engine (Jolt on Quest and
29
+ * Android, RealityKit on visionOS) behind the core contract, or an app's
30
+ * own `PhysicsFacility` in its place. Omit to read
31
+ * `globalThis.__rcHost.physics`. Without either, `physics` is null and no
32
+ * target can carry a body; the host conformance kit fails such a host.
33
+ */
34
+ physics?: NativePhysicsHost;
35
+ /** Step the physics from `update(dt)`. Default true; false when the host steps its engine itself. */
36
+ stepPhysics?: boolean;
24
37
  /**
25
38
  * Drive `update(dt)` from the native app's frame callback. Default false -
26
39
  * the app calls `update` from its own loop.
@@ -29,16 +42,43 @@ export interface NativeInteractionsOptions extends NativeInputProviderOptions, N
29
42
  /** Where frames come from for `attachToHost`. Omit to use `globalThis.__rcHost`. */
30
43
  frames?: NativeFrameSource;
31
44
  }
45
+ /** Per-registration options, as IWSDK's `register` takes them. */
46
+ export interface NativeRegisterOptions {
47
+ /**
48
+ * The radius, in metres, the host hit-tests this target with. Default
49
+ * 0.1, the radius IWSDK gives a target that declares none.
50
+ */
51
+ targetRadius?: number;
52
+ /**
53
+ * Give the target a body in the `physics` slice at its current world pose
54
+ * (core defaults: dynamic, no damping, gravity factor 1). A target whose
55
+ * body the host already holds (`physics.hasBody(id)`) needs neither this
56
+ * nor `shape`: its port is bound to that body as it is.
57
+ */
58
+ body?: PhysicsBodySpec;
59
+ /** The body's collider (default `"auto"`, the host's collider for the object). Implies `body`. */
60
+ shape?: PhysicsShapeSpec;
61
+ }
32
62
  export declare class NativeInteractions {
33
63
  readonly runtime: InteractionRuntime;
34
64
  readonly provider: NativeInputProvider;
35
65
  readonly hitTester: NativeHitTester;
66
+ /** The platform's physics over the `physics` slice, or null when the host installs none. */
67
+ readonly physics: NativePhysicsFacility | null;
68
+ private readonly stepPhysics;
36
69
  private readonly ports;
70
+ private readonly ownedBodies;
37
71
  private readonly options;
72
+ private readonly interactionsHost;
38
73
  private detachHost;
74
+ private readonly unsubscribeVisuals;
39
75
  constructor(options?: NativeInteractionsOptions);
40
- /** Register an interactable. The native app knows it by `descriptor.id`. */
41
- register(descriptor: InteractableDescriptor): NativeTransformPort;
76
+ /**
77
+ * Register an interactable. The native app knows it by `descriptor.id`,
78
+ * and is told the radius to hit-test it with: `options.targetRadius`, or
79
+ * 0.1 m, so a bare target is a 10 cm sphere as on IWSDK.
80
+ */
81
+ register(descriptor: InteractableDescriptor, options?: NativeRegisterOptions): NativeTransformPort;
42
82
  unregister(id: string): void;
43
83
  getPort(id: string): NativeTransformPort | undefined;
44
84
  update(dt: number): void;
package/dist/host.js CHANGED
@@ -16,33 +16,73 @@
16
16
  */
17
17
  import { InteractionRuntime, } from "@realitycollective/webxr-interactions";
18
18
  import { NativeHitTester } from "./hit-tester.js";
19
+ import { NativePhysicsFacility } from "./physics-facility.js";
19
20
  import { NativeInputProvider } from "./provider.js";
20
21
  import { NativeTransformPort } from "./transform-port.js";
21
- import { installedHost } from "./native-types.js";
22
+ import { findHostSlice, installedHost, resolveHostSlice, } from "./native-types.js";
23
+ /** The radius a target is hit-tested with when it declares none: IWSDK's `targetRadius ?? 0.1`. */
24
+ const DEFAULT_TARGET_RADIUS = 0.1;
22
25
  /** Longest frame the host-attached loop will report, in seconds. */
23
26
  const MAX_FRAME_SECONDS = 0.1;
24
27
  export class NativeInteractions {
25
28
  runtime;
26
29
  provider;
27
30
  hitTester;
31
+ /** The platform's physics over the `physics` slice, or null when the host installs none. */
32
+ physics;
33
+ stepPhysics;
28
34
  ports = new Map();
35
+ ownedBodies = new Set();
29
36
  options;
37
+ interactionsHost;
30
38
  detachHost = null;
39
+ unsubscribeVisuals;
31
40
  constructor(options = {}) {
32
41
  this.options = options;
42
+ this.interactionsHost = resolveHostSlice("interactions", options.interactions);
33
43
  this.provider = new NativeInputProvider(options);
34
44
  this.hitTester = new NativeHitTester(options);
35
45
  this.runtime = new InteractionRuntime({
36
46
  provider: this.provider,
37
47
  hitTester: this.hitTester,
38
48
  ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),
49
+ ...(options.nearPointer ? { nearPointer: options.nearPointer } : {}),
50
+ });
51
+ const physicsHost = findHostSlice("physics", options.physics);
52
+ this.physics = physicsHost ? new NativePhysicsFacility({ physics: physicsHost }) : null;
53
+ this.stepPhysics = options.stepPhysics ?? true;
54
+ // The host is handed the ray and cursor decision every frame, per source
55
+ // (`NativeInputHost.applyPointerVisuals`); it draws exactly that.
56
+ const input = resolveHostSlice("input", options.input);
57
+ this.unsubscribeVisuals = this.runtime.onPointerVisuals((visuals) => {
58
+ if (!input.applyPointerVisuals)
59
+ return;
60
+ for (const entry of visuals)
61
+ input.applyPointerVisuals(entry.sourceId, { ...entry });
39
62
  });
40
63
  if (options.attachToHost)
41
64
  this.attachToHost(options);
42
65
  }
43
- /** Register an interactable. The native app knows it by `descriptor.id`. */
44
- register(descriptor) {
45
- const port = new NativeTransformPort(descriptor.id, this.options.interactions ? { interactions: this.options.interactions } : {});
66
+ /**
67
+ * Register an interactable. The native app knows it by `descriptor.id`,
68
+ * and is told the radius to hit-test it with: `options.targetRadius`, or
69
+ * 0.1 m, so a bare target is a 10 cm sphere as on IWSDK.
70
+ */
71
+ register(descriptor, options = {}) {
72
+ const id = descriptor.id;
73
+ if (options.body || options.shape) {
74
+ if (!this.physics) {
75
+ throw new Error(`@realitycollective/native-interactions: "${id}" asks for a physics body but no physics slice is installed; the host installs globalThis.__rcHost.physics, or pass \`physics\` to createNativeInteractions`);
76
+ }
77
+ this.physics.addBody(id, this.interactionsHost.getWorldPose(id), options.body, options.shape);
78
+ this.ownedBodies.add(id);
79
+ }
80
+ const bound = this.physics && this.physics.hasBody(id) ? this.physics : null;
81
+ const port = new NativeTransformPort(id, {
82
+ ...(this.options.interactions ? { interactions: this.options.interactions } : {}),
83
+ ...(bound ? { physics: { facility: bound, bodyId: id } } : {}),
84
+ });
85
+ this.interactionsHost.setTargetRadius(descriptor.id, options.targetRadius ?? DEFAULT_TARGET_RADIUS);
46
86
  this.ports.set(descriptor.id, port);
47
87
  this.runtime.registerInteractable(descriptor, { transform: port });
48
88
  return port;
@@ -50,17 +90,25 @@ export class NativeInteractions {
50
90
  unregister(id) {
51
91
  this.runtime.unregisterInteractable(id);
52
92
  this.ports.delete(id);
93
+ if (this.ownedBodies.delete(id))
94
+ this.physics?.removeBody(id);
53
95
  }
54
96
  getPort(id) {
55
97
  return this.ports.get(id);
56
98
  }
57
99
  update(dt) {
100
+ // The provider's eye-gaze filter and grace integrate over the same step.
101
+ this.provider.setFrameDelta(dt);
58
102
  this.runtime.update(dt);
103
+ if (this.stepPhysics)
104
+ this.physics?.step(dt);
59
105
  }
60
106
  dispose() {
61
107
  this.detachHost?.();
62
108
  this.detachHost = null;
109
+ this.unsubscribeVisuals();
63
110
  this.runtime.dispose();
111
+ this.provider.dispose();
64
112
  }
65
113
  attachToHost(options) {
66
114
  const frames = options.frames ?? installedHost();
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * createNativeInteractions - one-call setup for the native adapter, with the\n * same shape as the Babylon, three.js, IWSDK and XR Blocks setups.\n *\n * ```ts\n * const interactions = createNativeInteractions({ attachToHost: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] });\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * The native app owns the scene, so an interactable is registered by id\n * alone: the app answers hit queries and pose reads for that id through the\n * `interactions` slice. With `attachToHost` the loop drives itself from the\n * app's own frame callback. Without it, call `update(dt)` yourself with\n * seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n} from \"@realitycollective/webxr-interactions\";\nimport { NativeHitTester, type NativeHitTesterOptions } from \"./hit-tester.js\";\nimport { NativeInputProvider, type NativeInputProviderOptions } from \"./provider.js\";\nimport { NativeTransformPort } from \"./transform-port.js\";\nimport { installedHost, type NativeFrameSource } from \"./native-types.js\";\n\nexport interface NativeInteractionsOptions\n extends NativeInputProviderOptions,\n NativeHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /**\n * Drive `update(dt)` from the native app's frame callback. Default false -\n * the app calls `update` from its own loop.\n */\n attachToHost?: boolean;\n /** Where frames come from for `attachToHost`. Omit to use `globalThis.__rcHost`. */\n frames?: NativeFrameSource;\n}\n\n/** Longest frame the host-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class NativeInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: NativeInputProvider;\n readonly hitTester: NativeHitTester;\n private readonly ports = new Map<string, NativeTransformPort>();\n private readonly options: NativeInteractionsOptions;\n private detachHost: (() => void) | null = null;\n\n constructor(options: NativeInteractionsOptions = {}) {\n this.options = options;\n this.provider = new NativeInputProvider(options);\n this.hitTester = new NativeHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n });\n if (options.attachToHost) this.attachToHost(options);\n }\n\n /** Register an interactable. The native app knows it by `descriptor.id`. */\n register(descriptor: InteractableDescriptor): NativeTransformPort {\n const port = new NativeTransformPort(\n descriptor.id,\n this.options.interactions ? { interactions: this.options.interactions } : {},\n );\n this.ports.set(descriptor.id, port);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.ports.delete(id);\n }\n\n getPort(id: string): NativeTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n this.runtime.update(dt);\n }\n\n dispose(): void {\n this.detachHost?.();\n this.detachHost = null;\n this.runtime.dispose();\n }\n\n private attachToHost(options: NativeInteractionsOptions): void {\n const frames = options.frames ?? installedHost();\n if (!frames?.onFrame) {\n throw new Error(\n \"@realitycollective/native-interactions: attachToHost needs a frame source, and globalThis.__rcHost.onFrame is not installed. Pass `frames`, or call update(dt) yourself.\",\n );\n }\n // The app reports the frame delta in seconds. A long frame is clamped so\n // behaviours integrate over a sane step rather than jumping.\n this.detachHost = frames.onFrame((_timestampMs, deltaS) => {\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, deltaS)));\n });\n }\n}\n\nexport function createNativeInteractions(\n options: NativeInteractionsOptions = {},\n): NativeInteractions {\n return new NativeInteractions(options);\n}\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,kBAAkB,GAMnB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,eAAe,EAA+B,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAmC,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,GAIjB,MAAM,mBAAmB,CAAC;AA6C3B,mGAAmG;AACnG,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,oEAAoE;AACpE,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,OAAO,kBAAkB;IACpB,OAAO,CAAqB;IAC5B,QAAQ,CAAsB;IAC9B,SAAS,CAAkB;IACpC,4FAA4F;IACnF,OAAO,CAA+B;IAC9B,WAAW,CAAU;IACrB,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC/C,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,OAAO,CAA4B;IACnC,gBAAgB,CAAwB;IACjD,UAAU,GAAwB,IAAI,CAAC;IAC9B,kBAAkB,CAAa;IAEhD,YAAY,UAAqC,EAAE;QACjD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,GAAG,IAAI,kBAAkB,CAAC;YACpC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,qBAAqB,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACxF,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;QAC/C,yEAAyE;QACzE,kEAAkE;QAClE,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE,EAAE;YAClE,IAAI,CAAC,KAAK,CAAC,mBAAmB;gBAAE,OAAO;YACvC,KAAK,MAAM,KAAK,IAAI,OAAO;gBAAE,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACvF,CAAC,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,YAAY;YAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,UAAkC,EAAE,UAAiC,EAAE;QAC9E,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,4CAA4C,EAAE,6JAA6J,CAC5M,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9F,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7E,MAAM,IAAI,GAAG,IAAI,mBAAmB,CAAC,EAAE,EAAE;YACvC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,YAAY,IAAI,qBAAqB,CAAC,CAAC;QACpG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,EAAU;QACf,yEAAyE;QACzE,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAEO,YAAY,CAAC,OAAkC;QACrD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,0KAA0K,CAC3K,CAAC;QACJ,CAAC;QACD,yEAAyE;QACzE,6DAA6D;QAC7D,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE;YACxD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,UAAU,wBAAwB,CACtC,UAAqC,EAAE;IAEvC,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACzC,CAAC","sourcesContent":["/**\n * createNativeInteractions - one-call setup for the native adapter, with the\n * same shape as the Babylon, three.js, IWSDK and XR Blocks setups.\n *\n * ```ts\n * const interactions = createNativeInteractions({ attachToHost: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] });\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * The native app owns the scene, so an interactable is registered by id\n * alone: the app answers hit queries and pose reads for that id through the\n * `interactions` slice. With `attachToHost` the loop drives itself from the\n * app's own frame callback. Without it, call `update(dt)` yourself with\n * seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n type NearPointerOptions,\n type PhysicsBodySpec,\n type PhysicsShapeSpec,\n} from \"@realitycollective/webxr-interactions\";\nimport { NativeHitTester, type NativeHitTesterOptions } from \"./hit-tester.js\";\nimport { NativePhysicsFacility } from \"./physics-facility.js\";\nimport { NativeInputProvider, type NativeInputProviderOptions } from \"./provider.js\";\nimport { NativeTransformPort } from \"./transform-port.js\";\nimport {\n findHostSlice,\n installedHost,\n resolveHostSlice,\n type NativeFrameSource,\n type NativeInteractionHost,\n type NativePhysicsHost,\n} from \"./native-types.js\";\n\nexport interface NativeInteractionsOptions\n extends NativeInputProviderOptions,\n NativeHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /** The near-pointer distances (touch hover, touch press, grab radius). Defaults are IWSDK 1.0.0's. */\n nearPointer?: Partial<NearPointerOptions>;\n /**\n * The `physics` slice: the host's default engine (Jolt on Quest and\n * Android, RealityKit on visionOS) behind the core contract, or an app's\n * own `PhysicsFacility` in its place. Omit to read\n * `globalThis.__rcHost.physics`. Without either, `physics` is null and no\n * target can carry a body; the host conformance kit fails such a host.\n */\n physics?: NativePhysicsHost;\n /** Step the physics from `update(dt)`. Default true; false when the host steps its engine itself. */\n stepPhysics?: boolean;\n /**\n * Drive `update(dt)` from the native app's frame callback. Default false -\n * the app calls `update` from its own loop.\n */\n attachToHost?: boolean;\n /** Where frames come from for `attachToHost`. Omit to use `globalThis.__rcHost`. */\n frames?: NativeFrameSource;\n}\n\n/** Per-registration options, as IWSDK's `register` takes them. */\nexport interface NativeRegisterOptions {\n /**\n * The radius, in metres, the host hit-tests this target with. Default\n * 0.1, the radius IWSDK gives a target that declares none.\n */\n targetRadius?: number;\n /**\n * Give the target a body in the `physics` slice at its current world pose\n * (core defaults: dynamic, no damping, gravity factor 1). A target whose\n * body the host already holds (`physics.hasBody(id)`) needs neither this\n * nor `shape`: its port is bound to that body as it is.\n */\n body?: PhysicsBodySpec;\n /** The body's collider (default `\"auto\"`, the host's collider for the object). Implies `body`. */\n shape?: PhysicsShapeSpec;\n}\n\n/** The radius a target is hit-tested with when it declares none: IWSDK's `targetRadius ?? 0.1`. */\nconst DEFAULT_TARGET_RADIUS = 0.1;\n\n/** Longest frame the host-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class NativeInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: NativeInputProvider;\n readonly hitTester: NativeHitTester;\n /** The platform's physics over the `physics` slice, or null when the host installs none. */\n readonly physics: NativePhysicsFacility | null;\n private readonly stepPhysics: boolean;\n private readonly ports = new Map<string, NativeTransformPort>();\n private readonly ownedBodies = new Set<string>();\n private readonly options: NativeInteractionsOptions;\n private readonly interactionsHost: NativeInteractionHost;\n private detachHost: (() => void) | null = null;\n private readonly unsubscribeVisuals: () => void;\n\n constructor(options: NativeInteractionsOptions = {}) {\n this.options = options;\n this.interactionsHost = resolveHostSlice(\"interactions\", options.interactions);\n this.provider = new NativeInputProvider(options);\n this.hitTester = new NativeHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n ...(options.nearPointer ? { nearPointer: options.nearPointer } : {}),\n });\n const physicsHost = findHostSlice(\"physics\", options.physics);\n this.physics = physicsHost ? new NativePhysicsFacility({ physics: physicsHost }) : null;\n this.stepPhysics = options.stepPhysics ?? true;\n // The host is handed the ray and cursor decision every frame, per source\n // (`NativeInputHost.applyPointerVisuals`); it draws exactly that.\n const input = resolveHostSlice(\"input\", options.input);\n this.unsubscribeVisuals = this.runtime.onPointerVisuals((visuals) => {\n if (!input.applyPointerVisuals) return;\n for (const entry of visuals) input.applyPointerVisuals(entry.sourceId, { ...entry });\n });\n if (options.attachToHost) this.attachToHost(options);\n }\n\n /**\n * Register an interactable. The native app knows it by `descriptor.id`,\n * and is told the radius to hit-test it with: `options.targetRadius`, or\n * 0.1 m, so a bare target is a 10 cm sphere as on IWSDK.\n */\n register(descriptor: InteractableDescriptor, options: NativeRegisterOptions = {}): NativeTransformPort {\n const id = descriptor.id;\n if (options.body || options.shape) {\n if (!this.physics) {\n throw new Error(\n `@realitycollective/native-interactions: \"${id}\" asks for a physics body but no physics slice is installed; the host installs globalThis.__rcHost.physics, or pass \\`physics\\` to createNativeInteractions`,\n );\n }\n this.physics.addBody(id, this.interactionsHost.getWorldPose(id), options.body, options.shape);\n this.ownedBodies.add(id);\n }\n const bound = this.physics && this.physics.hasBody(id) ? this.physics : null;\n const port = new NativeTransformPort(id, {\n ...(this.options.interactions ? { interactions: this.options.interactions } : {}),\n ...(bound ? { physics: { facility: bound, bodyId: id } } : {}),\n });\n this.interactionsHost.setTargetRadius(descriptor.id, options.targetRadius ?? DEFAULT_TARGET_RADIUS);\n this.ports.set(descriptor.id, port);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.ports.delete(id);\n if (this.ownedBodies.delete(id)) this.physics?.removeBody(id);\n }\n\n getPort(id: string): NativeTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n // The provider's eye-gaze filter and grace integrate over the same step.\n this.provider.setFrameDelta(dt);\n this.runtime.update(dt);\n if (this.stepPhysics) this.physics?.step(dt);\n }\n\n dispose(): void {\n this.detachHost?.();\n this.detachHost = null;\n this.unsubscribeVisuals();\n this.runtime.dispose();\n this.provider.dispose();\n }\n\n private attachToHost(options: NativeInteractionsOptions): void {\n const frames = options.frames ?? installedHost();\n if (!frames?.onFrame) {\n throw new Error(\n \"@realitycollective/native-interactions: attachToHost needs a frame source, and globalThis.__rcHost.onFrame is not installed. Pass `frames`, or call update(dt) yourself.\",\n );\n }\n // The app reports the frame delta in seconds. A long frame is clamped so\n // behaviours integrate over a sane step rather than jumping.\n this.detachHost = frames.onFrame((_timestampMs, deltaS) => {\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, deltaS)));\n });\n }\n}\n\nexport function createNativeInteractions(\n options: NativeInteractionsOptions = {},\n): NativeInteractions {\n return new NativeInteractions(options);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,12 +3,17 @@
3
3
  * Babylon, three.js, IWSDK and XR Blocks adapters. The core is re-exported;
4
4
  * the provider, hit tester and transform port implement its contracts; the
5
5
  * setup entry point wires them together; and the slice types describe what
6
- * the native app installs on `globalThis.__rcHost`. Slice reading and tuple
7
- * copying stay internal.
6
+ * the native app installs on `globalThis.__rcHost`. One addition the web
7
+ * adapters do not need: the host conformance kit a native app runs on its
8
+ * device against its real slices. Slice reading and tuple copying stay
9
+ * internal.
8
10
  */
9
11
  export * from "@realitycollective/webxr-interactions";
10
- export type { NativeFrameSource, NativeHit, NativeHostSlices, NativeInputHost, NativeInteractionHost, } from "./native-types.js";
12
+ export type { NativeFrameSource, NativeHit, NativeHostSlices, NativeInputFacts, NativeInputHost, NativeInteractionHost, NativeInteractionsTestHost, NativePhysicsHost, NativePresenceShown, } from "./native-types.js";
11
13
  export * from "./provider.js";
12
14
  export * from "./hit-tester.js";
13
15
  export * from "./transform-port.js";
16
+ export * from "./physics-facility.js";
14
17
  export * from "./host.js";
18
+ export { nativeInteractionsHostConformanceCases } from "./conformance.js";
19
+ export type { NativeInteractionsHostConformanceCase, NativeInteractionsHostConformanceSetup, } from "./conformance.js";
package/dist/index.js CHANGED
@@ -3,12 +3,16 @@
3
3
  * Babylon, three.js, IWSDK and XR Blocks adapters. The core is re-exported;
4
4
  * the provider, hit tester and transform port implement its contracts; the
5
5
  * setup entry point wires them together; and the slice types describe what
6
- * the native app installs on `globalThis.__rcHost`. Slice reading and tuple
7
- * copying stay internal.
6
+ * the native app installs on `globalThis.__rcHost`. One addition the web
7
+ * adapters do not need: the host conformance kit a native app runs on its
8
+ * device against its real slices. Slice reading and tuple copying stay
9
+ * internal.
8
10
  */
9
11
  export * from "@realitycollective/webxr-interactions";
10
12
  export * from "./provider.js";
11
13
  export * from "./hit-tester.js";
12
14
  export * from "./transform-port.js";
15
+ export * from "./physics-facility.js";
13
16
  export * from "./host.js";
17
+ export { nativeInteractionsHostConformanceCases } from "./conformance.js";
14
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,uCAAuC,CAAC;AAQtD,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC","sourcesContent":["/**\n * Public entry point of the native adapter: the same kinds of export as the\n * Babylon, three.js, IWSDK and XR Blocks adapters. The core is re-exported;\n * the provider, hit tester and transform port implement its contracts; the\n * setup entry point wires them together; and the slice types describe what\n * the native app installs on `globalThis.__rcHost`. Slice reading and tuple\n * copying stay internal.\n */\nexport * from \"@realitycollective/webxr-interactions\";\nexport type {\n NativeFrameSource,\n NativeHit,\n NativeHostSlices,\n NativeInputHost,\n NativeInteractionHost,\n} from \"./native-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./host.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,uCAAuC,CAAC;AAYtD,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,sCAAsC,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Public entry point of the native adapter: the same kinds of export as the\n * Babylon, three.js, IWSDK and XR Blocks adapters. The core is re-exported;\n * the provider, hit tester and transform port implement its contracts; the\n * setup entry point wires them together; and the slice types describe what\n * the native app installs on `globalThis.__rcHost`. One addition the web\n * adapters do not need: the host conformance kit a native app runs on its\n * device against its real slices. Slice reading and tuple copying stay\n * internal.\n */\nexport * from \"@realitycollective/webxr-interactions\";\nexport type {\n NativeFrameSource,\n NativeHit,\n NativeHostSlices,\n NativeInputFacts,\n NativeInputHost,\n NativeInteractionHost,\n NativeInteractionsTestHost,\n NativePhysicsHost,\n NativePresenceShown,\n} from \"./native-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./physics-facility.js\";\nexport * from \"./host.js\";\nexport { nativeInteractionsHostConformanceCases } from \"./conformance.js\";\nexport type {\n NativeInteractionsHostConformanceCase,\n NativeInteractionsHostConformanceSetup,\n} from \"./conformance.js\";\n"]}