@realitycollective/babylon-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 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
@@ -52,6 +52,7 @@ interactions.setPickWithRay((origin, direction, maxDistance) => {
52
52
  - **Rotations need a quaternion.** A node whose `rotationQuaternion` is null is still driven by Euler angles. Set `node.rotationQuaternion = Quaternion.Identity()` before registering it, or pass `createQuaternion` to `register`, otherwise the first rotation write stores a plain object that Babylon cannot use.
53
53
  - **Presence** shows and hides what Babylon built: motion controller root meshes and hand meshes. Babylon picks the visual per input source, so there is no hands/controllers switch - `setPresenceModality` always returns false.
54
54
  - **Desktop grip.** The pointer fallback puts its grip one metre along the pointer ray, matching the three.js adapter, so grab, hinge, dial and slide follow the cursor on desktop. Set `desktopGripDistance` near the distance of the things being manipulated; at 0 the grip sits on the camera and a drag reports camera motion only.
55
+ - **Held pose.** A node with a Physics V2 `physicsBody`, registered with `physicsMotionTypes` (Babylon's own `PhysicsMotionType.ANIMATED`/`.DYNAMIC` - this package holds no Babylon values, only shapes), grows `beginHold`/`endHold`: while held it switches to `ANIMATED` and follows `setWorldPose` exactly, ignoring gravity and collisions; on release it goes back to `DYNAMIC` with the grabbing hand's velocity, so a throw carries through; a `setWorldPose` while not held teleports and clears velocity. A node with no `physicsBody`, or a construction with no `physicsMotionTypes`, gets neither member and behaves exactly as before. This package has no `@babylonjs/core`/`@babylonjs/havok` dependency to verify the physics half against, so it is written from the Babylon Physics V2 documentation and untested against a live scene - confirm it there before relying on it.
55
56
 
56
57
  ## Peer dependency
57
58
 
@@ -65,6 +65,31 @@ export interface BabylonTransformNodeLike {
65
65
  setEnabled?(value: boolean): void;
66
66
  isEnabled?(checkAncestors?: boolean): boolean;
67
67
  isVisible?: boolean;
68
+ /** Present only on a node Physics V2 has a body for - see {@link BabylonPhysicsBodyLike}. */
69
+ physicsBody?: BabylonPhysicsBodyLike;
70
+ }
71
+ /**
72
+ * Structural slice of Babylon's `PhysicsBody` (Physics V2, Havok or any
73
+ * other plugin behind the same API) - present on `BabylonTransformNodeLike`
74
+ * only when the node has one. Written from the Babylon 7 Physics V2
75
+ * documentation, on the same honesty terms as the rest of this file: this
76
+ * package has no `@babylonjs/core` (or `@babylonjs/havok`) dependency to
77
+ * verify it against, so `BabylonTransformPort`'s held-pose behaviour is
78
+ * unverified against a live Babylon scene, and the maintainer should
79
+ * confirm it there before shipping.
80
+ *
81
+ * `disablePreStep` is Babylon's switch for which way a body and its node
82
+ * agree on the truth: `false` (the default) is physics-drives-node - the
83
+ * simulation writes the node's transform every step; `true` is
84
+ * node-drives-physics - Babylon reads the node's transform instead of
85
+ * writing it. Held, released and reset all briefly need the second
86
+ * direction, so the port toggles it around each of the three.
87
+ */
88
+ export interface BabylonPhysicsBodyLike {
89
+ disablePreStep: boolean;
90
+ setMotionType(motionType: unknown): void;
91
+ setLinearVelocity(velocity: BabylonVector3Like): void;
92
+ setAngularVelocity(velocity: BabylonVector3Like): void;
68
93
  }
69
94
  /** Structural slice of Babylon's `Camera`. */
70
95
  export interface BabylonCameraLike {
@@ -1 +1 @@
1
- {"version":3,"file":"babylon-types.js","sourceRoot":"","sources":["../src/babylon-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shape of the Babylon.js API this adapter reads, written out here\n * rather than imported.\n *\n * `@babylonjs/core` is NOT a dependency of this package, in the same way\n * the XR Blocks adapter does not depend on `xrblocks`. Babylon ships one\n * large package on a fast release train, and an adapter that imported it\n * would drag a version choice into every consumer and break on an upstream\n * rename. Matching the shape instead means an app installs whatever Babylon\n * it already uses and passes its objects straight in.\n *\n * The trade for that is honesty about provenance: these declarations were\n * written from the Babylon 7 documentation, not verified against an\n * installed package, so members a version might not carry are optional and\n * read defensively. Nothing here is required to be a Babylon object - a\n * plain object with the same members works, which is what the tests use.\n *\n * Coordinates: Babylon is LEFT-handed and a node's forward is +Z, where\n * three.js and raw WebXR use -Z. That difference is applied in one place\n * (`nodeForward`) so it is stated once.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { vApplyQuat } from \"@realitycollective/webxr-interactions\";\n\n/** Structural slice of Babylon's `Vector3`. */\nexport interface BabylonVector3Like {\n x: number;\n y: number;\n z: number;\n}\n\n/** Structural slice of Babylon's `Quaternion`. */\nexport interface BabylonQuaternionLike {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\n/** Structural slice of Babylon's `Ray`. */\nexport interface BabylonRayLike {\n origin: BabylonVector3Like;\n direction: BabylonVector3Like;\n}\n\n/**\n * Structural slice of Babylon's `Observable<T>`. The observer handle is\n * opaque - it is only ever handed straight back to `remove`.\n */\nexport interface BabylonObservableLike<T> {\n add(callback: (eventData: T) => void): unknown;\n remove(observer: unknown): boolean;\n}\n\n/**\n * Structural slice of Babylon's `TransformNode`, plus the one member\n * `AbstractMesh` adds that this adapter reads (`isVisible`).\n *\n * `parent` is `unknown` on purpose: Babylon types it as `Nullable<Node>`,\n * and `Node` carries none of the transform members, so anything narrower\n * would refuse a real Babylon node. Read it through `parentOf`.\n */\nexport interface BabylonTransformNodeLike {\n position: BabylonVector3Like;\n rotationQuaternion?: BabylonQuaternionLike | null;\n scaling?: BabylonVector3Like;\n parent?: unknown;\n getAbsolutePosition(): BabylonVector3Like;\n absoluteRotationQuaternion?: BabylonQuaternionLike;\n computeWorldMatrix?(force?: boolean): unknown;\n setEnabled?(value: boolean): void;\n isEnabled?(checkAncestors?: boolean): boolean;\n isVisible?: boolean;\n}\n\n/** Structural slice of Babylon's `Camera`. */\nexport interface BabylonCameraLike {\n globalPosition?: BabylonVector3Like;\n absoluteRotation?: BabylonQuaternionLike;\n position?: BabylonVector3Like;\n getForwardRay?(length?: number): BabylonRayLike;\n}\n\n/** Structural slice of Babylon's `Engine` - only the frame delta is read. */\nexport interface BabylonEngineLike {\n getDeltaTime(): number;\n}\n\n/** Structural slice of Babylon's `PickingInfo`. */\nexport interface BabylonPickingInfoLike {\n hit?: boolean;\n distance?: number;\n pickedPoint?: BabylonVector3Like | null;\n pickedMesh?: BabylonTransformNodeLike | null;\n ray?: BabylonRayLike | null;\n}\n\n/**\n * Structural slice of Babylon's `PointerInfo`. `type` is one of the\n * `PointerEventTypes` constants - see {@link POINTER_EVENT_TYPES}.\n */\nexport interface BabylonPointerInfoLike {\n type: number;\n event?: { clientX?: number; clientY?: number; button?: number };\n pickInfo?: BabylonPickingInfoLike | null;\n}\n\n/**\n * The `PointerEventTypes` values this adapter reacts to. Babylon defines\n * them as one bit per event; these three are unchanged across 5, 6 and 7.\n */\nexport const POINTER_EVENT_TYPES = {\n down: 1,\n up: 2,\n move: 4,\n} as const;\n\n/** Structural slice of Babylon's `Scene`. */\nexport interface BabylonSceneLike {\n /**\n * Babylon defaults to a left-handed system with forward +Z. A scene that\n * sets this flag is right-handed and its nodes face -Z; the adapter reads\n * it once at construction.\n */\n useRightHandedSystem?: boolean;\n onBeforeRenderObservable?: BabylonObservableLike<unknown>;\n onPointerObservable?: BabylonObservableLike<BabylonPointerInfoLike>;\n pick?(x: number, y: number): BabylonPickingInfoLike | null;\n activeCamera?: BabylonCameraLike | null;\n getEngine?(): BabylonEngineLike;\n}\n\n/** Structural slice of one `WebXRControllerComponent` reading. */\nexport interface BabylonMotionControllerComponentLike {\n value?: number;\n pressed?: boolean;\n}\n\n/** Structural slice of Babylon's `WebXRAbstractMotionController`. */\nexport interface BabylonMotionControllerLike {\n getComponentOfType?(type: string): BabylonMotionControllerComponentLike | null;\n getMainComponent?(): BabylonMotionControllerComponentLike | null;\n pulse?(value: number, duration: number): Promise<unknown>;\n rootMesh?: BabylonTransformNodeLike | null;\n}\n\n/** Structural slice of Babylon's `WebXRInputSource`. */\nexport interface BabylonXRControllerLike {\n uniqueId: string;\n inputSource: {\n handedness?: string;\n hand?: unknown;\n gamepad?: { hapticActuators?: readonly unknown[] } | null;\n };\n pointer: BabylonTransformNodeLike;\n grip?: BabylonTransformNodeLike | null;\n motionController?: BabylonMotionControllerLike | null;\n onMotionControllerInitObservable?: BabylonObservableLike<unknown>;\n}\n\n/** Structural slice of Babylon's `WebXRInput`. */\nexport interface BabylonXRInputLike {\n controllers: readonly BabylonXRControllerLike[];\n onControllerAddedObservable?: BabylonObservableLike<BabylonXRControllerLike>;\n onControllerRemovedObservable?: BabylonObservableLike<BabylonXRControllerLike>;\n}\n\n/** Structural slice of one tracked hand from the hand-tracking feature. */\nexport interface BabylonXRHandLike {\n getJointMesh?(jointName: string): BabylonTransformNodeLike | null | undefined;\n handMesh?: BabylonTransformNodeLike | null;\n}\n\n/**\n * Structural slice of `WebXRHandTracking`, the feature the features manager\n * registers under `\"xr-hand-tracking\"`.\n */\nexport interface BabylonHandTrackingLike {\n getHandByControllerId(id: string): BabylonXRHandLike | null | undefined;\n}\n\n/** Structural slice of Babylon's `WebXRDefaultExperience`. */\nexport interface BabylonXRExperienceLike {\n baseExperience?: {\n sessionManager?: {\n session?: unknown;\n onXRSessionInit?: BabylonObservableLike<unknown>;\n onXRSessionEnded?: BabylonObservableLike<unknown>;\n };\n featuresManager?: { getEnabledFeature(featureName: string): unknown };\n };\n input?: BabylonXRInputLike;\n}\n\n/** The name Babylon registers hand tracking under in the features manager. */\nexport const HAND_TRACKING_FEATURE = \"xr-hand-tracking\";\n\n/** Index fingertip joint, as WebXR and Babylon both spell it. */\nexport const INDEX_TIP_JOINT = \"index-finger-tip\";\n\n// ---------------------------------------------------------------------------\n// Conversions. Everything below turns Babylon-shaped objects into the core's\n// tuples, or writes tuples back IN PLACE.\n//\n// In place is deliberate. Babylon caches the previous position/rotation and\n// recomputes the world matrix when the live values differ, so mutating\n// `node.position.x` is seen. Replacing `node.position` with a plain object is\n// not just missed, it breaks Babylon, which calls Vector3 methods on it.\n// ---------------------------------------------------------------------------\n\n/** Copy a Babylon vector into a tuple. */\nexport function toVec3(v: BabylonVector3Like | null | undefined): Vec3Tuple | null {\n return v ? [v.x, v.y, v.z] : null;\n}\n\n/** Copy a Babylon quaternion into a tuple, defaulting to identity. */\nexport function toQuat(q: BabylonQuaternionLike | null | undefined): QuatTuple {\n return q ? [q.x, q.y, q.z, q.w] : [0, 0, 0, 1];\n}\n\n/** Write a tuple into an existing Babylon vector, in place. */\nexport function writeVec3(target: BabylonVector3Like, value: Vec3Tuple): void {\n target.x = value[0];\n target.y = value[1];\n target.z = value[2];\n}\n\n/** Write a tuple into an existing Babylon quaternion, in place. */\nexport function writeQuat(target: BabylonQuaternionLike, value: QuatTuple): void {\n target.x = value[0];\n target.y = value[1];\n target.z = value[2];\n target.w = value[3];\n}\n\n/** A node's world pose: absolute position and absolute rotation. */\nexport function nodeWorldPose(node: BabylonTransformNodeLike): PoseTuple {\n return {\n position: toVec3(node.getAbsolutePosition()) ?? [0, 0, 0],\n quaternion: toQuat(node.absoluteRotationQuaternion),\n };\n}\n\n/**\n * The world-space forward axis for a scene: +Z in Babylon's default\n * left-handed system, the opposite of three.js and of a raw WebXR target\n * ray, and -Z when the scene sets `useRightHandedSystem`.\n */\nexport function defaultForward(rightHanded = false): Vec3Tuple {\n return [0, 0, rightHanded ? -1 : 1];\n}\n\n/**\n * A node's forward direction in world space, honouring the scene's\n * handedness (see {@link defaultForward}).\n */\nexport function nodeForward(node: BabylonTransformNodeLike, rightHanded = false): Vec3Tuple {\n return vApplyQuat(defaultForward(rightHanded), toQuat(node.absoluteRotationQuaternion));\n}\n\n/**\n * The parent of a node, when it is one this adapter can read a world pose\n * from. Babylon types `parent` as `Node`, which has no transform, so a\n * parent that is a bone or a bare node reports null and the caller treats\n * the node as unparented.\n */\nexport function parentOf(node: BabylonTransformNodeLike): BabylonTransformNodeLike | null {\n const parent = node.parent;\n if (!parent || typeof parent !== \"object\") return null;\n const candidate = parent as Partial<BabylonTransformNodeLike>;\n return typeof candidate.getAbsolutePosition === \"function\"\n ? (parent as BabylonTransformNodeLike)\n : null;\n}\n\n/**\n * Is this node currently showing? A node the app disabled or hid is not a\n * hit-test candidate. Absent members mean yes - a fake, or a build that does\n * not carry them, should not silently drop out of targeting.\n */\nexport function nodeShowing(node: BabylonTransformNodeLike): boolean {\n if (node.isVisible === false) return false;\n return node.isEnabled ? node.isEnabled() !== false : true;\n}\n"]}
1
+ {"version":3,"file":"babylon-types.js","sourceRoot":"","sources":["../src/babylon-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shape of the Babylon.js API this adapter reads, written out here\n * rather than imported.\n *\n * `@babylonjs/core` is NOT a dependency of this package, in the same way\n * the XR Blocks adapter does not depend on `xrblocks`. Babylon ships one\n * large package on a fast release train, and an adapter that imported it\n * would drag a version choice into every consumer and break on an upstream\n * rename. Matching the shape instead means an app installs whatever Babylon\n * it already uses and passes its objects straight in.\n *\n * The trade for that is honesty about provenance: these declarations were\n * written from the Babylon 7 documentation, not verified against an\n * installed package, so members a version might not carry are optional and\n * read defensively. Nothing here is required to be a Babylon object - a\n * plain object with the same members works, which is what the tests use.\n *\n * Coordinates: Babylon is LEFT-handed and a node's forward is +Z, where\n * three.js and raw WebXR use -Z. That difference is applied in one place\n * (`nodeForward`) so it is stated once.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { vApplyQuat } from \"@realitycollective/webxr-interactions\";\n\n/** Structural slice of Babylon's `Vector3`. */\nexport interface BabylonVector3Like {\n x: number;\n y: number;\n z: number;\n}\n\n/** Structural slice of Babylon's `Quaternion`. */\nexport interface BabylonQuaternionLike {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\n/** Structural slice of Babylon's `Ray`. */\nexport interface BabylonRayLike {\n origin: BabylonVector3Like;\n direction: BabylonVector3Like;\n}\n\n/**\n * Structural slice of Babylon's `Observable<T>`. The observer handle is\n * opaque - it is only ever handed straight back to `remove`.\n */\nexport interface BabylonObservableLike<T> {\n add(callback: (eventData: T) => void): unknown;\n remove(observer: unknown): boolean;\n}\n\n/**\n * Structural slice of Babylon's `TransformNode`, plus the one member\n * `AbstractMesh` adds that this adapter reads (`isVisible`).\n *\n * `parent` is `unknown` on purpose: Babylon types it as `Nullable<Node>`,\n * and `Node` carries none of the transform members, so anything narrower\n * would refuse a real Babylon node. Read it through `parentOf`.\n */\nexport interface BabylonTransformNodeLike {\n position: BabylonVector3Like;\n rotationQuaternion?: BabylonQuaternionLike | null;\n scaling?: BabylonVector3Like;\n parent?: unknown;\n getAbsolutePosition(): BabylonVector3Like;\n absoluteRotationQuaternion?: BabylonQuaternionLike;\n computeWorldMatrix?(force?: boolean): unknown;\n setEnabled?(value: boolean): void;\n isEnabled?(checkAncestors?: boolean): boolean;\n isVisible?: boolean;\n /** Present only on a node Physics V2 has a body for - see {@link BabylonPhysicsBodyLike}. */\n physicsBody?: BabylonPhysicsBodyLike;\n}\n\n/**\n * Structural slice of Babylon's `PhysicsBody` (Physics V2, Havok or any\n * other plugin behind the same API) - present on `BabylonTransformNodeLike`\n * only when the node has one. Written from the Babylon 7 Physics V2\n * documentation, on the same honesty terms as the rest of this file: this\n * package has no `@babylonjs/core` (or `@babylonjs/havok`) dependency to\n * verify it against, so `BabylonTransformPort`'s held-pose behaviour is\n * unverified against a live Babylon scene, and the maintainer should\n * confirm it there before shipping.\n *\n * `disablePreStep` is Babylon's switch for which way a body and its node\n * agree on the truth: `false` (the default) is physics-drives-node - the\n * simulation writes the node's transform every step; `true` is\n * node-drives-physics - Babylon reads the node's transform instead of\n * writing it. Held, released and reset all briefly need the second\n * direction, so the port toggles it around each of the three.\n */\nexport interface BabylonPhysicsBodyLike {\n disablePreStep: boolean;\n setMotionType(motionType: unknown): void;\n setLinearVelocity(velocity: BabylonVector3Like): void;\n setAngularVelocity(velocity: BabylonVector3Like): void;\n}\n\n/** Structural slice of Babylon's `Camera`. */\nexport interface BabylonCameraLike {\n globalPosition?: BabylonVector3Like;\n absoluteRotation?: BabylonQuaternionLike;\n position?: BabylonVector3Like;\n getForwardRay?(length?: number): BabylonRayLike;\n}\n\n/** Structural slice of Babylon's `Engine` - only the frame delta is read. */\nexport interface BabylonEngineLike {\n getDeltaTime(): number;\n}\n\n/** Structural slice of Babylon's `PickingInfo`. */\nexport interface BabylonPickingInfoLike {\n hit?: boolean;\n distance?: number;\n pickedPoint?: BabylonVector3Like | null;\n pickedMesh?: BabylonTransformNodeLike | null;\n ray?: BabylonRayLike | null;\n}\n\n/**\n * Structural slice of Babylon's `PointerInfo`. `type` is one of the\n * `PointerEventTypes` constants - see {@link POINTER_EVENT_TYPES}.\n */\nexport interface BabylonPointerInfoLike {\n type: number;\n event?: { clientX?: number; clientY?: number; button?: number };\n pickInfo?: BabylonPickingInfoLike | null;\n}\n\n/**\n * The `PointerEventTypes` values this adapter reacts to. Babylon defines\n * them as one bit per event; these three are unchanged across 5, 6 and 7.\n */\nexport const POINTER_EVENT_TYPES = {\n down: 1,\n up: 2,\n move: 4,\n} as const;\n\n/** Structural slice of Babylon's `Scene`. */\nexport interface BabylonSceneLike {\n /**\n * Babylon defaults to a left-handed system with forward +Z. A scene that\n * sets this flag is right-handed and its nodes face -Z; the adapter reads\n * it once at construction.\n */\n useRightHandedSystem?: boolean;\n onBeforeRenderObservable?: BabylonObservableLike<unknown>;\n onPointerObservable?: BabylonObservableLike<BabylonPointerInfoLike>;\n pick?(x: number, y: number): BabylonPickingInfoLike | null;\n activeCamera?: BabylonCameraLike | null;\n getEngine?(): BabylonEngineLike;\n}\n\n/** Structural slice of one `WebXRControllerComponent` reading. */\nexport interface BabylonMotionControllerComponentLike {\n value?: number;\n pressed?: boolean;\n}\n\n/** Structural slice of Babylon's `WebXRAbstractMotionController`. */\nexport interface BabylonMotionControllerLike {\n getComponentOfType?(type: string): BabylonMotionControllerComponentLike | null;\n getMainComponent?(): BabylonMotionControllerComponentLike | null;\n pulse?(value: number, duration: number): Promise<unknown>;\n rootMesh?: BabylonTransformNodeLike | null;\n}\n\n/** Structural slice of Babylon's `WebXRInputSource`. */\nexport interface BabylonXRControllerLike {\n uniqueId: string;\n inputSource: {\n handedness?: string;\n hand?: unknown;\n gamepad?: { hapticActuators?: readonly unknown[] } | null;\n };\n pointer: BabylonTransformNodeLike;\n grip?: BabylonTransformNodeLike | null;\n motionController?: BabylonMotionControllerLike | null;\n onMotionControllerInitObservable?: BabylonObservableLike<unknown>;\n}\n\n/** Structural slice of Babylon's `WebXRInput`. */\nexport interface BabylonXRInputLike {\n controllers: readonly BabylonXRControllerLike[];\n onControllerAddedObservable?: BabylonObservableLike<BabylonXRControllerLike>;\n onControllerRemovedObservable?: BabylonObservableLike<BabylonXRControllerLike>;\n}\n\n/** Structural slice of one tracked hand from the hand-tracking feature. */\nexport interface BabylonXRHandLike {\n getJointMesh?(jointName: string): BabylonTransformNodeLike | null | undefined;\n handMesh?: BabylonTransformNodeLike | null;\n}\n\n/**\n * Structural slice of `WebXRHandTracking`, the feature the features manager\n * registers under `\"xr-hand-tracking\"`.\n */\nexport interface BabylonHandTrackingLike {\n getHandByControllerId(id: string): BabylonXRHandLike | null | undefined;\n}\n\n/** Structural slice of Babylon's `WebXRDefaultExperience`. */\nexport interface BabylonXRExperienceLike {\n baseExperience?: {\n sessionManager?: {\n session?: unknown;\n onXRSessionInit?: BabylonObservableLike<unknown>;\n onXRSessionEnded?: BabylonObservableLike<unknown>;\n };\n featuresManager?: { getEnabledFeature(featureName: string): unknown };\n };\n input?: BabylonXRInputLike;\n}\n\n/** The name Babylon registers hand tracking under in the features manager. */\nexport const HAND_TRACKING_FEATURE = \"xr-hand-tracking\";\n\n/** Index fingertip joint, as WebXR and Babylon both spell it. */\nexport const INDEX_TIP_JOINT = \"index-finger-tip\";\n\n// ---------------------------------------------------------------------------\n// Conversions. Everything below turns Babylon-shaped objects into the core's\n// tuples, or writes tuples back IN PLACE.\n//\n// In place is deliberate. Babylon caches the previous position/rotation and\n// recomputes the world matrix when the live values differ, so mutating\n// `node.position.x` is seen. Replacing `node.position` with a plain object is\n// not just missed, it breaks Babylon, which calls Vector3 methods on it.\n// ---------------------------------------------------------------------------\n\n/** Copy a Babylon vector into a tuple. */\nexport function toVec3(v: BabylonVector3Like | null | undefined): Vec3Tuple | null {\n return v ? [v.x, v.y, v.z] : null;\n}\n\n/** Copy a Babylon quaternion into a tuple, defaulting to identity. */\nexport function toQuat(q: BabylonQuaternionLike | null | undefined): QuatTuple {\n return q ? [q.x, q.y, q.z, q.w] : [0, 0, 0, 1];\n}\n\n/** Write a tuple into an existing Babylon vector, in place. */\nexport function writeVec3(target: BabylonVector3Like, value: Vec3Tuple): void {\n target.x = value[0];\n target.y = value[1];\n target.z = value[2];\n}\n\n/** Write a tuple into an existing Babylon quaternion, in place. */\nexport function writeQuat(target: BabylonQuaternionLike, value: QuatTuple): void {\n target.x = value[0];\n target.y = value[1];\n target.z = value[2];\n target.w = value[3];\n}\n\n/** A node's world pose: absolute position and absolute rotation. */\nexport function nodeWorldPose(node: BabylonTransformNodeLike): PoseTuple {\n return {\n position: toVec3(node.getAbsolutePosition()) ?? [0, 0, 0],\n quaternion: toQuat(node.absoluteRotationQuaternion),\n };\n}\n\n/**\n * The world-space forward axis for a scene: +Z in Babylon's default\n * left-handed system, the opposite of three.js and of a raw WebXR target\n * ray, and -Z when the scene sets `useRightHandedSystem`.\n */\nexport function defaultForward(rightHanded = false): Vec3Tuple {\n return [0, 0, rightHanded ? -1 : 1];\n}\n\n/**\n * A node's forward direction in world space, honouring the scene's\n * handedness (see {@link defaultForward}).\n */\nexport function nodeForward(node: BabylonTransformNodeLike, rightHanded = false): Vec3Tuple {\n return vApplyQuat(defaultForward(rightHanded), toQuat(node.absoluteRotationQuaternion));\n}\n\n/**\n * The parent of a node, when it is one this adapter can read a world pose\n * from. Babylon types `parent` as `Node`, which has no transform, so a\n * parent that is a bone or a bare node reports null and the caller treats\n * the node as unparented.\n */\nexport function parentOf(node: BabylonTransformNodeLike): BabylonTransformNodeLike | null {\n const parent = node.parent;\n if (!parent || typeof parent !== \"object\") return null;\n const candidate = parent as Partial<BabylonTransformNodeLike>;\n return typeof candidate.getAbsolutePosition === \"function\"\n ? (parent as BabylonTransformNodeLike)\n : null;\n}\n\n/**\n * Is this node currently showing? A node the app disabled or hid is not a\n * hit-test candidate. Absent members mean yes - a fake, or a build that does\n * not carry them, should not silently drop out of targeting.\n */\nexport function nodeShowing(node: BabylonTransformNodeLike): boolean {\n if (node.isVisible === false) return false;\n return node.isEnabled ? node.isEnabled() !== false : true;\n}\n"]}
@@ -11,7 +11,7 @@
11
11
  * them.
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, type TransformPort } from "@realitycollective/webxr-interactions";
15
15
  import { type BabylonQuaternionLike, type BabylonTransformNodeLike } from "./babylon-types.js";
16
16
  export interface BabylonTransformPortOptions {
17
17
  /**
@@ -26,6 +26,22 @@ export interface BabylonTransformPortOptions {
26
26
  * A node that already has one needs neither - it is mutated in place.
27
27
  */
28
28
  createQuaternion?: () => BabylonQuaternionLike;
29
+ /**
30
+ * The two `PhysicsMotionType` values `beginHold`/`endHold` switch a held
31
+ * node's `physicsBody` between - pass Babylon's own
32
+ * `{ animated: PhysicsMotionType.ANIMATED, dynamic: PhysicsMotionType.DYNAMIC }`.
33
+ * This package holds no Babylon values, only shapes, so the app supplies
34
+ * the actual enum members, the same idea as `createQuaternion`.
35
+ *
36
+ * `beginHold`/`endHold` exist on the port only when BOTH this option and
37
+ * `node.physicsBody` are present at construction - a node with no
38
+ * physics body, or a construction with no motion types, gets neither
39
+ * member and behaves exactly as before.
40
+ */
41
+ physicsMotionTypes?: {
42
+ animated: unknown;
43
+ dynamic: unknown;
44
+ };
29
45
  }
30
46
  export declare class BabylonTransformPort implements TransformPort {
31
47
  private readonly node;
@@ -33,6 +49,9 @@ export declare class BabylonTransformPort implements TransformPort {
33
49
  private restPosition;
34
50
  private restQuaternion;
35
51
  private restScale;
52
+ private held;
53
+ readonly beginHold?: () => void;
54
+ readonly endHold?: (release: HoldRelease) => void;
36
55
  constructor(node: BabylonTransformNodeLike, options?: BabylonTransformPortOptions);
37
56
  /** Re-read the node's current local transform as the new rest. */
38
57
  recaptureRest(): void;
@@ -48,8 +67,11 @@ export declare class BabylonTransformPort implements TransformPort {
48
67
  setLocalOffset(offset: Vec3Tuple): void;
49
68
  setLocalRotation(quaternion: QuatTuple): void;
50
69
  /**
51
- * Follow a world pose while grabbed. With a parent, the pose is resolved
52
- * into the parent's frame through its absolute position and rotation.
70
+ * Follow a world pose while grabbed, or place the node directly the rest
71
+ * of the time (reset / teleport) - see `TransformPort.setWorldPose`'s own
72
+ * comment for what the two mean on a node with physics. With a parent,
73
+ * the pose is resolved into the parent's frame through its absolute
74
+ * position and rotation.
53
75
  *
54
76
  * Simplification: parent SCALE is ignored. A grabbed object under a scaled
55
77
  * parent tracks the hand at the wrong distance. Grabbables are expected to
@@ -6,10 +6,32 @@ export class BabylonTransformPort {
6
6
  restPosition = [0, 0, 0];
7
7
  restQuaternion = [0, 0, 0, 1];
8
8
  restScale = [1, 1, 1];
9
+ held = false;
10
+ beginHold;
11
+ endHold;
9
12
  constructor(node, options = {}) {
10
13
  this.node = node;
11
14
  this.createQuaternion = options.createQuaternion ?? (() => ({ x: 0, y: 0, z: 0, w: 1 }));
12
15
  this.recaptureRest();
16
+ const body = node.physicsBody;
17
+ const motionTypes = options.physicsMotionTypes;
18
+ if (body && motionTypes) {
19
+ this.beginHold = () => {
20
+ this.held = true;
21
+ // Node-drives-physics: our setWorldPose writes below now stick,
22
+ // and gravity/collisions stop moving the node - see
23
+ // BabylonPhysicsBodyLike's own comment.
24
+ body.disablePreStep = true;
25
+ body.setMotionType(motionTypes.animated);
26
+ };
27
+ this.endHold = (release) => {
28
+ this.held = false;
29
+ body.setMotionType(motionTypes.dynamic);
30
+ body.disablePreStep = false;
31
+ body.setLinearVelocity(vector3Like(release.linearVelocity));
32
+ body.setAngularVelocity(vector3Like(release.angularVelocity));
33
+ };
34
+ }
13
35
  }
14
36
  /** Re-read the node's current local transform as the new rest. */
15
37
  recaptureRest() {
@@ -50,8 +72,11 @@ export class BabylonTransformPort {
50
72
  this.writeRotation(quatMultiply(this.restQuaternion, quaternion));
51
73
  }
52
74
  /**
53
- * Follow a world pose while grabbed. With a parent, the pose is resolved
54
- * into the parent's frame through its absolute position and rotation.
75
+ * Follow a world pose while grabbed, or place the node directly the rest
76
+ * of the time (reset / teleport) - see `TransformPort.setWorldPose`'s own
77
+ * comment for what the two mean on a node with physics. With a parent,
78
+ * the pose is resolved into the parent's frame through its absolute
79
+ * position and rotation.
55
80
  *
56
81
  * Simplification: parent SCALE is ignored. A grabbed object under a scaled
57
82
  * parent tracks the hand at the wrong distance. Grabbables are expected to
@@ -62,12 +87,23 @@ export class BabylonTransformPort {
62
87
  if (!parent) {
63
88
  writeVec3(this.node.position, pose.position);
64
89
  this.writeRotation(pose.quaternion);
65
- return;
66
90
  }
67
- const parentPose = nodeWorldPose(parent);
68
- const inverse = quatConjugate(parentPose.quaternion);
69
- writeVec3(this.node.position, vApplyQuat(vSub(pose.position, parentPose.position), inverse));
70
- this.writeRotation(quatMultiply(inverse, pose.quaternion));
91
+ else {
92
+ const parentPose = nodeWorldPose(parent);
93
+ const inverse = quatConjugate(parentPose.quaternion);
94
+ writeVec3(this.node.position, vApplyQuat(vSub(pose.position, parentPose.position), inverse));
95
+ this.writeRotation(quatMultiply(inverse, pose.quaternion));
96
+ }
97
+ // Not held: a physics-enabled node teleports and comes to rest, rather
98
+ // than carrying whatever velocity it had a moment before ("back to the
99
+ // tee"). A DYNAMIC body already picks up a direct node write on its
100
+ // next pre-step (the same sync `beginHold`'s ANIMATED switch disables),
101
+ // so all a reset needs on top of the write above is clearing velocity.
102
+ const body = this.node.physicsBody;
103
+ if (body && !this.held) {
104
+ body.setLinearVelocity(ZERO_VECTOR3);
105
+ body.setAngularVelocity(ZERO_VECTOR3);
106
+ }
71
107
  }
72
108
  /**
73
109
  * Uniform scale about the rest scale. The emissive part of the intent is
@@ -97,4 +133,9 @@ export class BabylonTransformPort {
97
133
  writeQuat(target, value);
98
134
  }
99
135
  }
136
+ /** A fresh plain `{ x, y, z }` - `setLinearVelocity`/`setAngularVelocity` only ever read it. */
137
+ function vector3Like(v) {
138
+ return { x: v[0], y: v[1], z: v[2] };
139
+ }
140
+ const ZERO_VECTOR3 = { x: 0, y: 0, z: 0 };
100
141
  //# sourceMappingURL=transform-port.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,aAAa,EACb,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,IAAI,GAEL,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EACT,SAAS,GAGV,MAAM,oBAAoB,CAAC;AAiB5B,MAAM,OAAO,oBAAoB;IACd,IAAI,CAA2B;IAC/B,gBAAgB,CAA8B;IACvD,YAAY,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACpC,cAAc,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,SAAS,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEzC,YAAY,IAA8B,EAAE,UAAuC,EAAE;QACnF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACzF,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,kEAAkE;IAClE,aAAa;QACX,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,2EAA2E;IAC3E,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,CAAC;QACrC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACd,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QACpF,CAAC;QACD,MAAM,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;YACzF,UAAU,EAAE,YAAY,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC;SACrE,CAAC;IACJ,CAAC;IAED,cAAc;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/E,OAAO,UAAU,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,cAAc,CAAC,MAAiB;QAC9B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;IAClG,CAAC;IAED,gBAAgB,CAAC,UAAqB;QACpC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC,CAAC;IACpE,CAAC;IAED;;;;;;;OAOG;IACH,YAAY,CAAC,IAAe;QAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACrD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7F,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAA6C;QACrD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;QAClC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,SAAS,CAAC,OAAO,EAAE;YACjB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK;YAChC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK;YAChC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK;SACjC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IACtE,aAAa,CAAC,KAAgB;QACpC,IAAI,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,kBAAkB,GAAG,MAAM,CAAC;QACxC,CAAC;QACD,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF","sourcesContent":["/**\n * BabylonTransformPort - the read/write surface of one interactable's\n * Babylon node, honouring the core's FROM-REST semantics: the rest pose is\n * captured at construction and offsets and rotations apply relative to it,\n * so a behaviour composes with anything else animating the same node.\n *\n * Every write mutates the node's existing `Vector3`/`Quaternion` in place.\n * Babylon compares the live values against its cache to decide whether the\n * world matrix needs recomputing, so in-place writes are seen; assigning a\n * plain object in their place would break Babylon, which calls methods on\n * them.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n quatConjugate,\n quatMultiply,\n vAdd,\n vApplyQuat,\n vSub,\n type TransformPort,\n} from \"@realitycollective/webxr-interactions\";\nimport {\n nodeWorldPose,\n parentOf,\n toQuat,\n toVec3,\n writeQuat,\n writeVec3,\n type BabylonQuaternionLike,\n type BabylonTransformNodeLike,\n} from \"./babylon-types.js\";\n\nexport interface BabylonTransformPortOptions {\n /**\n * Builds the `Quaternion` written to a node whose `rotationQuaternion` is\n * null - a node still driven by Euler `rotation`, which is Babylon's\n * default. Pass `() => Quaternion.Identity()` from `@babylonjs/core`.\n *\n * The fallback is a plain `{ x, y, z, w }` object, which carries the\n * numbers correctly but is NOT a Babylon `Quaternion` and will fail as\n * soon as Babylon calls a method on it. So: either give a node a real\n * `rotationQuaternion` before registering it, or supply this factory.\n * A node that already has one needs neither - it is mutated in place.\n */\n createQuaternion?: () => BabylonQuaternionLike;\n}\n\nexport class BabylonTransformPort implements TransformPort {\n private readonly node: BabylonTransformNodeLike;\n private readonly createQuaternion: () => BabylonQuaternionLike;\n private restPosition: Vec3Tuple = [0, 0, 0];\n private restQuaternion: QuatTuple = [0, 0, 0, 1];\n private restScale: Vec3Tuple = [1, 1, 1];\n\n constructor(node: BabylonTransformNodeLike, options: BabylonTransformPortOptions = {}) {\n this.node = node;\n this.createQuaternion = options.createQuaternion ?? (() => ({ x: 0, y: 0, z: 0, w: 1 }));\n this.recaptureRest();\n }\n\n /** Re-read the node's current local transform as the new rest. */\n recaptureRest(): void {\n this.restPosition = toVec3(this.node.position) ?? [0, 0, 0];\n this.restQuaternion = toQuat(this.node.rotationQuaternion);\n this.restScale = toVec3(this.node.scaling) ?? [1, 1, 1];\n }\n\n /** The node's LIVE world pose, from its absolute position and rotation. */\n getWorldPose(): PoseTuple {\n this.node.computeWorldMatrix?.(true);\n return nodeWorldPose(this.node);\n }\n\n /**\n * The captured rest pose in world space, resolved through the parent's\n * absolute position and rotation. Parent SCALE is ignored, the same\n * simplification as {@link setWorldPose}.\n */\n getRestWorldPose(): PoseTuple {\n const parent = parentOf(this.node);\n if (!parent) {\n return { position: [...this.restPosition], quaternion: [...this.restQuaternion] };\n }\n parent.computeWorldMatrix?.(true);\n const parentPose = nodeWorldPose(parent);\n return {\n position: vAdd(parentPose.position, vApplyQuat(this.restPosition, parentPose.quaternion)),\n quaternion: quatMultiply(parentPose.quaternion, this.restQuaternion),\n };\n }\n\n getLocalOffset(): Vec3Tuple {\n const delta = vSub(toVec3(this.node.position) ?? [0, 0, 0], this.restPosition);\n return vApplyQuat(delta, quatConjugate(this.restQuaternion));\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n writeVec3(this.node.position, vAdd(this.restPosition, vApplyQuat(offset, this.restQuaternion)));\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.writeRotation(quatMultiply(this.restQuaternion, quaternion));\n }\n\n /**\n * Follow a world pose while grabbed. With a parent, the pose is resolved\n * into the parent's frame through its absolute position and rotation.\n *\n * Simplification: parent SCALE is ignored. A grabbed object under a scaled\n * parent tracks the hand at the wrong distance. Grabbables are expected to\n * sit under an unscaled parent, which is how the demos build them.\n */\n setWorldPose(pose: PoseTuple): void {\n const parent = parentOf(this.node);\n if (!parent) {\n writeVec3(this.node.position, pose.position);\n this.writeRotation(pose.quaternion);\n return;\n }\n const parentPose = nodeWorldPose(parent);\n const inverse = quatConjugate(parentPose.quaternion);\n writeVec3(this.node.position, vApplyQuat(vSub(pose.position, parentPose.position), inverse));\n this.writeRotation(quatMultiply(inverse, pose.quaternion));\n }\n\n /**\n * Uniform scale about the rest scale. The emissive part of the intent is\n * not applied: reaching a material's emissive colour means knowing which\n * Babylon material the node carries, and this package holds no Babylon\n * types. Apps that want the glow subscribe to the core's feedback intents.\n */\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale === undefined) return;\n const scaling = this.node.scaling;\n if (!scaling) return;\n writeVec3(scaling, [\n this.restScale[0] * effect.scale,\n this.restScale[1] * effect.scale,\n this.restScale[2] * effect.scale,\n ]);\n }\n\n /** Write a quaternion, creating the node's `rotationQuaternion` if needed. */\n private writeRotation(value: QuatTuple): void {\n let target = this.node.rotationQuaternion;\n if (!target) {\n target = this.createQuaternion();\n this.node.rotationQuaternion = target;\n }\n writeQuat(target, value);\n }\n}\n"]}
1
+ {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonTransformPort - the read/write surface of one interactable's\n * Babylon node, honouring the core's FROM-REST semantics: the rest pose is\n * captured at construction and offsets and rotations apply relative to it,\n * so a behaviour composes with anything else animating the same node.\n *\n * Every write mutates the node's existing `Vector3`/`Quaternion` in place.\n * Babylon compares the live values against its cache to decide whether the\n * world matrix needs recomputing, so in-place writes are seen; assigning a\n * plain object in their place would break Babylon, which calls methods on\n * them.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n quatConjugate,\n quatMultiply,\n vAdd,\n vApplyQuat,\n vSub,\n type HoldRelease,\n type TransformPort,\n} from \"@realitycollective/webxr-interactions\";\nimport {\n nodeWorldPose,\n parentOf,\n toQuat,\n toVec3,\n writeQuat,\n writeVec3,\n type BabylonQuaternionLike,\n type BabylonTransformNodeLike,\n type BabylonVector3Like,\n} from \"./babylon-types.js\";\n\nexport interface BabylonTransformPortOptions {\n /**\n * Builds the `Quaternion` written to a node whose `rotationQuaternion` is\n * null - a node still driven by Euler `rotation`, which is Babylon's\n * default. Pass `() => Quaternion.Identity()` from `@babylonjs/core`.\n *\n * The fallback is a plain `{ x, y, z, w }` object, which carries the\n * numbers correctly but is NOT a Babylon `Quaternion` and will fail as\n * soon as Babylon calls a method on it. So: either give a node a real\n * `rotationQuaternion` before registering it, or supply this factory.\n * A node that already has one needs neither - it is mutated in place.\n */\n createQuaternion?: () => BabylonQuaternionLike;\n /**\n * The two `PhysicsMotionType` values `beginHold`/`endHold` switch a held\n * node's `physicsBody` between - pass Babylon's own\n * `{ animated: PhysicsMotionType.ANIMATED, dynamic: PhysicsMotionType.DYNAMIC }`.\n * This package holds no Babylon values, only shapes, so the app supplies\n * the actual enum members, the same idea as `createQuaternion`.\n *\n * `beginHold`/`endHold` exist on the port only when BOTH this option and\n * `node.physicsBody` are present at construction - a node with no\n * physics body, or a construction with no motion types, gets neither\n * member and behaves exactly as before.\n */\n physicsMotionTypes?: { animated: unknown; dynamic: unknown };\n}\n\nexport class BabylonTransformPort implements TransformPort {\n private readonly node: BabylonTransformNodeLike;\n private readonly createQuaternion: () => BabylonQuaternionLike;\n private restPosition: Vec3Tuple = [0, 0, 0];\n private restQuaternion: QuatTuple = [0, 0, 0, 1];\n private restScale: Vec3Tuple = [1, 1, 1];\n private held = false;\n\n readonly beginHold?: () => void;\n readonly endHold?: (release: HoldRelease) => void;\n\n constructor(node: BabylonTransformNodeLike, options: BabylonTransformPortOptions = {}) {\n this.node = node;\n this.createQuaternion = options.createQuaternion ?? (() => ({ x: 0, y: 0, z: 0, w: 1 }));\n this.recaptureRest();\n\n const body = node.physicsBody;\n const motionTypes = options.physicsMotionTypes;\n if (body && motionTypes) {\n this.beginHold = () => {\n this.held = true;\n // Node-drives-physics: our setWorldPose writes below now stick,\n // and gravity/collisions stop moving the node - see\n // BabylonPhysicsBodyLike's own comment.\n body.disablePreStep = true;\n body.setMotionType(motionTypes.animated);\n };\n this.endHold = (release) => {\n this.held = false;\n body.setMotionType(motionTypes.dynamic);\n body.disablePreStep = false;\n body.setLinearVelocity(vector3Like(release.linearVelocity));\n body.setAngularVelocity(vector3Like(release.angularVelocity));\n };\n }\n }\n\n /** Re-read the node's current local transform as the new rest. */\n recaptureRest(): void {\n this.restPosition = toVec3(this.node.position) ?? [0, 0, 0];\n this.restQuaternion = toQuat(this.node.rotationQuaternion);\n this.restScale = toVec3(this.node.scaling) ?? [1, 1, 1];\n }\n\n /** The node's LIVE world pose, from its absolute position and rotation. */\n getWorldPose(): PoseTuple {\n this.node.computeWorldMatrix?.(true);\n return nodeWorldPose(this.node);\n }\n\n /**\n * The captured rest pose in world space, resolved through the parent's\n * absolute position and rotation. Parent SCALE is ignored, the same\n * simplification as {@link setWorldPose}.\n */\n getRestWorldPose(): PoseTuple {\n const parent = parentOf(this.node);\n if (!parent) {\n return { position: [...this.restPosition], quaternion: [...this.restQuaternion] };\n }\n parent.computeWorldMatrix?.(true);\n const parentPose = nodeWorldPose(parent);\n return {\n position: vAdd(parentPose.position, vApplyQuat(this.restPosition, parentPose.quaternion)),\n quaternion: quatMultiply(parentPose.quaternion, this.restQuaternion),\n };\n }\n\n getLocalOffset(): Vec3Tuple {\n const delta = vSub(toVec3(this.node.position) ?? [0, 0, 0], this.restPosition);\n return vApplyQuat(delta, quatConjugate(this.restQuaternion));\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n writeVec3(this.node.position, vAdd(this.restPosition, vApplyQuat(offset, this.restQuaternion)));\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.writeRotation(quatMultiply(this.restQuaternion, quaternion));\n }\n\n /**\n * Follow a world pose while grabbed, or place the node directly the rest\n * of the time (reset / teleport) - see `TransformPort.setWorldPose`'s own\n * comment for what the two mean on a node with physics. With a parent,\n * the pose is resolved into the parent's frame through its absolute\n * position and rotation.\n *\n * Simplification: parent SCALE is ignored. A grabbed object under a scaled\n * parent tracks the hand at the wrong distance. Grabbables are expected to\n * sit under an unscaled parent, which is how the demos build them.\n */\n setWorldPose(pose: PoseTuple): void {\n const parent = parentOf(this.node);\n if (!parent) {\n writeVec3(this.node.position, pose.position);\n this.writeRotation(pose.quaternion);\n } else {\n const parentPose = nodeWorldPose(parent);\n const inverse = quatConjugate(parentPose.quaternion);\n writeVec3(this.node.position, vApplyQuat(vSub(pose.position, parentPose.position), inverse));\n this.writeRotation(quatMultiply(inverse, pose.quaternion));\n }\n // Not held: a physics-enabled node teleports and comes to rest, rather\n // than carrying whatever velocity it had a moment before (\"back to the\n // tee\"). A DYNAMIC body already picks up a direct node write on its\n // next pre-step (the same sync `beginHold`'s ANIMATED switch disables),\n // so all a reset needs on top of the write above is clearing velocity.\n const body = this.node.physicsBody;\n if (body && !this.held) {\n body.setLinearVelocity(ZERO_VECTOR3);\n body.setAngularVelocity(ZERO_VECTOR3);\n }\n }\n\n /**\n * Uniform scale about the rest scale. The emissive part of the intent is\n * not applied: reaching a material's emissive colour means knowing which\n * Babylon material the node carries, and this package holds no Babylon\n * types. Apps that want the glow subscribe to the core's feedback intents.\n */\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale === undefined) return;\n const scaling = this.node.scaling;\n if (!scaling) return;\n writeVec3(scaling, [\n this.restScale[0] * effect.scale,\n this.restScale[1] * effect.scale,\n this.restScale[2] * effect.scale,\n ]);\n }\n\n /** Write a quaternion, creating the node's `rotationQuaternion` if needed. */\n private writeRotation(value: QuatTuple): void {\n let target = this.node.rotationQuaternion;\n if (!target) {\n target = this.createQuaternion();\n this.node.rotationQuaternion = target;\n }\n writeQuat(target, value);\n }\n}\n\n/** A fresh plain `{ x, y, z }` - `setLinearVelocity`/`setAngularVelocity` only ever read it. */\nfunction vector3Like(v: Vec3Tuple): BabylonVector3Like {\n return { x: v[0], y: v[1], z: v[2] };\n}\n\nconst ZERO_VECTOR3: BabylonVector3Like = { x: 0, y: 0, z: 0 };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/babylon-interactions",
3
- "version": "0.1.1-preview.0",
3
+ "version": "0.1.1-preview.1",
4
4
  "description": "Babylon.js adapter for the Reality Collective Interaction Extensions - maps a Babylon WebXR experience (WebXRDefaultExperience controllers, motion controller components, hand-tracking joints) into the engine-free @realitycollective/webxr-interactions core, with a scene pointer fallback on desktop. Structurally typed: no @babylonjs/core import. Re-exports the core.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@realitycollective/webxr-input": "^0.1.4",
37
- "@realitycollective/webxr-interactions": "^0.1.1-preview.0"
37
+ "@realitycollective/webxr-interactions": "^0.1.1-preview.1"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",