@realitycollective/babylon-interactions 0.1.1-preview.1 → 0.1.1-preview.2

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
@@ -8,15 +8,33 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
8
8
 
9
9
  ### Added
10
10
 
11
+ - `@realitycollective/webxr-interactions` - three `hitTesterContractCases()` that hold every platform to IWSDK's hit-test semantics: `hitProximity` reports the distance to the target's SURFACE, clamped at zero (a point 3 cm outside a 10 cm target reports 0.03 within 1 mm, not 0.13); it misses a target whose surface is farther than the query radius; and a ray that passes a target farther than its radius misses it. Every platform in the repository already passed them. The `HitTester` and `TransformPort.setEffect` docs now state those semantics, and the effect rule: `scale` multiplies the rest scale, `emissive` adds to the base intensity, and the last write wins.
12
+ - `@realitycollective/native-interactions` - `nativeInteractionsHostConformanceCases()`, the host conformance kit a native app runs on its device against its real slices: the core's whole `hitTesterContractCases()` run against the host itself, the 0.1 m default target radius, the release velocity reaching `endHold`, live-session capabilities, presence hiding the models only per side, `"auto"` following the live modality, and presence never hiding the cursors. The host provides the test readbacks of the new `NativeInteractionsTestHost` type. The kit fails a host that measures proximity to a target's centre, ignores the radius it is handed, loses a release velocity, or draws the wrong presence.
11
13
  - `@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
14
  - `@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
15
  - `@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
16
  - `@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
17
  - `@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
18
  - `@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.
