@realitycollective/native-interactions 0.1.1-preview.0 → 0.1.1-preview.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.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -1
- package/dist/native-types.d.ts +11 -0
- package/dist/native-types.js.map +1 -1
- package/dist/transform-port.d.ts +3 -1
- package/dist/transform-port.js +13 -0
- package/dist/transform-port.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
10
10
|
|
|
11
11
|
- `@realitycollective/webxr-interactions` - shared conformance suites for `HitTester` and `TransformPort`, `hitTesterContractCases()` and `transformPortContractCases()`, alongside the existing `inputProviderContractCases()` from `@realitycollective/webxr-input`. Runner-free data, the same pattern as WebXR-UIExtensions' `windowHostContractCases()`: each case throws a plain `Error` naming what a platform's ray/proximity query or transform read/write broke, and asserts only what `ports.ts` documents - a ray or proximity query answers null or an id with a positive distance and a point near the query, the nearer of two targets wins, an offset round-trips, `getWorldPose()` is finite with a unit quaternion, `getRestWorldPose()` is the registration pose and stays put, `getWorldPose()` follows `setLocalOffset`, `setLocalRotation` and `setWorldPose` from that rest frame, every returned tuple is a fresh value the caller owns and no tuple passed in is kept, and the optional `setEffect` accepts its shape without throwing. Each platform's subject starts away from the origin and turned, so a port that ignores its rest frame fails. Run against all five platforms in `packages/iwsdk-interactions/test/port-parity.test.ts`, next to the provider parity suite; every platform passes every case.
|
|
12
12
|
- `@realitycollective/native-interactions` - a native host adapter, the fifth platform. It reads the `input` and `interactions` slices a native app (OpenXR on Quest, CompositorServices on visionOS) installs on `globalThis.__rcHost`, or hands in directly, and maps them onto `InputProvider`, `HitTester` and `TransformPort` with no engine dependency at all - assets and rendering stay in the native app, and only tuples and target ids cross the boundary. Every snapshot, hit and pose read across that boundary is copied, so a host that reuses its own buffers cannot reach an object the app is still holding, and optional members (`getHeadPose`, `sampleHints`, `pulse`, `setPresenceVisible`, `setPresenceModality`, `setWorldPose`, `setEffect`) are present only when the host slice itself carries them. `createNativeInteractions()` is the one-call setup, with the same shape as the other adapters' setups; `attachToHost` drives updates from the app's frame callback. The public surface matches the other adapters, and slice reading and tuple copying stay internal. Covered headlessly by an in-memory fake of both slices, including the shared `InputProvider` contract suite.
|
|
13
|
+
- `@realitycollective/webxr-interactions` - "held pose" for a grabbed object with physics: `TransformPort` gains two optional members, `beginHold()` and `endHold(release: HoldRelease)`, and the `HoldRelease` type they take (`linearVelocity`/`angularVelocity`, metres and radians per second, world space). A host whose object has physics uses them to give a `poseOnly` grab the same three behaviours native grab fulfilment already had: held (physics suspended, the object follows `setWorldPose` exactly, gravity and collisions do nothing), released (physics resumes with the grabbing hand's velocity, so a throw carries through), and reset (a `setWorldPose` while not held teleports and clears velocity - "back to the tee"). `setWorldPose`'s own doc states this split. A platform with no physics grows neither member and behaves exactly as before; a platform that fulfils grabs natively is expected to show the same three behaviours through its own engine, and the runtime never calls `beginHold`/`endHold` for one. `InteractorInfo` gains optional `linearVelocity`/`angularVelocity`, the grabbing source's tracked velocity `GrabBehaviour` reads for the release. `transformPortContractCases()` gains three cases - Held, Released, Reset - that run only when the contract subject carries an optional `physics` driver, and skip otherwise.
|
|
14
|
+
- `@realitycollective/native-interactions` - the `interactions` slice gains the matching optional `beginHold(targetId)`/`endHold(targetId, release)`, keyed by target id like every other member. `NativeTransformPort` grows the two only when the host slice carries them, the same rule as `setWorldPose`/`setEffect`. The README states the rule for a native app that fulfils grabs itself: it is responsible for showing held/released/reset through its own engine, and `beginHold`/`endHold` are never called for a grab that app already owns.
|
|
15
|
+
- `@realitycollective/babylon-interactions` - `BabylonTransformPort` implements the held pose over Physics V2's `PhysicsBody`, through a new `physicsMotionTypes` construction option (Babylon's own `PhysicsMotionType.ANIMATED`/`.DYNAMIC` values - this package holds no Babylon values, only shapes). A held node switches to `ANIMATED`; `endHold` switches it back to `DYNAMIC` with the release velocity. A node with no `physicsBody`, or a construction with no `physicsMotionTypes`, grows neither member. Written from the Babylon Physics V2 documentation - this package has no `@babylonjs/core`/`@babylonjs/havok` dependency to verify it against - and not yet exercised against a live scene.
|
|
16
|
+
- `@realitycollective/iwsdk-interactions` - `IWSDKTransformPort` takes an optional `physics: IWSDKPhysicsBinding` (`beginHold`/`endHold`/`teleport`); `IWSDKInteractions.register()` builds one from `@iwsdk/core`'s `PhysicsBody`/`PhysicsShape`/`PhysicsManipulation` components and `PhysicsSystem.setBodyTransform` whenever the entity has a physics body. A hold removes `PhysicsBody` (keeping `PhysicsShape`), which tears down the entity's Havok body so gravity, collisions and the physics-to-object3D sync all stop while the port's own `setWorldPose` writes take over; release re-adds `PhysicsBody` at the state it had and, for a nonzero velocity, a `PhysicsManipulation`. Never the `Grabbed` tag `GrabSystem` owns - its own doc says not to add or remove it by hand - and never the private Havok handle `PhysicsSystem` keeps to itself.
|
|
13
17
|
|
|
14
18
|
### Changed
|
|
15
19
|
|
|
@@ -21,6 +25,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
21
25
|
- `@realitycollective/threejs-interactions`, `@realitycollective/iwsdk-interactions` and `@realitycollective/xrblocks-interactions` - `getWorldPose()` returned the pose captured at registration, not where the object was. Toss scoring therefore read a thrown ball at its registered position until the app called `recaptureRest()`, and a second grab of an object moved without a recapture took its hold offset from the old position. `getWorldPose()` now returns the live pose, as the Babylon.js port already did.
|
|
22
26
|
- `@realitycollective/webxr-interactions` - `InteractableDescriptor.pokeRadius` had no effect. The value was stored on the registration and documented as the poke trigger radius, but the proximity query always used the 5 cm default, so an interactable asking for a wider or narrower poke got the default. The runtime now queries once per source at the largest radius any enabled interactable registered, and accepts the nearest hit only when it lies inside that interactable's own radius. One query per source is kept deliberately; the trade is that a farther interactable with a larger radius is not found behind a nearer one with a smaller radius, because the hit tester returns only the nearest. Covered by a runtime test that failed before the change.
|
|
23
27
|
- CI - the staging deploy published under `--branch=pr-<number>` while the `-test` Pages project's production branch is `staging`, so only `pr-<n>.webxr-interactions-test.pages.dev` aliases were ever created and the project's root URL was a 404. It now deploys as `staging` on every pull request and on every push to `development`, so `webxr-interactions-test.pages.dev` serves the newest preview build, the same arrangement WebXR-UIExtensions already had.
|
|
28
|
+
- `@realitycollective/webxr-interactions` - a `poseOnly` grab on a host with physics fought its own physics solver. `GrabBehaviour` wrote `setWorldPose` every frame while held and told the port nothing at grab end; the solver kept simulating over the top of that write, physics never resumed on release, and a throw dropped instead of carrying velocity. `GrabBehaviour` now calls the port's `beginHold()` on grab start and `endHold(release)` on grab end, with the grabbing source's tracked velocity (zero on a synthesized release - source lost, target unregistered, runtime disposed, or the interactable disabled mid-hold - which carries none). Every way a grab can end now calls `endHold` exactly once, including `InteractionRuntime.dispose()`, which previously left a held grab's physics suspended forever with no `endHold` at all.
|
|
24
29
|
|
|
25
30
|
## [0.1.0] - 2026-09-17
|
|
26
31
|
|
package/README.md
CHANGED
|
@@ -49,7 +49,8 @@ A missing slice - neither passed in nor found on `globalThis.__rcHost` - throws
|
|
|
49
49
|
## Things to know
|
|
50
50
|
|
|
51
51
|
- **Copies, not references.** Every snapshot `sample()` returns, and every tuple inside it, is copied before it leaves `NativeInputProvider`, so a host that reuses its own sample buffers cannot change a snapshot your app is still holding. The same applies to hit results and to poses read through `NativeTransformPort`.
|
|
52
|
-
- **Optional members are honest.** `getHeadPose`, `sampleHints`, `pulse`, `setPresenceVisible` and `setPresenceModality` on `NativeInputProvider`, and `setWorldPose`/`setEffect` on `NativeTransformPort`, are only ever present when the host slice itself carries the matching member - never a method that silently no-ops.
|
|
52
|
+
- **Optional members are honest.** `getHeadPose`, `sampleHints`, `pulse`, `setPresenceVisible` and `setPresenceModality` on `NativeInputProvider`, and `setWorldPose`/`setEffect`/`beginHold`/`endHold` on `NativeTransformPort`, are only ever present when the host slice itself carries the matching member - never a method that silently no-ops.
|
|
53
|
+
- **Held pose.** `beginHold(targetId)`/`endHold(targetId, release)` suspend and resume physics for one target, the same rule `TransformPort.beginHold`/`endHold` document in `@realitycollective/webxr-interactions`: while held, the object follows `setWorldPose` writes exactly and gravity/collisions stop moving it; on release, physics resumes with `release`'s velocities so a throw carries through; a `setWorldPose` while NOT held teleports and clears velocity. The core's own `poseOnly` grab calls these for you when your `interactions` slice carries them - add them to your host only when it can drive its own physics this way, and leave them off when it cannot, the same as any other optional member. **A native app that fulfils grabs itself**, through its own engine rather than this package's `poseOnly` grab, is responsible for showing the same three behaviours (held / released / reset) on its own: `beginHold`/`endHold` are never called for a grab your engine already owns.
|
|
53
54
|
- **One port per interactable.** `TransformPort` is per object; the native host is one object serving every registered interactable, so `NativeTransformPort` binds one `targetId` onto it, the same idea as an engine adapter's port binding to one scene node.
|
|
54
55
|
- **`NativeHit.targetId`** is renamed to the core's `interactableId` on the way through `NativeHitTester` - the native host names the thing it hit, the core names the thing it manages.
|
|
55
56
|
|
package/dist/native-types.d.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* native side.
|
|
21
21
|
*/
|
|
22
22
|
import type { Handedness, HeadPose, InputCapabilities, InputHitHint, InputSourceSnapshot, PoseTuple, PresenceModality, QuatTuple, RayTuple, Unsubscribe, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
23
|
+
import type { HoldRelease } from "@realitycollective/webxr-interactions";
|
|
23
24
|
/** The native host's `input` slice. Mirrors `InputProvider` member for member. */
|
|
24
25
|
export interface NativeInputHost {
|
|
25
26
|
getCapabilities(): InputCapabilities;
|
|
@@ -62,6 +63,16 @@ export interface NativeInteractionHost {
|
|
|
62
63
|
scale?: number;
|
|
63
64
|
emissive?: number;
|
|
64
65
|
}): void;
|
|
66
|
+
/**
|
|
67
|
+
* Suspend physics for `targetId` - see `TransformPort.beginHold` in
|
|
68
|
+
* `@realitycollective/webxr-interactions`. Present only on a host that
|
|
69
|
+
* can drive its own physics this way; a host with no physics, or a
|
|
70
|
+
* native app that fulfils grabs itself through its own engine, never
|
|
71
|
+
* needs it - see this package's README.
|
|
72
|
+
*/
|
|
73
|
+
beginHold?(targetId: string): void;
|
|
74
|
+
/** Resume physics for `targetId` with `release` as its new velocity. */
|
|
75
|
+
endHold?(targetId: string, release: HoldRelease): void;
|
|
65
76
|
}
|
|
66
77
|
/**
|
|
67
78
|
* The native app's frame callback, the root member of `__rcHost` that
|
package/dist/native-types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-types.js","sourceRoot":"","sources":["../src/native-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native-types.js","sourceRoot":"","sources":["../src/native-types.ts"],"names":[],"mappings":"AAuGA;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAQ,UAAoF,CAAC,QAAQ,CAAC;AACxG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAO,EACP,QAAyC;IAEzC,MAAM,KAAK,GAAG,QAAQ,IAAK,aAAa,EAA4C,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7F,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,+CAA+C,IAAI,gDAAgD,IAAI,6GAA6G,CACrN,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAE9E,6BAA6B;AAC7B,MAAM,UAAU,QAAQ,CAAC,CAAY;IACnC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,QAAQ,CAAC,CAAY;IACnC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;AACtF,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAA2B;IACtD,MAAM,IAAI,GAAwB;QAChC,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;IACF,IAAI,MAAM,CAAC,GAAG;QAAE,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,cAAc;QAAE,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACjF,IAAI,MAAM,CAAC,eAAe;QAAE,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACpF,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;QAAE,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IACrF,IAAI,MAAM,CAAC,gBAAgB,KAAK,SAAS;QAAE,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAC3F,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * The `input` and `interactions` slices a native host (OpenXR on Quest,\n * CompositorServices on visionOS, or any other shell embedding a JavaScript\n * engine such as Hermes) installs on `globalThis.__rcHost`.\n *\n * These are NOT new contracts: `NativeInputHost` is the exact shape of\n * `InputProvider` from `@realitycollective/webxr-input`, and\n * `NativeInteractionHost` is `HitTester` plus `TransformPort` from\n * `@realitycollective/webxr-interactions/ports`, with a `targetId` added to\n * every `TransformPort` member because that port is per object and the host\n * is one object serving every registered interactable. Declaring them again\n * here, rather than reusing the originals by reference, is deliberate: this\n * file is the one place that states what crosses the native boundary, kept\n * in the same structural-typing style as `babylon-types.ts` and the XR\n * Blocks `XB*Like` types, so it reads correctly even without the other two\n * packages open.\n *\n * Values that cross are plain: numbers, strings, booleans and tuples. No\n * engine objects in either direction, and assets stay entirely on the\n * native side.\n */\nimport type {\n Handedness,\n HeadPose,\n InputCapabilities,\n InputHitHint,\n InputSourceSnapshot,\n PoseTuple,\n PresenceModality,\n QuatTuple,\n RayTuple,\n Unsubscribe,\n Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport type { HoldRelease } from \"@realitycollective/webxr-interactions\";\n\n/** The native host's `input` slice. Mirrors `InputProvider` member for member. */\nexport interface NativeInputHost {\n getCapabilities(): InputCapabilities;\n onCapabilitiesChanged(listener: (capabilities: InputCapabilities) => void): Unsubscribe;\n onSourcesChanged(listener: () => void): Unsubscribe;\n sample(): readonly InputSourceSnapshot[];\n getHeadPose?(): HeadPose;\n sampleHints?(): readonly InputHitHint[];\n pulse?(sourceId: string, intensity: number, durationMs: number): boolean;\n setPresenceVisible?(target: Handedness | \"all\", visible: boolean): boolean;\n setPresenceModality?(mode: PresenceModality): boolean;\n}\n\n/** What the host's ray/proximity query reports: the target it reached, if any. */\nexport interface NativeHit {\n /** The target id the app registered with `NativeTransformPort`/its own scene. */\n targetId: string;\n /** Distance from the query origin (ray origin / probe point). */\n distance: number;\n /** World-space hit or closest point. */\n point: Vec3Tuple;\n}\n\n/**\n * The native host's `interactions` slice: `HitTester` unchanged, and\n * `TransformPort` with every member keyed by the target id the app chose\n * when it registered the object with the native scene, because one host\n * object serves every interactable rather than one port per object.\n */\nexport interface NativeInteractionHost {\n hitRay(ray: RayTuple): NativeHit | null;\n hitProximity(point: Vec3Tuple, radius: number): NativeHit | null;\n /** Where the object is now. */\n getWorldPose(targetId: string): PoseTuple;\n /** The rest pose captured at registration, in world space. */\n getRestWorldPose(targetId: string): PoseTuple;\n getLocalOffset(targetId: string): Vec3Tuple;\n setLocalOffset(targetId: string, offset: Vec3Tuple): void;\n setLocalRotation(targetId: string, quaternion: QuatTuple): void;\n setWorldPose?(targetId: string, pose: PoseTuple): void;\n setEffect?(targetId: string, effect: { scale?: number; emissive?: number }): void;\n /**\n * Suspend physics for `targetId` - see `TransformPort.beginHold` in\n * `@realitycollective/webxr-interactions`. Present only on a host that\n * can drive its own physics this way; a host with no physics, or a\n * native app that fulfils grabs itself through its own engine, never\n * needs it - see this package's README.\n */\n beginHold?(targetId: string): void;\n /** Resume physics for `targetId` with `release` as its new velocity. */\n endHold?(targetId: string, release: HoldRelease): void;\n}\n\n/**\n * The native app's frame callback, the root member of `__rcHost` that\n * `NativeInteractions` attaches to when `attachToHost` is set.\n */\nexport interface NativeFrameSource {\n onFrame(callback: (timestampMs: number, deltaS: number) => void): () => void;\n}\n\n/** The two slices this package reads off `globalThis.__rcHost`. */\nexport interface NativeHostSlices {\n input: NativeInputHost;\n interactions: NativeInteractionHost;\n}\n\n/**\n * `globalThis.__rcHost`, read defensively: the root `NativeHost` interface\n * belongs to `service-framework-native`, which this package does not depend\n * on, so the global is read as an unknown bag of optional slices rather than\n * imported.\n */\nexport function installedHost(): (Partial<NativeHostSlices> & Partial<NativeFrameSource>) | undefined {\n return (globalThis as { __rcHost?: Partial<NativeHostSlices> & Partial<NativeFrameSource> }).__rcHost;\n}\n\n/**\n * Resolve one slice: the value passed in, or `globalThis.__rcHost`'s slice\n * of the same name. Throws one clear error naming the missing slice, so a\n * native-interactions class fails at construction rather than the first\n * time something calls a method that is not there.\n */\nexport function resolveHostSlice<K extends keyof NativeHostSlices>(\n name: K,\n injected: NativeHostSlices[K] | undefined,\n): NativeHostSlices[K] {\n const slice = injected ?? (installedHost() as Partial<NativeHostSlices> | undefined)?.[name];\n if (!slice) {\n throw new Error(\n `@realitycollective/native-interactions: no \"${name}\" slice was supplied and globalThis.__rcHost.${name} is not installed. Pass one directly, or have the native app install it before this package is constructed.`,\n );\n }\n return slice;\n}\n\n// ---------------------------------------------------------------------------\n// Copies. A host may reuse its own buffers across calls, so every tuple that\n// crosses back into this package is copied on the way in, never referenced.\n// ---------------------------------------------------------------------------\n\n/** Copy a position tuple. */\nexport function copyVec3(v: Vec3Tuple): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\n/** Copy an orientation tuple. */\nexport function copyQuat(q: QuatTuple): QuatTuple {\n return [q[0], q[1], q[2], q[3]];\n}\n\n/** Copy a pose (position + orientation). */\nexport function copyPose(pose: PoseTuple): PoseTuple {\n return { position: copyVec3(pose.position), quaternion: copyQuat(pose.quaternion) };\n}\n\n/** Copy a ray (origin + direction). */\nexport function copyRay(ray: RayTuple): RayTuple {\n return { origin: copyVec3(ray.origin), direction: copyVec3(ray.direction) };\n}\n\n/**\n * Copy one `InputSourceSnapshot` field by field, including every tuple it\n * carries, so a host that pools and refills its own snapshot objects still\n * meets the ownership rule: a snapshot handed to `sample()`'s caller is\n * never written to again.\n */\nexport function copySnapshot(source: InputSourceSnapshot): InputSourceSnapshot {\n const copy: InputSourceSnapshot = {\n id: source.id,\n kind: source.kind,\n handedness: source.handedness,\n select: source.select,\n squeeze: source.squeeze,\n };\n if (source.ray) copy.ray = copyRay(source.ray);\n if (source.gripPose) copy.gripPose = copyPose(source.gripPose);\n if (source.indexTip) copy.indexTip = copyVec3(source.indexTip);\n if (source.linearVelocity) copy.linearVelocity = copyVec3(source.linearVelocity);\n if (source.angularVelocity) copy.angularVelocity = copyVec3(source.angularVelocity);\n if (source.nativeGrabbing !== undefined) copy.nativeGrabbing = source.nativeGrabbing;\n if (source.hapticsAvailable !== undefined) copy.hapticsAvailable = source.hapticsAvailable;\n return copy;\n}\n"]}
|
package/dist/transform-port.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* hook, sees a port without one rather than a method that silently no-ops.
|
|
12
12
|
*/
|
|
13
13
|
import type { PoseTuple, QuatTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
14
|
-
import type { TransformPort } from "@realitycollective/webxr-interactions";
|
|
14
|
+
import type { HoldRelease, TransformPort } from "@realitycollective/webxr-interactions";
|
|
15
15
|
import { type NativeInteractionHost } from "./native-types.js";
|
|
16
16
|
export interface NativeTransformPortOptions {
|
|
17
17
|
/** The `interactions` slice. Omit to read `globalThis.__rcHost.interactions`. */
|
|
@@ -25,6 +25,8 @@ export declare class NativeTransformPort implements TransformPort {
|
|
|
25
25
|
scale?: number;
|
|
26
26
|
emissive?: number;
|
|
27
27
|
}) => void;
|
|
28
|
+
readonly beginHold?: () => void;
|
|
29
|
+
readonly endHold?: (release: HoldRelease) => void;
|
|
28
30
|
constructor(targetId: string, options?: NativeTransformPortOptions);
|
|
29
31
|
getWorldPose(): PoseTuple;
|
|
30
32
|
getRestWorldPose(): PoseTuple;
|
package/dist/transform-port.js
CHANGED
|
@@ -4,6 +4,8 @@ export class NativeTransformPort {
|
|
|
4
4
|
targetId;
|
|
5
5
|
setWorldPose;
|
|
6
6
|
setEffect;
|
|
7
|
+
beginHold;
|
|
8
|
+
endHold;
|
|
7
9
|
constructor(targetId, options = {}) {
|
|
8
10
|
this.targetId = targetId;
|
|
9
11
|
this.host = resolveHostSlice("interactions", options.interactions);
|
|
@@ -15,6 +17,17 @@ export class NativeTransformPort {
|
|
|
15
17
|
const host = this.host;
|
|
16
18
|
this.setEffect = (effect) => host.setEffect(targetId, effect);
|
|
17
19
|
}
|
|
20
|
+
if (this.host.beginHold) {
|
|
21
|
+
const host = this.host;
|
|
22
|
+
this.beginHold = () => host.beginHold(targetId);
|
|
23
|
+
}
|
|
24
|
+
if (this.host.endHold) {
|
|
25
|
+
const host = this.host;
|
|
26
|
+
this.endHold = (release) => host.endHold(targetId, {
|
|
27
|
+
linearVelocity: copyVec3(release.linearVelocity),
|
|
28
|
+
angularVelocity: copyVec3(release.angularVelocity),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
18
31
|
}
|
|
19
32
|
getWorldPose() {
|
|
20
33
|
return copyPose(this.host.getWorldPose(this.targetId));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"AAcA,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;
|
|
1
|
+
{"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"AAcA,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,CAAc;IACvB,OAAO,CAAkC;IAElD,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,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,SAAU,CAAC,QAAQ,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,EAAE,CACzB,IAAI,CAAC,OAAQ,CAAC,QAAQ,EAAE;gBACtB,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;gBAChD,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC;aACnD,CAAC,CAAC;QACP,CAAC;IACH,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 */\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 (this.host.beginHold) {\n const host = this.host;\n this.beginHold = () => host.beginHold!(targetId);\n }\n if (this.host.endHold) {\n const host = this.host;\n this.endHold = (release) =>\n host.endHold!(targetId, {\n linearVelocity: copyVec3(release.linearVelocity),\n angularVelocity: copyVec3(release.angularVelocity),\n });\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.
|
|
3
|
+
"version": "0.1.1-preview.1",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@realitycollective/webxr-input": "^0.1.4",
|
|
38
|
-
"@realitycollective/webxr-interactions": "^0.1.1-preview.
|
|
38
|
+
"@realitycollective/webxr-interactions": "^0.1.1-preview.1"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|