19
+ - `@realitycollective/threejs-interactions` (reused by `@realitycollective/xrblocks-interactions`) - `ThreeHitTester.register(id, object, targetRadius?)` takes an optional radius, default 0.1 m, used by `hitProximity` for a target with no geometry of its own (a bare `Group`), matching IWSDK's default for a target registered without one. A target with a mesh keeps using its own bounding-sphere radius. `hitTesterContractCases()` gains a case proving the default, run wherever a driver's new optional `placeBare(id, position)` can register one.
20
+ - `@realitycollective/threejs-interactions` (reused by `@realitycollective/xrblocks-interactions`) - `ThreeHitTester.hitRay`/`hitProximity` now drop a hit whose object, or any ancestor, has `visible === false`. three.js's own `Raycaster` ignores `visible` by design (r186, issue 14700), so the rule is re-applied by the adapter. `hitTesterContractCases()` gains a case proving it, run wherever a driver's new optional `setVisible(id, visible)` can toggle one; `HitTester`'s doc states the rule for every platform.
21
+ - `@realitycollective/threejs-interactions`, `@realitycollective/babylon-interactions` and `@realitycollective/xrblocks-interactions` - `sample()` reports no sources while a live session exists but is not `"visible"` (backgrounded, or the browser's own "content is obscured" state), the rule `IWSDKInputProvider` already applied to `world.visibilityState`. Proven per platform, and through `provider-parity.test.ts`'s shared suite where a fake session can express visibility.
22
+ - `@realitycollective/xrblocks-interactions` - `XRBlocksInputProvider` reads the real grip pose from `controller.inputSource.gripSpace` (falling back to `targetRaySpace`), through a new optional `xr` context on `XRBlocksContext` - the same `WebXRManager`-shaped access `webxr-uiextensions`' xrblocks integration already reads `gripSpace` through. Angular velocity, differentiated from consecutive grip poses by the core's `VelocityTracker`, is now meaningful; before this the grip pose was the ray origin with an identity orientation, and every rotation reads were zero. `xr` also gates sampling on session visibility. Falls back to the previous ray-pose behaviour with no `xr` context supplied.
23
+ - `@realitycollective/xrblocks-interactions` - `XRBlocksInputProvider.pulse()` plays a haptic pulse through the controller's WebXR Gamepad `hapticActuators` (`Controller.gamepad`, verified against `input/Controller.ts`), clamping intensity to 0..1 exactly as the other providers do, and reports `capabilities.haptics`/`hapticsAvailable` accordingly. xrblocks 0.20.0 had no such surface; 0.21.1 does.
24
+ - `@realitycollective/xrblocks-interactions` - a new `nativeGrab` provider option (mirroring `IWSDKInputProvider`'s) switches `capabilities.grabs` to `"native"`, and `XRBlocksInteractions.onObjectGrabStart`/`onObjectGrabEnd` forward XR Blocks' `ManipulationManager`-driven `ObjectGrabEvent` Script hooks into `setNativeGrabbing`, the same hint `IWSDKInputProvider` raises for its own native grab. xrblocks 0.20.0 had no engine-owned grab; 0.21.1's `ManipulationManager` does.
25
+ - `@realitycollective/xrblocks-interactions` - a new `XRBlocksTransformPort`, replacing the reused `ThreeTransformPort` in the platform's own registration path (`XRBlocksInteractions.register()`) and in `port-parity.test.ts`'s platform list. It implements `beginHold`/`endHold` when the target's object carries a RAPIER rigid body (`register(descriptor, object, { rigidBody })`, plus a `rigidBodyTypes` construction option - the same `physicsMotionTypes` idea `BabylonTransformPort` uses for Havok): held switches the body kinematic and keeps it at the object's written pose; released switches it back to dynamic with the release velocity. With no body, or no `rigidBodyTypes`, it behaves exactly like `ThreeTransformPort`. xrblocks 0.20.0 bundled no physics; 0.21.1 bundles RAPIER (`PhysicsOptions.RAPIER`).
26
+ - `@realitycollective/babylon-interactions` - `BabylonTransformPort.setEffect`'s emissive pulse now lights the target: `material.emissiveColor` (`StandardMaterial` and `PBRMaterial` both carry it) is scaled by `1 + effect.emissive` about its own captured base, and on a `PBRMaterial`, `emissiveIntensity` is additionally set to its base plus `effect.emissive`, the same additive rule three.js's `emissiveIntensity` already follows. Before this the pulse only scaled the mesh. A node with no material, or a material with no `emissiveColor`, is left alone, as before.
17
27
 
18
28
  ### Changed
19
29
 
30
+ - The reference is now IWSDK 1.0. `@realitycollective/iwsdk-interactions` peers on `@iwsdk/core` and `@iwsdk/xr-input` `>=1.0.0 <2.0.0` (was `>=0.5.0 <0.6.0`) and is built and tested against 1.0.0. The per-frame visibility write the presence code works around is unchanged in 1.0.
31
+ - `@realitycollective/threejs-interactions`, `@realitycollective/babylon-interactions` and `@realitycollective/xrblocks-interactions` - a controller's `indexTip` is its ray origin, as IWSDK's input rig makes it, so a controller pokes buttons and interactables as a fingertip does. Before this only a tracked hand carried an index tip on these three platforms, and controllers could not poke at all.
32
+ - **Breaking for native hosts:** `@realitycollective/native-interactions` now derives and decides what the IWSDK provider derives and decides, and a native host reports facts rather than conclusions. What a host must absorb:
33
+ - The `input` slice reports `getFacts()` (`immersive`, `focused`, `handTracking`) and `onFactsChanged(cb)` in place of `getCapabilities()` and `onCapabilitiesChanged(cb)`. `NativeInputProvider` derives every capability as `IWSDKInputProvider.refreshCapabilities` does: `rays`, `pokes` and pose-only grabs from immersion (a controller pokes with its ray origin, so `pokes` no longer waits for hand tracking), `handJoints` and `pinch` from the hand-tracking fact or a tracked hand, `buttonsAxes` from a controller source, `haptics` from a source with an actuator on a host that can pulse, `gaze` and `headPose` from a head pose, and `presence` from `applyPresence`. It re-derives and fires `onCapabilitiesChanged` on every facts or source change; before this a native host hard-coded them and the event never fired.
34
+ - `setPresenceVisible` and `setPresenceModality` are no longer host members. The provider implements them, decides per side what to show as `IWSDKInputProvider.applyPresence` does (`"auto"` shows hands while hand joints are live), and calls the host's new `applyPresence(side, { hand, controller })` with the result, only when a side changed.
35
+ - `beginHold(targetId)` and `endHold(targetId, release)` are required members of the `interactions` slice, and `NativeTransformPort` throws at construction without them. A pose-only grab calls both, and a host without them silently dropped every release velocity. A target with no physics body rests where released, as on IWSDK.
36
+ - The `interactions` slice gains `setTargetRadius(targetId, radius)`. `NativeInteractions.register(descriptor, { targetRadius })` calls it with the given radius or 0.1 m, the radius IWSDK gives a bare target, so a target is never excluded from hit testing for want of one.
37
+ - `@realitycollective/native-interactions` - `NativeInputProvider` samples no sources while the session lacks focus, as IWSDK's provider samples none unless its visibility state is `Visible`; clamps a pulse's intensity to 0..1, as IWSDK does; takes `nativeGrab` as IWSDK's provider does; and has a `dispose()`, which `NativeInteractions.dispose()` calls.
20
38
  - `@realitycollective/webxr-interactions` - `TransformPort.getWorldPose()` is the LIVE world pose on every platform, and the new required `getRestWorldPose()` returns the rest pose captured at registration, in world space. The core only said "current world pose", and the platforms disagreed: the three.js, IWSDK and XR Blocks ports returned the rest pose, while the Babylon.js port returned the live one. The same behaviour therefore acted differently by platform. The hinge, slide and dial behaviours measure the hand against the rest pose, so they now call `getRestWorldPose()`. Grab and toss scoring keep `getWorldPose()` and now see where the object really is. `ports.ts` also states tuple ownership for `TransformPort` and `HitTester`: every returned tuple is fresh and owned by the caller, and a tuple passed in is read during the call and not kept. Every platform already met the ownership rule; the suites now hold them to it. The native `interactions` slice gains `getRestWorldPose(targetId)`, which the native app must implement. This adds a required member to `TransformPort`, so a port written outside this repository must implement it.
21
39
  - `@realitycollective/xrblocks-interactions` - the structural slices `XBVec3Like`, `XBQuatLike`, `XBRayLike` and `XBControllerLike` are exported, under the same `XB` prefix as `XBRaySourceLike`, `XBDirectTouchLike` and `XBFrameLike` whose fields they type. They were module-private, so a consumer building a fake frame for a test had nothing to name, and TypeDoc reported each as referenced but undocumented.
22
40
 
@@ -47,8 +47,21 @@ export interface BabylonObservableLike<T> {
47
47
  remove(observer: unknown): boolean;
48
48
  }
49
49
  /**
50
- * Structural slice of Babylon's `TransformNode`, plus the one member
51
- * `AbstractMesh` adds that this adapter reads (`isVisible`).
50
+ * Structural slice of Babylon's `StandardMaterial`/`PBRMaterial` emissive
51
+ * channel - written from the Babylon 7 documentation, on the same honesty
52
+ * terms as the rest of this file. `emissiveColor` is common to both;
53
+ * `emissiveIntensity` is a PBRMaterial-only scalar multiplier over it -
54
+ * `StandardMaterial` has no such field, so it is absent there and
55
+ * `BabylonTransformPort.setEffect` only scales `emissiveColor` for one.
56
+ */
57
+ export interface BabylonMaterialLike {
58
+ emissiveColor?: BabylonVector3Like;
59
+ /** PBRMaterial only - StandardMaterial carries no separate intensity. */
60
+ emissiveIntensity?: number;
61
+ }
62
+ /**
63
+ * Structural slice of Babylon's `TransformNode`, plus the two members
64
+ * `AbstractMesh` adds that this adapter reads (`isVisible`, `material`).
52
65
  *
53
66
  * `parent` is `unknown` on purpose: Babylon types it as `Nullable<Node>`,
54
67
  * and `Node` carries none of the transform members, so anything narrower
@@ -67,6 +80,8 @@ export interface BabylonTransformNodeLike {
67
80
  isVisible?: boolean;
68
81
  /** Present only on a node Physics V2 has a body for - see {@link BabylonPhysicsBodyLike}. */
69
82
  physicsBody?: BabylonPhysicsBodyLike;
83
+ /** `AbstractMesh.material` - a multi-material mesh's first slot is what `setEffect`'s emissive pulse lights. */
84
+ material?: BabylonMaterialLike | BabylonMaterialLike[] | null;
70
85
  }
71
86
  /**
72
87
  * Structural slice of Babylon's `PhysicsBody` (Physics V2, Havok or any
@@ -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 /** 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"]}
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 `StandardMaterial`/`PBRMaterial` emissive\n * channel - written from the Babylon 7 documentation, on the same honesty\n * terms as the rest of this file. `emissiveColor` is common to both;\n * `emissiveIntensity` is a PBRMaterial-only scalar multiplier over it -\n * `StandardMaterial` has no such field, so it is absent there and\n * `BabylonTransformPort.setEffect` only scales `emissiveColor` for one.\n */\nexport interface BabylonMaterialLike {\n emissiveColor?: BabylonVector3Like;\n /** PBRMaterial only - StandardMaterial carries no separate intensity. */\n emissiveIntensity?: number;\n}\n\n/**\n * Structural slice of Babylon's `TransformNode`, plus the two members\n * `AbstractMesh` adds that this adapter reads (`isVisible`, `material`).\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 /** `AbstractMesh.material` - a multi-material mesh's first slot is what `setEffect`'s emissive pulse lights. */\n material?: BabylonMaterialLike | BabylonMaterialLike[] | null;\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,9 @@
11
11
  *
12
12
  * Capabilities are derived from the LIVE session, never from the requested
13
13
  * configuration, and are re-published on session start, session end and
14
- * controller add/remove.
14
+ * controller add/remove. `sample()` reports no sources at all while the
15
+ * session exists but is not `"visible"`, the same rule IWSDK's provider
16
+ * applies.
15
17
  */
16
18
  import { type Handedness, type HeadPose, type InputCapabilities, type InputProvider, type InputSourceSnapshot, type PresenceModality, type Unsubscribe } from "@realitycollective/webxr-input";
17
19
  import { type BabylonCameraLike, type BabylonSceneLike, type BabylonXRExperienceLike } from "./babylon-types.js";
package/dist/provider.js CHANGED
@@ -11,7 +11,9 @@
11
11
  *
12
12
  * Capabilities are derived from the LIVE session, never from the requested
13
13
  * configuration, and are re-published on session start, session end and
14
- * controller add/remove.
14
+ * controller add/remove. `sample()` reports no sources at all while the
15
+ * session exists but is not `"visible"`, the same rule IWSDK's provider
16
+ * applies.
15
17
  */
16
18
  import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
17
19
  import { HAND_TRACKING_FEATURE, INDEX_TIP_JOINT, POINTER_EVENT_TYPES, nodeForward, defaultForward, nodeWorldPose, toQuat, toVec3, } from "./babylon-types.js";
@@ -186,12 +188,21 @@ export class BabylonInputProvider {
186
188
  // -- sampling -----------------------------------------------------------------
187
189
  sample() {
188
190
  this.sampled.clear();
189
- if (this.session() === null) {
191
+ const session = this.session();
192
+ if (session === null) {
190
193
  // A session that ended between frames leaves stale capabilities behind.
191
194
  if (this.capabilities.rays)
192
195
  this.refreshCapabilities();
193
196
  return this.samplePointer();
194
197
  }
198
+ // A session that exists but is not visible (backgrounded, or the
199
+ // browser's own "content is obscured" state) reports no sources at all,
200
+ // the same rule IWSDK's provider applies to `world.visibilityState`.
201
+ // Babylon's session manager passes the raw `XRSession` through, so its
202
+ // standard `visibilityState` is read straight off it.
203
+ const visibilityState = session.visibilityState;
204
+ if (visibilityState !== undefined && visibilityState !== "visible")
205
+ return [];
195
206
  const snapshots = [];
196
207
  for (const controller of this.controllers()) {
197
208
  const handedness = normalizeHandedness(controller.inputSource.handedness);
@@ -220,6 +231,10 @@ export class BabylonInputProvider {
220
231
  const tip = jointPosition(hand, INDEX_TIP_JOINT);
221
232
  if (tip)
222
233
  snapshot.indexTip = tip;
234
+ // A controller has no fingertip: its index tip is its ray origin, as
235
+ // IWSDK's input rig makes it, so a controller pokes as a finger does.
236
+ else if (!controller.inputSource.hand && snapshot.ray)
237
+ snapshot.indexTip = [...snapshot.ray.origin];
223
238
  if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {
224
239
  snapshot.hapticsAvailable = true;
225
240
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonInputProvider - the Babylon.js input provider.\n *\n * Reads a `WebXRDefaultExperience`: its `input.controllers` for poses and\n * motion-controller components, its session manager for what is live, and\n * the hand-tracking feature for fingertip joints. Falls back to the scene's\n * own pointer (`scene.onPointerObservable`) when no immersive session is\n * running, so the same scene is usable in a flat browser.\n *\n * Nothing here imports `@babylonjs/core` - see `babylon-types.ts`.\n *\n * Capabilities are derived from the LIVE session, never from the requested\n * configuration, and are re-published on session start, session end and\n * controller add/remove.\n */\nimport {\n NO_CAPABILITIES,\n type Handedness,\n type HeadPose,\n type InputCapabilities,\n type InputProvider,\n type InputSourceSnapshot,\n type PresenceModality,\n type Unsubscribe,\n type Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport {\n HAND_TRACKING_FEATURE,\n INDEX_TIP_JOINT,\n POINTER_EVENT_TYPES,\n nodeForward,\n defaultForward,\n nodeWorldPose,\n toQuat,\n toVec3,\n type BabylonCameraLike,\n type BabylonHandTrackingLike,\n type BabylonPointerInfoLike,\n type BabylonSceneLike,\n type BabylonTransformNodeLike,\n type BabylonXRControllerLike,\n type BabylonXRExperienceLike,\n type BabylonXRHandLike,\n} from \"./babylon-types.js\";\n\nexport interface BabylonProviderOptions {\n /**\n * The `WebXRDefaultExperience` (or anything with the same surface).\n * Omit it, or pass null, for a desktop-only app: the provider then only\n * ever reports the scene pointer.\n */\n xr?: BabylonXRExperienceLike | null;\n /** The scene. Supplies the pointer fallback, the camera and the engine. */\n scene: BabylonSceneLike;\n /** Head pose camera. Defaults to `scene.activeCamera`. */\n camera?: BabylonCameraLike | null;\n /**\n * Metres along the pointer ray at which the desktop fallback places its\n * synthetic grip pose. Default 1 metre, matching the three.js adapter, so\n * grab, hinge, dial and slide follow the cursor on desktop. Set it near\n * the distance of the things being manipulated; at 0 the grip sits on the\n * camera and a mouse drag reports camera motion only.\n */\n desktopGripDistance?: number;\n}\n\n/** Trigger, then the main component, then nothing. */\nconst TRIGGER_COMPONENT = \"trigger\";\nconst SQUEEZE_COMPONENT = \"squeeze\";\n\n/** The desktop pointer source's id - stable for the life of the page. */\nexport const DESKTOP_SOURCE_ID = \"babylon-pointer\";\n\ninterface PointerState {\n down: boolean;\n x: number;\n y: number;\n ray: { origin: Vec3Tuple; direction: Vec3Tuple } | null;\n}\n\ninterface SampledSource {\n handedness: \"left\" | \"right\" | \"none\";\n controller: BabylonXRControllerLike;\n}\n\nexport class BabylonInputProvider implements InputProvider {\n private readonly options: BabylonProviderOptions;\n private capabilities: InputCapabilities;\n private readonly capsListeners = new Set<(c: InputCapabilities) => void>();\n private readonly sourceListeners = new Set<() => void>();\n /** id -> handedness + controller, recorded while sampling, read by pulse. */\n private readonly sampled = new Map<string, SampledSource>();\n private readonly detach: Unsubscribe[] = [];\n private readonly gripDistance: number;\n /** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */\n private readonly rightHanded: boolean;\n\n private readonly pointer: PointerState = { down: false, x: 0, y: 0, ray: null };\n\n constructor(options: BabylonProviderOptions) {\n this.options = options;\n this.gripDistance = options.desktopGripDistance ?? 1;\n this.rightHanded = options.scene.useRightHandedSystem === true;\n this.capabilities = { ...NO_CAPABILITIES, headPose: true, gaze: true };\n this.attachPointer();\n this.attachSession();\n this.refreshCapabilities();\n }\n\n // -- wiring -------------------------------------------------------------------\n\n private attachPointer(): void {\n const observable = this.options.scene.onPointerObservable;\n if (!observable) return;\n const observer = observable.add((info: BabylonPointerInfoLike) => this.onPointer(info));\n this.detach.push(() => {\n observable.remove(observer);\n });\n }\n\n private onPointer(info: BabylonPointerInfoLike): void {\n const event = info.event;\n if (event?.clientX !== undefined) this.pointer.x = event.clientX;\n if (event?.clientY !== undefined) this.pointer.y = event.clientY;\n // Babylon hands the pick it already did for this event; reuse it rather\n // than picking the scene a second time on the next sample.\n const ray = info.pickInfo?.ray;\n if (ray) {\n this.pointer.ray = {\n origin: toVec3(ray.origin) ?? [0, 0, 0],\n direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),\n };\n }\n // Only the primary button selects. Babylon reports it as button 0, the\n // same numbering as a DOM PointerEvent.\n if (info.type === POINTER_EVENT_TYPES.down && (event?.button ?? 0) === 0) {\n this.pointer.down = true;\n }\n if (info.type === POINTER_EVENT_TYPES.up && (event?.button ?? 0) === 0) {\n this.pointer.down = false;\n }\n }\n\n private attachSession(): void {\n const manager = this.options.xr?.baseExperience?.sessionManager;\n const input = this.options.xr?.input;\n const republish = () => {\n this.refreshCapabilities();\n for (const listener of [...this.sourceListeners]) listener();\n };\n const subscribe = <T>(\n observable: { add(cb: (value: T) => void): unknown; remove(o: unknown): boolean } | undefined,\n handler: () => void,\n ): void => {\n if (!observable) return;\n const observer = observable.add(() => handler());\n this.detach.push(() => {\n observable.remove(observer);\n });\n };\n subscribe(manager?.onXRSessionInit, republish);\n subscribe(manager?.onXRSessionEnded, () => {\n this.sampled.clear();\n republish();\n });\n subscribe(input?.onControllerAddedObservable, republish);\n subscribe(input?.onControllerRemovedObservable, republish);\n }\n\n // -- capabilities -------------------------------------------------------------\n\n /** The live XR session, or null when none is running. */\n private session(): unknown {\n return this.options.xr?.baseExperience?.sessionManager?.session ?? null;\n }\n\n private controllers(): readonly BabylonXRControllerLike[] {\n return this.options.xr?.input?.controllers ?? [];\n }\n\n /** The hand-tracking feature, when the app enabled it. */\n private handTracking(): BabylonHandTrackingLike | null {\n const manager = this.options.xr?.baseExperience?.featuresManager;\n if (!manager) return null;\n const feature = manager.getEnabledFeature(HAND_TRACKING_FEATURE);\n if (!feature || typeof feature !== \"object\") return null;\n const candidate = feature as Partial<BabylonHandTrackingLike>;\n return typeof candidate.getHandByControllerId === \"function\"\n ? (feature as BabylonHandTrackingLike)\n : null;\n }\n\n private handFor(controller: BabylonXRControllerLike): BabylonXRHandLike | null {\n return this.handTracking()?.getHandByControllerId(controller.uniqueId) ?? null;\n }\n\n private refreshCapabilities(): void {\n const inSession = this.session() !== null;\n const desktop = !inSession && this.options.scene.onPointerObservable !== undefined;\n // Babylon reports no native grab observation, so grabs are always\n // poseOnly: the core drives the transform through the port.\n const next: InputCapabilities = {\n ...NO_CAPABILITIES,\n headPose: true,\n gaze: true,\n pointer2d: desktop,\n // The pointer fallback synthesises a grip on the ray, so hand-driven\n // behaviours negotiate on desktop too. Without this every one of them\n // switches itself off off-headset and only press remains usable.\n grabs: desktop ? \"poseOnly\" : NO_CAPABILITIES.grabs,\n // Set in the loop below: presence is true once Babylon has built at\n // least one visual this provider can hide - a motion controller root\n // mesh, or a hand mesh from the hand-tracking feature.\n presence: false,\n };\n if (inSession) {\n for (const controller of this.controllers()) {\n if (controller.pointer) next.rays = true;\n if (controller.grip ?? controller.pointer) next.grabs = \"poseOnly\";\n if (controller.inputSource.hand) {\n next.handJoints = true;\n next.pokes = true;\n }\n // Pinch strength needs the tracked hand itself, not just the flag on\n // the input source, so it is gated on the feature reporting a hand.\n if (this.handFor(controller)) next.pinch = true;\n if (controller.motionController) next.buttonsAxes = true;\n if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {\n next.haptics = true;\n }\n if (this.visualsFor(controller).length > 0) next.presence = true;\n }\n }\n const changed = JSON.stringify(next) !== JSON.stringify(this.capabilities);\n this.capabilities = next;\n if (changed) {\n for (const listener of [...this.capsListeners]) listener(next);\n }\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(listener: (c: InputCapabilities) => void): Unsubscribe {\n this.capsListeners.add(listener);\n return () => this.capsListeners.delete(listener);\n }\n\n onSourcesChanged(listener: () => void): Unsubscribe {\n this.sourceListeners.add(listener);\n return () => this.sourceListeners.delete(listener);\n }\n\n // -- sampling -----------------------------------------------------------------\n\n sample(): readonly InputSourceSnapshot[] {\n this.sampled.clear();\n if (this.session() === null) {\n // A session that ended between frames leaves stale capabilities behind.\n if (this.capabilities.rays) this.refreshCapabilities();\n return this.samplePointer();\n }\n\n const snapshots: InputSourceSnapshot[] = [];\n for (const controller of this.controllers()) {\n const handedness = normalizeHandedness(controller.inputSource.handedness);\n const hand = this.handFor(controller);\n const pointerNode = controller.pointer;\n const snapshot: InputSourceSnapshot = {\n id: controller.uniqueId,\n kind: controller.inputSource.hand ? \"hand\" : \"controller\",\n handedness,\n select: componentValue(\n controller.motionController?.getComponentOfType?.(TRIGGER_COMPONENT) ??\n controller.motionController?.getMainComponent?.() ??\n null,\n ),\n squeeze: componentValue(\n controller.motionController?.getComponentOfType?.(SQUEEZE_COMPONENT) ?? null,\n ),\n };\n if (pointerNode) {\n snapshot.ray = {\n origin: toVec3(pointerNode.getAbsolutePosition()) ?? [0, 0, 0],\n direction: nodeForward(pointerNode, this.rightHanded),\n };\n }\n // Babylon exposes the grip node only for a controller with one; a hand,\n // and a controller without a grip space, carry on the pointer node.\n const gripNode = controller.grip ?? pointerNode;\n if (gripNode) snapshot.gripPose = nodeWorldPose(gripNode);\n const tip = jointPosition(hand, INDEX_TIP_JOINT);\n if (tip) snapshot.indexTip = tip;\n if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {\n snapshot.hapticsAvailable = true;\n }\n // Babylon reports no per-pose velocity, so nothing is set here and the\n // core's VelocityTracker derives it from consecutive grip poses.\n snapshots.push(snapshot);\n this.sampled.set(snapshot.id, { handedness, controller });\n }\n return snapshots;\n }\n\n /**\n * The desktop pointer, shaped like the three.js adapter's mouse fallback:\n * one source, `handedness: \"none\"`, `kind: \"pointer2d\"`.\n */\n private samplePointer(): readonly InputSourceSnapshot[] {\n if (this.options.scene.onPointerObservable === undefined) return [];\n const ray = this.pointer.ray ?? this.pickRay();\n if (!ray) return [];\n const grip: Vec3Tuple = [\n ray.origin[0] + ray.direction[0] * this.gripDistance,\n ray.origin[1] + ray.direction[1] * this.gripDistance,\n ray.origin[2] + ray.direction[2] * this.gripDistance,\n ];\n return [\n {\n id: DESKTOP_SOURCE_ID,\n kind: \"pointer2d\",\n handedness: \"none\",\n ray: { origin: ray.origin, direction: ray.direction },\n gripPose: { position: grip, quaternion: toQuat(this.camera()?.absoluteRotation) },\n select: this.pointer.down ? 1 : 0,\n // A mouse has one button that means select. Squeeze stays 0 rather\n // than doubling the left button up as both, which would fire a press\n // and a grab together on an interactable that carries both.\n squeeze: 0,\n },\n ];\n }\n\n /** Pick the scene at the last known cursor position for a ray. */\n private pickRay(): { origin: Vec3Tuple; direction: Vec3Tuple } | null {\n const picked = this.options.scene.pick?.(this.pointer.x, this.pointer.y);\n const ray = picked?.ray;\n if (!ray) return null;\n return {\n origin: toVec3(ray.origin) ?? [0, 0, 0],\n direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),\n };\n }\n\n private camera(): BabylonCameraLike | null {\n return this.options.camera ?? this.options.scene.activeCamera ?? null;\n }\n\n getHeadPose(): HeadPose {\n const camera = this.camera();\n return {\n position: toVec3(camera?.globalPosition ?? camera?.position) ?? [0, 0, 0],\n quaternion: toQuat(camera?.absoluteRotation),\n };\n }\n\n // -- haptics ------------------------------------------------------------------\n\n /**\n * Babylon routes haptics through the motion controller rather than the\n * gamepad actuator, so a source with no motion controller cannot pulse\n * even when the gamepad reports an actuator.\n */\n pulse(sourceId: string, intensity: number, durationMs: number): boolean {\n const controller =\n this.sampled.get(sourceId)?.controller ??\n this.controllers().find((c) => c.uniqueId === sourceId);\n const motionController = controller?.motionController;\n if (!motionController?.pulse) return false;\n void motionController.pulse(Math.min(1, Math.max(0, intensity)), durationMs);\n return true;\n }\n\n // -- presence -----------------------------------------------------------------\n\n /**\n * Show or hide the visuals Babylon built for the targeted sides. Returns\n * false when the target has nothing to show or hide. `\"none\"` targets no\n * side, so it reports whether presence is usable at all without changing\n * anything.\n */\n setPresenceVisible(target: Handedness | \"all\", visible: boolean): boolean {\n if (target === \"none\") return this.capabilities.presence;\n let applied = false;\n for (const controller of this.controllers()) {\n const side = normalizeHandedness(controller.inputSource.handedness);\n if (target !== \"all\" && target !== side) continue;\n for (const node of this.visualsFor(controller)) {\n node.setEnabled?.(visible);\n applied = true;\n }\n }\n return applied;\n }\n\n /**\n * Always false. Babylon picks the visual per input source - a controller\n * gets its motion-controller model, a tracked hand gets the hand mesh -\n * and exposes no switch between the two. IWSDK builds both families, so\n * its adapter implements this.\n */\n setPresenceModality(_mode: PresenceModality): boolean {\n return false;\n }\n\n /** Every node that represents this controller on screen. */\n private visualsFor(controller: BabylonXRControllerLike): BabylonTransformNodeLike[] {\n const nodes: BabylonTransformNodeLike[] = [];\n const root = controller.motionController?.rootMesh;\n if (root) nodes.push(root);\n const handMesh = this.handFor(controller)?.handMesh;\n if (handMesh) nodes.push(handMesh);\n return nodes;\n }\n\n dispose(): void {\n for (const off of this.detach.splice(0)) off();\n this.sampled.clear();\n this.capsListeners.clear();\n this.sourceListeners.clear();\n }\n}\n\n/** Analog value where the component reports one, else the pressed flag. */\nfunction componentValue(\n component: { value?: number; pressed?: boolean } | null | undefined,\n): number {\n if (!component) return 0;\n if (typeof component.value === \"number\" && component.value > 0) return component.value;\n return component.pressed ? 1 : 0;\n}\n\nfunction jointPosition(\n hand: BabylonXRHandLike | null,\n joint: string,\n): Vec3Tuple | null {\n const mesh = hand?.getJointMesh?.(joint);\n return mesh ? toVec3(mesh.getAbsolutePosition()) : null;\n}\n\nfunction normalizeHandedness(value: string | undefined): \"left\" | \"right\" | \"none\" {\n return value === \"left\" || value === \"right\" ? value : \"none\";\n}\n"]}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonInputProvider - the Babylon.js input provider.\n *\n * Reads a `WebXRDefaultExperience`: its `input.controllers` for poses and\n * motion-controller components, its session manager for what is live, and\n * the hand-tracking feature for fingertip joints. Falls back to the scene's\n * own pointer (`scene.onPointerObservable`) when no immersive session is\n * running, so the same scene is usable in a flat browser.\n *\n * Nothing here imports `@babylonjs/core` - see `babylon-types.ts`.\n *\n * Capabilities are derived from the LIVE session, never from the requested\n * configuration, and are re-published on session start, session end and\n * controller add/remove. `sample()` reports no sources at all while the\n * session exists but is not `\"visible\"`, the same rule IWSDK's provider\n * applies.\n */\nimport {\n NO_CAPABILITIES,\n type Handedness,\n type HeadPose,\n type InputCapabilities,\n type InputProvider,\n type InputSourceSnapshot,\n type PresenceModality,\n type Unsubscribe,\n type Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport {\n HAND_TRACKING_FEATURE,\n INDEX_TIP_JOINT,\n POINTER_EVENT_TYPES,\n nodeForward,\n defaultForward,\n nodeWorldPose,\n toQuat,\n toVec3,\n type BabylonCameraLike,\n type BabylonHandTrackingLike,\n type BabylonPointerInfoLike,\n type BabylonSceneLike,\n type BabylonTransformNodeLike,\n type BabylonXRControllerLike,\n type BabylonXRExperienceLike,\n type BabylonXRHandLike,\n} from \"./babylon-types.js\";\n\nexport interface BabylonProviderOptions {\n /**\n * The `WebXRDefaultExperience` (or anything with the same surface).\n * Omit it, or pass null, for a desktop-only app: the provider then only\n * ever reports the scene pointer.\n */\n xr?: BabylonXRExperienceLike | null;\n /** The scene. Supplies the pointer fallback, the camera and the engine. */\n scene: BabylonSceneLike;\n /** Head pose camera. Defaults to `scene.activeCamera`. */\n camera?: BabylonCameraLike | null;\n /**\n * Metres along the pointer ray at which the desktop fallback places its\n * synthetic grip pose. Default 1 metre, matching the three.js adapter, so\n * grab, hinge, dial and slide follow the cursor on desktop. Set it near\n * the distance of the things being manipulated; at 0 the grip sits on the\n * camera and a mouse drag reports camera motion only.\n */\n desktopGripDistance?: number;\n}\n\n/** Trigger, then the main component, then nothing. */\nconst TRIGGER_COMPONENT = \"trigger\";\nconst SQUEEZE_COMPONENT = \"squeeze\";\n\n/** The desktop pointer source's id - stable for the life of the page. */\nexport const DESKTOP_SOURCE_ID = \"babylon-pointer\";\n\ninterface PointerState {\n down: boolean;\n x: number;\n y: number;\n ray: { origin: Vec3Tuple; direction: Vec3Tuple } | null;\n}\n\ninterface SampledSource {\n handedness: \"left\" | \"right\" | \"none\";\n controller: BabylonXRControllerLike;\n}\n\nexport class BabylonInputProvider implements InputProvider {\n private readonly options: BabylonProviderOptions;\n private capabilities: InputCapabilities;\n private readonly capsListeners = new Set<(c: InputCapabilities) => void>();\n private readonly sourceListeners = new Set<() => void>();\n /** id -> handedness + controller, recorded while sampling, read by pulse. */\n private readonly sampled = new Map<string, SampledSource>();\n private readonly detach: Unsubscribe[] = [];\n private readonly gripDistance: number;\n /** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */\n private readonly rightHanded: boolean;\n\n private readonly pointer: PointerState = { down: false, x: 0, y: 0, ray: null };\n\n constructor(options: BabylonProviderOptions) {\n this.options = options;\n this.gripDistance = options.desktopGripDistance ?? 1;\n this.rightHanded = options.scene.useRightHandedSystem === true;\n this.capabilities = { ...NO_CAPABILITIES, headPose: true, gaze: true };\n this.attachPointer();\n this.attachSession();\n this.refreshCapabilities();\n }\n\n // -- wiring -------------------------------------------------------------------\n\n private attachPointer(): void {\n const observable = this.options.scene.onPointerObservable;\n if (!observable) return;\n const observer = observable.add((info: BabylonPointerInfoLike) => this.onPointer(info));\n this.detach.push(() => {\n observable.remove(observer);\n });\n }\n\n private onPointer(info: BabylonPointerInfoLike): void {\n const event = info.event;\n if (event?.clientX !== undefined) this.pointer.x = event.clientX;\n if (event?.clientY !== undefined) this.pointer.y = event.clientY;\n // Babylon hands the pick it already did for this event; reuse it rather\n // than picking the scene a second time on the next sample.\n const ray = info.pickInfo?.ray;\n if (ray) {\n this.pointer.ray = {\n origin: toVec3(ray.origin) ?? [0, 0, 0],\n direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),\n };\n }\n // Only the primary button selects. Babylon reports it as button 0, the\n // same numbering as a DOM PointerEvent.\n if (info.type === POINTER_EVENT_TYPES.down && (event?.button ?? 0) === 0) {\n this.pointer.down = true;\n }\n if (info.type === POINTER_EVENT_TYPES.up && (event?.button ?? 0) === 0) {\n this.pointer.down = false;\n }\n }\n\n private attachSession(): void {\n const manager = this.options.xr?.baseExperience?.sessionManager;\n const input = this.options.xr?.input;\n const republish = () => {\n this.refreshCapabilities();\n for (const listener of [...this.sourceListeners]) listener();\n };\n const subscribe = <T>(\n observable: { add(cb: (value: T) => void): unknown; remove(o: unknown): boolean } | undefined,\n handler: () => void,\n ): void => {\n if (!observable) return;\n const observer = observable.add(() => handler());\n this.detach.push(() => {\n observable.remove(observer);\n });\n };\n subscribe(manager?.onXRSessionInit, republish);\n subscribe(manager?.onXRSessionEnded, () => {\n this.sampled.clear();\n republish();\n });\n subscribe(input?.onControllerAddedObservable, republish);\n subscribe(input?.onControllerRemovedObservable, republish);\n }\n\n // -- capabilities -------------------------------------------------------------\n\n /** The live XR session, or null when none is running. */\n private session(): unknown {\n return this.options.xr?.baseExperience?.sessionManager?.session ?? null;\n }\n\n private controllers(): readonly BabylonXRControllerLike[] {\n return this.options.xr?.input?.controllers ?? [];\n }\n\n /** The hand-tracking feature, when the app enabled it. */\n private handTracking(): BabylonHandTrackingLike | null {\n const manager = this.options.xr?.baseExperience?.featuresManager;\n if (!manager) return null;\n const feature = manager.getEnabledFeature(HAND_TRACKING_FEATURE);\n if (!feature || typeof feature !== \"object\") return null;\n const candidate = feature as Partial<BabylonHandTrackingLike>;\n return typeof candidate.getHandByControllerId === \"function\"\n ? (feature as BabylonHandTrackingLike)\n : null;\n }\n\n private handFor(controller: BabylonXRControllerLike): BabylonXRHandLike | null {\n return this.handTracking()?.getHandByControllerId(controller.uniqueId) ?? null;\n }\n\n private refreshCapabilities(): void {\n const inSession = this.session() !== null;\n const desktop = !inSession && this.options.scene.onPointerObservable !== undefined;\n // Babylon reports no native grab observation, so grabs are always\n // poseOnly: the core drives the transform through the port.\n const next: InputCapabilities = {\n ...NO_CAPABILITIES,\n headPose: true,\n gaze: true,\n pointer2d: desktop,\n // The pointer fallback synthesises a grip on the ray, so hand-driven\n // behaviours negotiate on desktop too. Without this every one of them\n // switches itself off off-headset and only press remains usable.\n grabs: desktop ? \"poseOnly\" : NO_CAPABILITIES.grabs,\n // Set in the loop below: presence is true once Babylon has built at\n // least one visual this provider can hide - a motion controller root\n // mesh, or a hand mesh from the hand-tracking feature.\n presence: false,\n };\n if (inSession) {\n for (const controller of this.controllers()) {\n if (controller.pointer) next.rays = true;\n if (controller.grip ?? controller.pointer) next.grabs = \"poseOnly\";\n if (controller.inputSource.hand) {\n next.handJoints = true;\n next.pokes = true;\n }\n // Pinch strength needs the tracked hand itself, not just the flag on\n // the input source, so it is gated on the feature reporting a hand.\n if (this.handFor(controller)) next.pinch = true;\n if (controller.motionController) next.buttonsAxes = true;\n if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {\n next.haptics = true;\n }\n if (this.visualsFor(controller).length > 0) next.presence = true;\n }\n }\n const changed = JSON.stringify(next) !== JSON.stringify(this.capabilities);\n this.capabilities = next;\n if (changed) {\n for (const listener of [...this.capsListeners]) listener(next);\n }\n }\n\n getCapabilities(): InputCapabilities {\n return this.capabilities;\n }\n\n onCapabilitiesChanged(listener: (c: InputCapabilities) => void): Unsubscribe {\n this.capsListeners.add(listener);\n return () => this.capsListeners.delete(listener);\n }\n\n onSourcesChanged(listener: () => void): Unsubscribe {\n this.sourceListeners.add(listener);\n return () => this.sourceListeners.delete(listener);\n }\n\n // -- sampling -----------------------------------------------------------------\n\n sample(): readonly InputSourceSnapshot[] {\n this.sampled.clear();\n const session = this.session();\n if (session === null) {\n // A session that ended between frames leaves stale capabilities behind.\n if (this.capabilities.rays) this.refreshCapabilities();\n return this.samplePointer();\n }\n // A session that exists but is not visible (backgrounded, or the\n // browser's own \"content is obscured\" state) reports no sources at all,\n // the same rule IWSDK's provider applies to `world.visibilityState`.\n // Babylon's session manager passes the raw `XRSession` through, so its\n // standard `visibilityState` is read straight off it.\n const visibilityState = (session as { visibilityState?: string }).visibilityState;\n if (visibilityState !== undefined && visibilityState !== \"visible\") return [];\n\n const snapshots: InputSourceSnapshot[] = [];\n for (const controller of this.controllers()) {\n const handedness = normalizeHandedness(controller.inputSource.handedness);\n const hand = this.handFor(controller);\n const pointerNode = controller.pointer;\n const snapshot: InputSourceSnapshot = {\n id: controller.uniqueId,\n kind: controller.inputSource.hand ? \"hand\" : \"controller\",\n handedness,\n select: componentValue(\n controller.motionController?.getComponentOfType?.(TRIGGER_COMPONENT) ??\n controller.motionController?.getMainComponent?.() ??\n null,\n ),\n squeeze: componentValue(\n controller.motionController?.getComponentOfType?.(SQUEEZE_COMPONENT) ?? null,\n ),\n };\n if (pointerNode) {\n snapshot.ray = {\n origin: toVec3(pointerNode.getAbsolutePosition()) ?? [0, 0, 0],\n direction: nodeForward(pointerNode, this.rightHanded),\n };\n }\n // Babylon exposes the grip node only for a controller with one; a hand,\n // and a controller without a grip space, carry on the pointer node.\n const gripNode = controller.grip ?? pointerNode;\n if (gripNode) snapshot.gripPose = nodeWorldPose(gripNode);\n const tip = jointPosition(hand, INDEX_TIP_JOINT);\n if (tip) snapshot.indexTip = tip;\n // A controller has no fingertip: its index tip is its ray origin, as\n // IWSDK's input rig makes it, so a controller pokes as a finger does.\n else if (!controller.inputSource.hand && snapshot.ray) snapshot.indexTip = [...snapshot.ray.origin];\n if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {\n snapshot.hapticsAvailable = true;\n }\n // Babylon reports no per-pose velocity, so nothing is set here and the\n // core's VelocityTracker derives it from consecutive grip poses.\n snapshots.push(snapshot);\n this.sampled.set(snapshot.id, { handedness, controller });\n }\n return snapshots;\n }\n\n /**\n * The desktop pointer, shaped like the three.js adapter's mouse fallback:\n * one source, `handedness: \"none\"`, `kind: \"pointer2d\"`.\n */\n private samplePointer(): readonly InputSourceSnapshot[] {\n if (this.options.scene.onPointerObservable === undefined) return [];\n const ray = this.pointer.ray ?? this.pickRay();\n if (!ray) return [];\n const grip: Vec3Tuple = [\n ray.origin[0] + ray.direction[0] * this.gripDistance,\n ray.origin[1] + ray.direction[1] * this.gripDistance,\n ray.origin[2] + ray.direction[2] * this.gripDistance,\n ];\n return [\n {\n id: DESKTOP_SOURCE_ID,\n kind: \"pointer2d\",\n handedness: \"none\",\n ray: { origin: ray.origin, direction: ray.direction },\n gripPose: { position: grip, quaternion: toQuat(this.camera()?.absoluteRotation) },\n select: this.pointer.down ? 1 : 0,\n // A mouse has one button that means select. Squeeze stays 0 rather\n // than doubling the left button up as both, which would fire a press\n // and a grab together on an interactable that carries both.\n squeeze: 0,\n },\n ];\n }\n\n /** Pick the scene at the last known cursor position for a ray. */\n private pickRay(): { origin: Vec3Tuple; direction: Vec3Tuple } | null {\n const picked = this.options.scene.pick?.(this.pointer.x, this.pointer.y);\n const ray = picked?.ray;\n if (!ray) return null;\n return {\n origin: toVec3(ray.origin) ?? [0, 0, 0],\n direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),\n };\n }\n\n private camera(): BabylonCameraLike | null {\n return this.options.camera ?? this.options.scene.activeCamera ?? null;\n }\n\n getHeadPose(): HeadPose {\n const camera = this.camera();\n return {\n position: toVec3(camera?.globalPosition ?? camera?.position) ?? [0, 0, 0],\n quaternion: toQuat(camera?.absoluteRotation),\n };\n }\n\n // -- haptics ------------------------------------------------------------------\n\n /**\n * Babylon routes haptics through the motion controller rather than the\n * gamepad actuator, so a source with no motion controller cannot pulse\n * even when the gamepad reports an actuator.\n */\n pulse(sourceId: string, intensity: number, durationMs: number): boolean {\n const controller =\n this.sampled.get(sourceId)?.controller ??\n this.controllers().find((c) => c.uniqueId === sourceId);\n const motionController = controller?.motionController;\n if (!motionController?.pulse) return false;\n void motionController.pulse(Math.min(1, Math.max(0, intensity)), durationMs);\n return true;\n }\n\n // -- presence -----------------------------------------------------------------\n\n /**\n * Show or hide the visuals Babylon built for the targeted sides. Returns\n * false when the target has nothing to show or hide. `\"none\"` targets no\n * side, so it reports whether presence is usable at all without changing\n * anything.\n */\n setPresenceVisible(target: Handedness | \"all\", visible: boolean): boolean {\n if (target === \"none\") return this.capabilities.presence;\n let applied = false;\n for (const controller of this.controllers()) {\n const side = normalizeHandedness(controller.inputSource.handedness);\n if (target !== \"all\" && target !== side) continue;\n for (const node of this.visualsFor(controller)) {\n node.setEnabled?.(visible);\n applied = true;\n }\n }\n return applied;\n }\n\n /**\n * Always false. Babylon picks the visual per input source - a controller\n * gets its motion-controller model, a tracked hand gets the hand mesh -\n * and exposes no switch between the two. IWSDK builds both families, so\n * its adapter implements this.\n */\n setPresenceModality(_mode: PresenceModality): boolean {\n return false;\n }\n\n /** Every node that represents this controller on screen. */\n private visualsFor(controller: BabylonXRControllerLike): BabylonTransformNodeLike[] {\n const nodes: BabylonTransformNodeLike[] = [];\n const root = controller.motionController?.rootMesh;\n if (root) nodes.push(root);\n const handMesh = this.handFor(controller)?.handMesh;\n if (handMesh) nodes.push(handMesh);\n return nodes;\n }\n\n dispose(): void {\n for (const off of this.detach.splice(0)) off();\n this.sampled.clear();\n this.capsListeners.clear();\n this.sourceListeners.clear();\n }\n}\n\n/** Analog value where the component reports one, else the pressed flag. */\nfunction componentValue(\n component: { value?: number; pressed?: boolean } | null | undefined,\n): number {\n if (!component) return 0;\n if (typeof component.value === \"number\" && component.value > 0) return component.value;\n return component.pressed ? 1 : 0;\n}\n\nfunction jointPosition(\n hand: BabylonXRHandLike | null,\n joint: string,\n): Vec3Tuple | null {\n const mesh = hand?.getJointMesh?.(joint);\n return mesh ? toVec3(mesh.getAbsolutePosition()) : null;\n}\n\nfunction normalizeHandedness(value: string | undefined): \"left\" | \"right\" | \"none\" {\n return value === \"left\" || value === \"right\" ? value : \"none\";\n}\n"]}
@@ -50,6 +50,10 @@ export declare class BabylonTransformPort implements TransformPort {
50
50
  private restQuaternion;
51
51
  private restScale;
52
52
  private held;
53
+ /** The material's `emissiveColor` before any pulse, captured on first use. */
54
+ private baseEmissiveColor;
55
+ /** PBRMaterial's `emissiveIntensity` before any pulse, captured on first use - StandardMaterial has none. */
56
+ private baseEmissiveIntensity;
53
57
  readonly beginHold?: () => void;
54
58
  readonly endHold?: (release: HoldRelease) => void;
55
59
  constructor(node: BabylonTransformNodeLike, options?: BabylonTransformPortOptions);
@@ -79,15 +83,22 @@ export declare class BabylonTransformPort implements TransformPort {
79
83
  */
80
84
  setWorldPose(pose: PoseTuple): void;
81
85
  /**
82
- * Uniform scale about the rest scale. The emissive part of the intent is
83
- * not applied: reaching a material's emissive colour means knowing which
84
- * Babylon material the node carries, and this package holds no Babylon
85
- * types. Apps that want the glow subscribe to the core's feedback intents.
86
+ * Uniform scale about the rest scale, and - mirroring the IWSDK and
87
+ * three.js ports - a pulse of light: `emissiveColor` is scaled by
88
+ * `1 + effect.emissive` about its own base colour (StandardMaterial and
89
+ * PBRMaterial both carry it), and on a PBRMaterial `emissiveIntensity` is
90
+ * ALSO set to its base plus `effect.emissive`, the same additive rule
91
+ * three.js's `emissiveIntensity` follows - PBRMaterial's field is that
92
+ * one's Babylon analogue. A node with no material, or a material with no
93
+ * `emissiveColor`, is left alone. Base values are captured on first use,
94
+ * so a later `recaptureRest()`-style re-read is not needed for this part.
86
95
  */
87
96
  setEffect(effect: {
88
97
  scale?: number;
89
98
  emissive?: number;
90
99
  }): void;
100
+ /** The node's first material slot - `AbstractMesh.material`, single or the first of an array. */
101
+ private firstMaterial;
91
102
  /** Write a quaternion, creating the node's `rotationQuaternion` if needed. */
92
103
  private writeRotation;
93
104
  }
@@ -7,6 +7,10 @@ export class BabylonTransformPort {
7
7
  restQuaternion = [0, 0, 0, 1];
8
8
  restScale = [1, 1, 1];
9
9
  held = false;
10
+ /** The material's `emissiveColor` before any pulse, captured on first use. */
11
+ baseEmissiveColor = null;
12
+ /** PBRMaterial's `emissiveIntensity` before any pulse, captured on first use - StandardMaterial has none. */
13
+ baseEmissiveIntensity = null;
10
14
  beginHold;
11
15
  endHold;
12
16
  constructor(node, options = {}) {
@@ -106,22 +110,52 @@ export class BabylonTransformPort {
106
110
  }
107
111
  }
108
112
  /**
109
- * Uniform scale about the rest scale. The emissive part of the intent is
110
- * not applied: reaching a material's emissive colour means knowing which
111
- * Babylon material the node carries, and this package holds no Babylon
112
- * types. Apps that want the glow subscribe to the core's feedback intents.
113
+ * Uniform scale about the rest scale, and - mirroring the IWSDK and
114
+ * three.js ports - a pulse of light: `emissiveColor` is scaled by
115
+ * `1 + effect.emissive` about its own base colour (StandardMaterial and
116
+ * PBRMaterial both carry it), and on a PBRMaterial `emissiveIntensity` is
117
+ * ALSO set to its base plus `effect.emissive`, the same additive rule
118
+ * three.js's `emissiveIntensity` follows - PBRMaterial's field is that
119
+ * one's Babylon analogue. A node with no material, or a material with no
120
+ * `emissiveColor`, is left alone. Base values are captured on first use,
121
+ * so a later `recaptureRest()`-style re-read is not needed for this part.
113
122
  */
114
123
  setEffect(effect) {
115
- if (effect.scale === undefined)
116
- return;
117
- const scaling = this.node.scaling;
118
- if (!scaling)
119
- return;
120
- writeVec3(scaling, [
121
- this.restScale[0] * effect.scale,
122
- this.restScale[1] * effect.scale,
123
- this.restScale[2] * effect.scale,
124
- ]);
124
+ if (effect.scale !== undefined) {
125
+ const scaling = this.node.scaling;
126
+ if (scaling) {
127
+ writeVec3(scaling, [
128
+ this.restScale[0] * effect.scale,
129
+ this.restScale[1] * effect.scale,
130
+ this.restScale[2] * effect.scale,
131
+ ]);
132
+ }
133
+ }
134
+ if (effect.emissive !== undefined) {
135
+ const material = this.firstMaterial();
136
+ if (material?.emissiveColor) {
137
+ if (this.baseEmissiveColor === null)
138
+ this.baseEmissiveColor = toVec3(material.emissiveColor) ?? [0, 0, 0];
139
+ const factor = 1 + effect.emissive;
140
+ writeVec3(material.emissiveColor, [
141
+ this.baseEmissiveColor[0] * factor,
142
+ this.baseEmissiveColor[1] * factor,
143
+ this.baseEmissiveColor[2] * factor,
144
+ ]);
145
+ if (material.emissiveIntensity !== undefined) {
146
+ if (this.baseEmissiveIntensity === null)
147
+ this.baseEmissiveIntensity = material.emissiveIntensity;
148
+ material.emissiveIntensity = this.baseEmissiveIntensity + effect.emissive;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ /** The node's first material slot - `AbstractMesh.material`, single or the first of an array. */
154
+ firstMaterial() {
155
+ const material = this.node.material;
156
+ if (!material)
157
+ return null;
158
+ return Array.isArray(material) ? (material[0] ?? null) : material;
125
159
  }
126
160
  /** Write a quaternion, creating the node's `rotationQuaternion` if needed. */
127
161
  writeRotation(value) {
@@ -1 +1 @@
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"]}
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 BabylonMaterialLike,\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 /** The material's `emissiveColor` before any pulse, captured on first use. */\n private baseEmissiveColor: Vec3Tuple | null = null;\n /** PBRMaterial's `emissiveIntensity` before any pulse, captured on first use - StandardMaterial has none. */\n private baseEmissiveIntensity: number | null = null;\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, and - mirroring the IWSDK and\n * three.js ports - a pulse of light: `emissiveColor` is scaled by\n * `1 + effect.emissive` about its own base colour (StandardMaterial and\n * PBRMaterial both carry it), and on a PBRMaterial `emissiveIntensity` is\n * ALSO set to its base plus `effect.emissive`, the same additive rule\n * three.js's `emissiveIntensity` follows - PBRMaterial's field is that\n * one's Babylon analogue. A node with no material, or a material with no\n * `emissiveColor`, is left alone. Base values are captured on first use,\n * so a later `recaptureRest()`-style re-read is not needed for this part.\n */\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale !== undefined) {\n const scaling = this.node.scaling;\n if (scaling) {\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 if (effect.emissive !== undefined) {\n const material = this.firstMaterial();\n if (material?.emissiveColor) {\n if (this.baseEmissiveColor === null) this.baseEmissiveColor = toVec3(material.emissiveColor) ?? [0, 0, 0];\n const factor = 1 + effect.emissive;\n writeVec3(material.emissiveColor, [\n this.baseEmissiveColor[0] * factor,\n this.baseEmissiveColor[1] * factor,\n this.baseEmissiveColor[2] * factor,\n ]);\n if (material.emissiveIntensity !== undefined) {\n if (this.baseEmissiveIntensity === null) this.baseEmissiveIntensity = material.emissiveIntensity;\n material.emissiveIntensity = this.baseEmissiveIntensity + effect.emissive;\n }\n }\n }\n }\n\n /** The node's first material slot - `AbstractMesh.material`, single or the first of an array. */\n private firstMaterial(): BabylonMaterialLike | null {\n const material = this.node.material;\n if (!material) return null;\n return Array.isArray(material) ? (material[0] ?? null) : material;\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.1",
3
+ "version": "0.1.1-preview.2",
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.1"
37
+ "@realitycollective/webxr-interactions": "^0.1.1-preview.2"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",