@realitycollective/babylon-interactions 0.1.1-preview.2 → 0.1.1-preview.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/babylon-types.d.ts +104 -0
- package/dist/babylon-types.js +2 -0
- package/dist/babylon-types.js.map +1 -1
- package/dist/hit-tester.d.ts +6 -0
- package/dist/hit-tester.js +23 -1
- package/dist/hit-tester.js.map +1 -1
- package/dist/host.d.ts +36 -3
- package/dist/host.js +40 -2
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/physics-facility.d.ts +77 -0
- package/dist/physics-facility.js +199 -0
- package/dist/physics-facility.js.map +1 -0
- package/dist/provider.d.ts +16 -1
- package/dist/provider.js +49 -4
- package/dist/provider.js.map +1 -1
- package/dist/transform-port.d.ts +30 -2
- package/dist/transform-port.js +23 -0
- package/dist/transform-port.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,17 @@ 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` - the near-pointer rule, IWSDK 1.0.0's pointer stack restated as one core decision every binding runs (`near-pointer.ts`, `NEAR_POINTER_DEFAULTS`): a fingertip (or a controller's ray origin) within 0.15 m of a target's surface owns the source as the touch pointer (hysteresis out to 0.2 m), presses at 0.02 m arriving from outside and releases on the way out, never pressing from inside or behind; the grip is a 0.07 m grab sphere; per source the first of touch, grab, ray with a candidate is the active pointer, locked while it presses or grabs; and `InteractionRuntime` publishes per-source `PointerVisuals` (`onPointerVisuals`, `getPointerVisuals`, `getActivePointer`): the ray and its cursor show only while the ray owns the source or nothing does, with the cursor at the active pointer's hit. `InteractableDescriptor.pokeRadius` is now the touch hover enter distance for that target (default 0.15 m, was a 0.05 m proximity trigger). `nearPointerContractCases()` is the shared suite, run on every platform's hit tester.
|
|
12
|
+
- `@realitycollective/webxr-interactions` - the physics contract (`physics.ts`): `PhysicsFacility`, the platform physics behind one set of semantics and IWSDK 1.0.0's defaults (`PHYSICS_DEFAULTS`: gravity `[0, -9.81, 0]`, 60 steps per second with interpolation, a dynamic body with no damping and gravity factor 1, an auto shape with density 1, restitution 0 and friction 0.5), static and kinematic states, suspend for a hold, resume with the release velocity, teleport with velocity cleared. `physicsFacilityContractCases()` proves a facility falls and rests, bounces, holds, throws, teleports and disposes; `MemoryPhysicsFacility` is the engine-free reference. Every binding names a default engine and takes an app's own facility instead: `createThreeInteractions({ physics: { rapier } })` (Rapier, `RapierPhysicsFacility`, proved against the real engine), `connectXRBlocksInteractions({ physics: { rapier } })` (the Rapier XR Blocks bundles), `registerInteractions` (Havok through IWSDK's components, `IWSDKPhysicsFacility`), `createBabylonInteractions({ physics: { kit } })` (Havok, `BabylonPhysicsFacility`, proved against a fake), and the native `physics` slice (`NativePhysicsHost`, Jolt on Quest and Android, RealityKit on visionOS, with every obligation stated in `native-types.ts` and the whole shared suite in the host conformance kit). A registered object with a body drives the held pose through its facility; `register` takes `body` and `shape` specs.
|
|
13
|
+
- `@realitycollective/native-interactions` - `NativeInputHost.applyPointerVisuals(sourceId, visuals)`: the host is told each frame whether to draw a source's ray and where its cursor sits, and draws exactly that; the earlier rule that the host draws a cursor at every ray hit is withdrawn. The kit gains `input/the ray and cursor are drawn exactly as told`, the whole `nearPointerContractCases()` suite over the host's own queries, and the physics cases.
|
|
14
|
+
- `@realitycollective/iwsdk-interactions` - `test/near-pointer-reference.test.ts` reads the touch and grab constants off IWSDK 1.0.0's own pointer classes and drives the core touch machine beside `@pmndrs/pointer-events`' press rule, recording the one frame they differ on by design (a fingertip first seen inside the press band).
|
|
15
|
+
- `@realitycollective/xrblocks-interactions` - the provider reads the XR Blocks camera's head pose through real three.js scratch objects; a plain object handed to `getWorldPosition` threw against the real camera.
|
|
16
|
+
- Eye gaze and pinch, on every platform, following Meta IWSDK 1.0.0's gaze-and-pinch interaction (the primary input on Meta VR Glasses). The provider half of the rule is `@realitycollective/webxr-input` 0.1.7's `EyeGazeInput`; this repository applies it in every provider and adds the targeting half to the core. `@realitycollective/webxr-interactions` - `HitTester` gains an optional `hitCone(ray, halfAngle, maxLength)`, IWSDK's `GazeConecaster` query (a target the ray reaches wins outright, otherwise the nearest to the axis within the cone, the nearer on a tie within half a degree), with `coneHitForSpheres` for sphere-based testers and `GazeConsensus`, the time-weighted dwell vote over IWSDK's 0.15 s window. `InteractionRuntime` takes `eyeGaze` options (`EYE_GAZE_DEFAULTS`: 5 degree cone, 30 m, 0.15 s, suppression on, follow the hand on) and, when the provider reports `capabilities.eyeGaze`, targets the gaze source through the cone and the consensus, targets nothing while a hand's near pointer is hovering or grabbing, satisfies `gaze.required` and dwell from the eye gaze, presses or grabs the gazed target when the owning hand pinches, and from the next frame drives the gaze source's ray from the pinching hand aimed at the gaze hit (`selectorPose`), so `onSample` listeners such as the pointer bridge and every behaviour see a drag that follows the hand. `hitTesterContractCases()` gains five `hitCone` cases that run when a tester has one. Without `eyeGaze` nothing changes: a gaze-kind source still targets by plain ray as head gaze does.
|
|
17
|
+
- `@realitycollective/iwsdk-interactions` - `IWSDKInputProvider` reports `eyeGaze` when the session carries a gaze input source, reads IWSDK's own sampled eye pose (`xrOrigin.eyeSpace` while `gazeOrigin` is `"tracked"`) and applies `EyeGazeInput`, so it hands far targeting to gaze exactly when IWSDK's `GazePointer` does; `EntityHitTester` answers `hitCone` over its spheres. `test/gaze-reference.test.ts` drives IWSDK's `FilteredEyeGaze` and `GazeConecaster` side by side with the core's filter and consensus.
|
|
18
|
+
- `@realitycollective/threejs-interactions` (reused by `@realitycollective/xrblocks-interactions`) - `WebXRInputProvider` finds the `targetRayMode === "gaze"` source, poses it against the reference space and applies `EyeGazeInput`; `ThreeHitTester.hitCone` tries the raycast first and then the cone over the registered roots' bounding spheres.
|
|
19
|
+
- `@realitycollective/babylon-interactions` - `BabylonInputProvider` reads the `xr-eye-tracking` feature (`WebXREyeTracking.getEyeGaze()`) when the app enabled it and applies `EyeGazeInput`; `BabylonHitTester.hitCone` runs the cone over its spheres.
|
|
20
|
+
- `@realitycollective/native-interactions` - `NativeInputFacts.eyeTracking` (an eye-gaze source is present: OpenXR `XR_EXT_eye_gaze_interaction` bound and active) and `NativeInputHost.getEyeGazePose()` (this frame's gaze target-ray pose, world space, or null) let a native host report the facts while the binding applies the rule: `eyeGaze` is derived, far rays are dropped, the pinch selects, and the selector pose is built from the hand's ray. The `interactions` slice gains an optional `hitCone` the host answers over its own scene, forwarded by `NativeHitTester`. The host conformance kit gains `input/eye gaze` cases: a host that reports eye tracking must pose the gaze, and a hidden target never answers a cone query.
|
|
21
|
+
|
|
11
22
|
- `@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
23
|
- `@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.
|
|
13
24
|
- `@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.
|
package/dist/babylon-types.d.ts
CHANGED
|
@@ -82,6 +82,13 @@ export interface BabylonTransformNodeLike {
|
|
|
82
82
|
physicsBody?: BabylonPhysicsBodyLike;
|
|
83
83
|
/** `AbstractMesh.material` - a multi-material mesh's first slot is what `setEffect`'s emissive pulse lights. */
|
|
84
84
|
material?: BabylonMaterialLike | BabylonMaterialLike[] | null;
|
|
85
|
+
/**
|
|
86
|
+
* `AbstractMesh.getTotalVertices()` - absent, or zero, on a plain
|
|
87
|
+
* `TransformNode` with no geometry of its own. `BabylonPhysicsFacility`'s
|
|
88
|
+
* `"auto"` shape reads this to tell a real mesh from an empty node, the
|
|
89
|
+
* same distinction `RapierPhysicsFacility` makes from an object's bounds.
|
|
90
|
+
*/
|
|
91
|
+
getTotalVertices?(): number;
|
|
85
92
|
}
|
|
86
93
|
/**
|
|
87
94
|
* Structural slice of Babylon's `PhysicsBody` (Physics V2, Havok or any
|
|
@@ -99,12 +106,97 @@ export interface BabylonTransformNodeLike {
|
|
|
99
106
|
* node-drives-physics - Babylon reads the node's transform instead of
|
|
100
107
|
* writing it. Held, released and reset all briefly need the second
|
|
101
108
|
* direction, so the port toggles it around each of the three.
|
|
109
|
+
*
|
|
110
|
+
* The read/damping/gravity/target members below back `BabylonPhysicsFacility`
|
|
111
|
+
* (`physics-facility.ts`), which needs a body's current state and the
|
|
112
|
+
* ability to teleport it, not only switch it - `BabylonTransformPort`'s own
|
|
113
|
+
* physicsMotionTypes hold does not need them.
|
|
102
114
|
*/
|
|
103
115
|
export interface BabylonPhysicsBodyLike {
|
|
104
116
|
disablePreStep: boolean;
|
|
117
|
+
/** `PhysicsBody.shape` - `Nullable<PhysicsShape>`, the collider driving this body; settable to swap it. */
|
|
118
|
+
shape?: BabylonPhysicsShapeLike | null;
|
|
105
119
|
setMotionType(motionType: unknown): void;
|
|
120
|
+
/** `PhysicsBody.getMotionType()` - the value last given to `setMotionType`. */
|
|
121
|
+
getMotionType(): unknown;
|
|
106
122
|
setLinearVelocity(velocity: BabylonVector3Like): void;
|
|
123
|
+
/** `PhysicsBody.getLinearVelocity()` - metres per second, world space. */
|
|
124
|
+
getLinearVelocity(): BabylonVector3Like;
|
|
107
125
|
setAngularVelocity(velocity: BabylonVector3Like): void;
|
|
126
|
+
/** `PhysicsBody.getAngularVelocity()` - radians per second, world space. */
|
|
127
|
+
getAngularVelocity(): BabylonVector3Like;
|
|
128
|
+
/** `PhysicsBody.setGravityFactor()` - multiplier on the world's gravity for this body. */
|
|
129
|
+
setGravityFactor(factor: number): void;
|
|
130
|
+
/** `PhysicsBody.setLinearDamping()` - fraction of linear velocity lost per second. */
|
|
131
|
+
setLinearDamping(damping: number): void;
|
|
132
|
+
/** `PhysicsBody.setAngularDamping()` - fraction of angular velocity lost per second. */
|
|
133
|
+
setAngularDamping(damping: number): void;
|
|
134
|
+
/**
|
|
135
|
+
* `PhysicsBody.setTargetTransform()` - Babylon's teleport for a body: sets
|
|
136
|
+
* where the body (and, for an `ANIMATED` one, its kinematic target) is
|
|
137
|
+
* next, in world space, independent of the node's own parent frame.
|
|
138
|
+
*/
|
|
139
|
+
setTargetTransform(position: BabylonVector3Like, rotation: BabylonQuaternionLike): void;
|
|
140
|
+
/** `PhysicsBody.dispose()` - releases the body's Havok handle. */
|
|
141
|
+
dispose(): void;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Structural slice of Babylon's `PhysicsShape` (a `PhysicsShapeSphere`,
|
|
145
|
+
* `PhysicsShapeBox`, `PhysicsShapeCapsule` or `PhysicsShapeMesh` - every
|
|
146
|
+
* kind shares this surface). `material` is a plain data object Havok reads
|
|
147
|
+
* each step, not a class - Babylon's own `PhysicsShape.material` setter
|
|
148
|
+
* takes the same shape.
|
|
149
|
+
*/
|
|
150
|
+
export interface BabylonPhysicsShapeLike {
|
|
151
|
+
material: {
|
|
152
|
+
friction: number;
|
|
153
|
+
restitution: number;
|
|
154
|
+
};
|
|
155
|
+
/** Kilograms per cubic metre; `PhysicsBody`'s mass follows from it. */
|
|
156
|
+
density: number;
|
|
157
|
+
/** `PhysicsShape.dispose()` - releases the shape's Havok handle. */
|
|
158
|
+
dispose(): void;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The Havok-era Physics V2 constructors and values `BabylonPhysicsFacility`
|
|
162
|
+
* needs, handed over by the app exactly as `BabylonTransformPortOptions.physicsMotionTypes`
|
|
163
|
+
* already does for the two motion types it reads - this package has no
|
|
164
|
+
* `@babylonjs/core` dependency to import them from.
|
|
165
|
+
*/
|
|
166
|
+
export interface BabylonPhysicsKitLike {
|
|
167
|
+
PhysicsBody: new (node: BabylonTransformNodeLike, motionType: unknown, startsAsleep: boolean, scene: BabylonSceneLike) => BabylonPhysicsBodyLike;
|
|
168
|
+
PhysicsShapeSphere: new (center: BabylonVector3Like, radius: number, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;
|
|
169
|
+
PhysicsShapeBox: new (center: BabylonVector3Like, rotation: BabylonQuaternionLike, extents: BabylonVector3Like, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;
|
|
170
|
+
/** `pointA`/`pointB` are the capsule's two hemisphere centres along its axis. */
|
|
171
|
+
PhysicsShapeCapsule: new (pointA: BabylonVector3Like, pointB: BabylonVector3Like, radius: number, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;
|
|
172
|
+
/** For an `"auto"` shape over a node with geometry - see `getTotalVertices` above. */
|
|
173
|
+
PhysicsShapeMesh: new (mesh: BabylonTransformNodeLike, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;
|
|
174
|
+
PhysicsMotionType: {
|
|
175
|
+
STATIC: unknown;
|
|
176
|
+
ANIMATED: unknown;
|
|
177
|
+
DYNAMIC: unknown;
|
|
178
|
+
};
|
|
179
|
+
Vector3: new (x: number, y: number, z: number) => BabylonVector3Like;
|
|
180
|
+
Quaternion: new (x: number, y: number, z: number, w: number) => BabylonQuaternionLike;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Structural slice of Babylon's `IPhysicsEngine` - the plugin-level physics
|
|
184
|
+
* world (Havok, or another plugin behind the same API) a scene owns once the
|
|
185
|
+
* app calls `scene.enablePhysics(...)`.
|
|
186
|
+
*/
|
|
187
|
+
export interface BabylonPhysicsEngineLike {
|
|
188
|
+
setGravity(gravity: BabylonVector3Like): void;
|
|
189
|
+
gravity: BabylonVector3Like;
|
|
190
|
+
/**
|
|
191
|
+
* Babylon's own per-frame physics step, called internally by
|
|
192
|
+
* `Scene.render()` once physics is enabled - not part of the public API,
|
|
193
|
+
* named here only so `BabylonPhysicsFacility.step` can advance the
|
|
194
|
+
* simulation directly where nothing drives a render loop (a test, or an
|
|
195
|
+
* app that ticks physics outside Babylon's own loop). Babylon normally
|
|
196
|
+
* steps physics itself; a facility never needs to call this in a real
|
|
197
|
+
* running scene with a render loop.
|
|
198
|
+
*/
|
|
199
|
+
_step?(dtSeconds: number): void;
|
|
108
200
|
}
|
|
109
201
|
/** Structural slice of Babylon's `Camera`. */
|
|
110
202
|
export interface BabylonCameraLike {
|
|
@@ -160,6 +252,8 @@ export interface BabylonSceneLike {
|
|
|
160
252
|
pick?(x: number, y: number): BabylonPickingInfoLike | null;
|
|
161
253
|
activeCamera?: BabylonCameraLike | null;
|
|
162
254
|
getEngine?(): BabylonEngineLike;
|
|
255
|
+
/** `Scene.getPhysicsEngine()` - null until the app calls `scene.enablePhysics(...)`. */
|
|
256
|
+
getPhysicsEngine?(): BabylonPhysicsEngineLike | null;
|
|
163
257
|
}
|
|
164
258
|
/** Structural slice of one `WebXRControllerComponent` reading. */
|
|
165
259
|
export interface BabylonMotionControllerComponentLike {
|
|
@@ -222,6 +316,16 @@ export interface BabylonXRExperienceLike {
|
|
|
222
316
|
}
|
|
223
317
|
/** The name Babylon registers hand tracking under in the features manager. */
|
|
224
318
|
export declare const HAND_TRACKING_FEATURE = "xr-hand-tracking";
|
|
319
|
+
/** The name Babylon registers eye tracking under in the features manager (`WebXREyeTracking.Name`). */
|
|
320
|
+
export declare const EYE_TRACKING_FEATURE = "xr-eye-tracking";
|
|
321
|
+
/**
|
|
322
|
+
* Structural slice of `WebXREyeTracking`: `isEyeGazeValid` says the runtime
|
|
323
|
+
* posed the gaze this frame, `getEyeGaze()` is the gaze ray, world space.
|
|
324
|
+
*/
|
|
325
|
+
export interface BabylonEyeTrackingLike {
|
|
326
|
+
isEyeGazeValid?: boolean;
|
|
327
|
+
getEyeGaze(): BabylonRayLike | null | undefined;
|
|
328
|
+
}
|
|
225
329
|
/** Index fingertip joint, as WebXR and Babylon both spell it. */
|
|
226
330
|
export declare const INDEX_TIP_JOINT = "index-finger-tip";
|
|
227
331
|
/** Copy a Babylon vector into a tuple. */
|
package/dist/babylon-types.js
CHANGED
|
@@ -10,6 +10,8 @@ export const POINTER_EVENT_TYPES = {
|
|
|
10
10
|
};
|
|
11
11
|
/** The name Babylon registers hand tracking under in the features manager. */
|
|
12
12
|
export const HAND_TRACKING_FEATURE = "xr-hand-tracking";
|
|
13
|
+
/** The name Babylon registers eye tracking under in the features manager (`WebXREyeTracking.Name`). */
|
|
14
|
+
export const EYE_TRACKING_FEATURE = "xr-eye-tracking";
|
|
13
15
|
/** Index fingertip joint, as WebXR and Babylon both spell it. */
|
|
14
16
|
export const INDEX_TIP_JOINT = "index-finger-tip";
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -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 `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"]}
|
|
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 * `AbstractMesh.getTotalVertices()` - absent, or zero, on a plain\n * `TransformNode` with no geometry of its own. `BabylonPhysicsFacility`'s\n * `\"auto\"` shape reads this to tell a real mesh from an empty node, the\n * same distinction `RapierPhysicsFacility` makes from an object's bounds.\n */\n getTotalVertices?(): number;\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 *\n * The read/damping/gravity/target members below back `BabylonPhysicsFacility`\n * (`physics-facility.ts`), which needs a body's current state and the\n * ability to teleport it, not only switch it - `BabylonTransformPort`'s own\n * physicsMotionTypes hold does not need them.\n */\nexport interface BabylonPhysicsBodyLike {\n disablePreStep: boolean;\n /** `PhysicsBody.shape` - `Nullable<PhysicsShape>`, the collider driving this body; settable to swap it. */\n shape?: BabylonPhysicsShapeLike | null;\n setMotionType(motionType: unknown): void;\n /** `PhysicsBody.getMotionType()` - the value last given to `setMotionType`. */\n getMotionType(): unknown;\n setLinearVelocity(velocity: BabylonVector3Like): void;\n /** `PhysicsBody.getLinearVelocity()` - metres per second, world space. */\n getLinearVelocity(): BabylonVector3Like;\n setAngularVelocity(velocity: BabylonVector3Like): void;\n /** `PhysicsBody.getAngularVelocity()` - radians per second, world space. */\n getAngularVelocity(): BabylonVector3Like;\n /** `PhysicsBody.setGravityFactor()` - multiplier on the world's gravity for this body. */\n setGravityFactor(factor: number): void;\n /** `PhysicsBody.setLinearDamping()` - fraction of linear velocity lost per second. */\n setLinearDamping(damping: number): void;\n /** `PhysicsBody.setAngularDamping()` - fraction of angular velocity lost per second. */\n setAngularDamping(damping: number): void;\n /**\n * `PhysicsBody.setTargetTransform()` - Babylon's teleport for a body: sets\n * where the body (and, for an `ANIMATED` one, its kinematic target) is\n * next, in world space, independent of the node's own parent frame.\n */\n setTargetTransform(position: BabylonVector3Like, rotation: BabylonQuaternionLike): void;\n /** `PhysicsBody.dispose()` - releases the body's Havok handle. */\n dispose(): void;\n}\n\n/**\n * Structural slice of Babylon's `PhysicsShape` (a `PhysicsShapeSphere`,\n * `PhysicsShapeBox`, `PhysicsShapeCapsule` or `PhysicsShapeMesh` - every\n * kind shares this surface). `material` is a plain data object Havok reads\n * each step, not a class - Babylon's own `PhysicsShape.material` setter\n * takes the same shape.\n */\nexport interface BabylonPhysicsShapeLike {\n material: { friction: number; restitution: number };\n /** Kilograms per cubic metre; `PhysicsBody`'s mass follows from it. */\n density: number;\n /** `PhysicsShape.dispose()` - releases the shape's Havok handle. */\n dispose(): void;\n}\n\n/**\n * The Havok-era Physics V2 constructors and values `BabylonPhysicsFacility`\n * needs, handed over by the app exactly as `BabylonTransformPortOptions.physicsMotionTypes`\n * already does for the two motion types it reads - this package has no\n * `@babylonjs/core` dependency to import them from.\n */\nexport interface BabylonPhysicsKitLike {\n PhysicsBody: new (\n node: BabylonTransformNodeLike,\n motionType: unknown,\n startsAsleep: boolean,\n scene: BabylonSceneLike,\n ) => BabylonPhysicsBodyLike;\n PhysicsShapeSphere: new (center: BabylonVector3Like, radius: number, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;\n PhysicsShapeBox: new (\n center: BabylonVector3Like,\n rotation: BabylonQuaternionLike,\n extents: BabylonVector3Like,\n scene: BabylonSceneLike,\n ) => BabylonPhysicsShapeLike;\n /** `pointA`/`pointB` are the capsule's two hemisphere centres along its axis. */\n PhysicsShapeCapsule: new (\n pointA: BabylonVector3Like,\n pointB: BabylonVector3Like,\n radius: number,\n scene: BabylonSceneLike,\n ) => BabylonPhysicsShapeLike;\n /** For an `\"auto\"` shape over a node with geometry - see `getTotalVertices` above. */\n PhysicsShapeMesh: new (mesh: BabylonTransformNodeLike, scene: BabylonSceneLike) => BabylonPhysicsShapeLike;\n PhysicsMotionType: { STATIC: unknown; ANIMATED: unknown; DYNAMIC: unknown };\n Vector3: new (x: number, y: number, z: number) => BabylonVector3Like;\n Quaternion: new (x: number, y: number, z: number, w: number) => BabylonQuaternionLike;\n}\n\n/**\n * Structural slice of Babylon's `IPhysicsEngine` - the plugin-level physics\n * world (Havok, or another plugin behind the same API) a scene owns once the\n * app calls `scene.enablePhysics(...)`.\n */\nexport interface BabylonPhysicsEngineLike {\n setGravity(gravity: BabylonVector3Like): void;\n gravity: BabylonVector3Like;\n /**\n * Babylon's own per-frame physics step, called internally by\n * `Scene.render()` once physics is enabled - not part of the public API,\n * named here only so `BabylonPhysicsFacility.step` can advance the\n * simulation directly where nothing drives a render loop (a test, or an\n * app that ticks physics outside Babylon's own loop). Babylon normally\n * steps physics itself; a facility never needs to call this in a real\n * running scene with a render loop.\n */\n _step?(dtSeconds: number): 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 /** `Scene.getPhysicsEngine()` - null until the app calls `scene.enablePhysics(...)`. */\n getPhysicsEngine?(): BabylonPhysicsEngineLike | null;\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/** The name Babylon registers eye tracking under in the features manager (`WebXREyeTracking.Name`). */\nexport const EYE_TRACKING_FEATURE = \"xr-eye-tracking\";\n\n/**\n * Structural slice of `WebXREyeTracking`: `isEyeGazeValid` says the runtime\n * posed the gaze this frame, `getEyeGaze()` is the gaze ray, world space.\n */\nexport interface BabylonEyeTrackingLike {\n isEyeGazeValid?: boolean;\n getEyeGaze(): BabylonRayLike | null | undefined;\n}\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"]}
|
package/dist/hit-tester.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export declare class BabylonHitTester implements HitTester {
|
|
|
52
52
|
getNode(id: string): BabylonTransformNodeLike | undefined;
|
|
53
53
|
hitRay(ray: RayTuple): InteractableHit | null;
|
|
54
54
|
hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null;
|
|
55
|
+
/**
|
|
56
|
+
* The eye-gaze cone (`ports.ts`, `hitCone`) over the registered spheres,
|
|
57
|
+
* the core's `coneHitForSpheres`. With an app-supplied `pickWithRay`, a
|
|
58
|
+
* mesh the ray itself reaches wins first, as it does for `hitRay`.
|
|
59
|
+
*/
|
|
60
|
+
hitCone(ray: RayTuple, halfAngle: number, maxLength: number): InteractableHit | null;
|
|
55
61
|
/** The registered interactable a picked node belongs to, if any. */
|
|
56
62
|
private ownerOf;
|
|
57
63
|
}
|
package/dist/hit-tester.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rayPointDistance, } from "@realitycollective/webxr-interactions";
|
|
1
|
+
import { coneHitForSpheres, rayPointDistance, } from "@realitycollective/webxr-interactions";
|
|
2
2
|
import { nodeShowing, parentOf, toVec3 } from "./babylon-types.js";
|
|
3
3
|
const DEFAULT_RADIUS = 0.1;
|
|
4
4
|
const DEFAULT_MAX_RAY_DISTANCE = 100;
|
|
@@ -75,6 +75,28 @@ export class BabylonHitTester {
|
|
|
75
75
|
}
|
|
76
76
|
return best;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The eye-gaze cone (`ports.ts`, `hitCone`) over the registered spheres,
|
|
80
|
+
* the core's `coneHitForSpheres`. With an app-supplied `pickWithRay`, a
|
|
81
|
+
* mesh the ray itself reaches wins first, as it does for `hitRay`.
|
|
82
|
+
*/
|
|
83
|
+
hitCone(ray, halfAngle, maxLength) {
|
|
84
|
+
if (this.pickWithRay) {
|
|
85
|
+
const direct = this.hitRay(ray);
|
|
86
|
+
if (direct && direct.distance <= maxLength)
|
|
87
|
+
return direct;
|
|
88
|
+
}
|
|
89
|
+
const spheres = [];
|
|
90
|
+
for (const [id, { node, radius }] of this.entries) {
|
|
91
|
+
if (!nodeShowing(node))
|
|
92
|
+
continue;
|
|
93
|
+
const center = toVec3(node.getAbsolutePosition());
|
|
94
|
+
if (center)
|
|
95
|
+
spheres.push({ id, center, radius });
|
|
96
|
+
}
|
|
97
|
+
const hit = coneHitForSpheres(ray, spheres, halfAngle, maxLength);
|
|
98
|
+
return hit ? { interactableId: hit.interactableId, distance: hit.distance, point: hit.point } : null;
|
|
99
|
+
}
|
|
78
100
|
/** The registered interactable a picked node belongs to, if any. */
|
|
79
101
|
ownerOf(node) {
|
|
80
102
|
let current = node;
|
package/dist/hit-tester.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hit-tester.js","sourceRoot":"","sources":["../src/hit-tester.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,gBAAgB,GAGjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAiC,MAAM,oBAAoB,CAAC;AA+BlG,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAG,IAAI,GAAG,EAA8D,CAAC;IAChF,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;IACrD,aAAa,CAAS;IACtB,cAAc,CAAS;IAChC,WAAW,CAA4B;IAE/C,YAAY,UAAmC,EAAE;QAC/C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC;QAC7D,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;QACzE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;IACjD,CAAC;IAED,mEAAmE;IACnE,cAAc,CAAC,IAA+B;QAC5C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,QAAQ,CAAC,EAAU,EAAE,IAA8B,EAAE,MAAe;QAClE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACpB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,KAAK;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,GAAa;QAClB,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,iEAAiE;QACjE,wDAAwD;QACxD,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YAChF,IAAI,CAAC,MAAM,EAAE,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,OAAO,EAAE,KAAK,IAAI;gBAChB,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7E,CAAC;QAED,IAAI,IAAI,GAA2B,IAAI,CAAC;QACxC,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;gBAAE,SAAS;YACjC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrD,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxE,IAAI,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;YACpD,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,YAAY,CAAC,KAAgB,EAAE,MAAc;QAC3C,IAAI,IAAI,GAA2B,IAAI,CAAC;QACxC,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;gBAAE,SAAS;YACjC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE;gBAAE,SAAS;YAClB,MAAM,QAAQ,GACZ,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC;YAClF,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtE,IAAI,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YAC5E,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oEAAoE;IAC5D,OAAO,CAAC,IAA8B;QAC5C,IAAI,OAAO,GAAoC,IAAI,CAAC;QACpD,OAAO,OAAO,EAAE,CAAC;YACf,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpC,IAAI,EAAE,KAAK,SAAS;gBAAE,OAAO,EAAE,CAAC;YAChC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["/**\n * BabylonHitTester - resolves the core's ray and proximity queries against\n * registered Babylon nodes.\n *\n * The default test needs no Babylon raycaster: each registered node carries\n * a radius, and a query is a sphere test against the node's absolute\n * position. That is the same model the IWSDK adapter uses, and it is enough\n * for buttons, levers, dials and grabbables, which are small next to the\n * distance they are pointed at from.\n *\n * An app that wants mesh-accurate picking supplies `pickWithRay`, typically\n * a one-line wrapper over `scene.pickWithRay`. Whatever mesh it returns is\n * mapped back to an interactable id through the registration map, walking up\n * parents so a pick on a child mesh still resolves to the registered root.\n */\nimport type { RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n rayPointDistance,\n type HitTester,\n type InteractableHit,\n} from \"@realitycollective/webxr-interactions\";\nimport { nodeShowing, parentOf, toVec3, type BabylonTransformNodeLike } from \"./babylon-types.js\";\n\n/** What an app's mesh-accurate pick reports back. */\nexport interface BabylonPickResult {\n /** The node that was hit. Mapped to an interactable through registration. */\n mesh: BabylonTransformNodeLike | null | undefined;\n /** Distance from the ray origin, in metres. */\n distance: number;\n /** World-space hit point. */\n point: Vec3Tuple;\n}\n\n/**\n * A mesh-accurate pick supplied by the app - `scene.pickWithRay` with a Ray\n * built from these arguments. Return null for a miss.\n */\nexport type BabylonPickWithRay = (\n origin: Vec3Tuple,\n direction: Vec3Tuple,\n maxDistance: number,\n) => BabylonPickResult | null;\n\nexport interface BabylonHitTesterOptions {\n /** Targeting radius for a node registered without one. Default 0.1 m. */\n defaultRadius?: number;\n /** Ray length handed to `pickWithRay`, in metres. Default 100. */\n maxRayDistance?: number;\n /** Optional mesh-accurate pick. Absent, the sphere test is used. */\n pickWithRay?: BabylonPickWithRay;\n}\n\nconst DEFAULT_RADIUS = 0.1;\nconst DEFAULT_MAX_RAY_DISTANCE = 100;\n\nexport class BabylonHitTester implements HitTester {\n private readonly entries = new Map<string, { node: BabylonTransformNodeLike; radius: number }>();\n private readonly byNode = new Map<BabylonTransformNodeLike, string>();\n private readonly defaultRadius: number;\n private readonly maxRayDistance: number;\n private pickWithRay: BabylonPickWithRay | null;\n\n constructor(options: BabylonHitTesterOptions = {}) {\n this.defaultRadius = options.defaultRadius ?? DEFAULT_RADIUS;\n this.maxRayDistance = options.maxRayDistance ?? DEFAULT_MAX_RAY_DISTANCE;\n this.pickWithRay = options.pickWithRay ?? null;\n }\n\n /** Supply or replace the mesh-accurate pick after construction. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.pickWithRay = pick;\n }\n\n register(id: string, node: BabylonTransformNodeLike, radius?: number): void {\n this.unregister(id);\n this.entries.set(id, { node, radius: radius ?? this.defaultRadius });\n this.byNode.set(node, id);\n }\n\n unregister(id: string): void {\n const entry = this.entries.get(id);\n if (entry) this.byNode.delete(entry.node);\n this.entries.delete(id);\n }\n\n getNode(id: string): BabylonTransformNodeLike | undefined {\n return this.entries.get(id)?.node;\n }\n\n hitRay(ray: RayTuple): InteractableHit | null {\n // An app-supplied pick REPLACES the sphere test for rays - it does not\n // sit in front of it. A miss, or a hit on scenery, is the answer: the\n // app's own pick already said what the ray reached first, and testing\n // spheres behind it would target something the ray never got to.\n // Proximity queries stay on the sphere test either way.\n if (this.pickWithRay) {\n const picked = this.pickWithRay(ray.origin, ray.direction, this.maxRayDistance);\n if (!picked?.mesh) return null;\n const id = this.ownerOf(picked.mesh);\n return id === null\n ? null\n : { interactableId: id, distance: picked.distance, point: picked.point };\n }\n\n let best: InteractableHit | null = null;\n for (const [id, { node, radius }] of this.entries) {\n if (!nodeShowing(node)) continue;\n const point = toVec3(node.getAbsolutePosition());\n if (!point) continue;\n const { distance, t } = rayPointDistance(ray, point);\n if (t > 0 && distance <= radius && (best === null || t < best.distance)) {\n best = { interactableId: id, distance: t, point };\n }\n }\n return best;\n }\n\n hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const [id, { node, radius: targetRadius }] of this.entries) {\n if (!nodeShowing(node)) continue;\n const at = toVec3(node.getAbsolutePosition());\n if (!at) continue;\n const distance =\n Math.hypot(at[0] - point[0], at[1] - point[1], at[2] - point[2]) - targetRadius;\n if (distance <= radius && (best === null || distance < best.distance)) {\n best = { interactableId: id, distance: Math.max(0, distance), point: at };\n }\n }\n return best;\n }\n\n /** The registered interactable a picked node belongs to, if any. */\n private ownerOf(node: BabylonTransformNodeLike): string | null {\n let current: BabylonTransformNodeLike | null = node;\n while (current) {\n const id = this.byNode.get(current);\n if (id !== undefined) return id;\n current = parentOf(current);\n }\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hit-tester.js","sourceRoot":"","sources":["../src/hit-tester.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonHitTester - resolves the core's ray and proximity queries against\n * registered Babylon nodes.\n *\n * The default test needs no Babylon raycaster: each registered node carries\n * a radius, and a query is a sphere test against the node's absolute\n * position. That is the same model the IWSDK adapter uses, and it is enough\n * for buttons, levers, dials and grabbables, which are small next to the\n * distance they are pointed at from.\n *\n * An app that wants mesh-accurate picking supplies `pickWithRay`, typically\n * a one-line wrapper over `scene.pickWithRay`. Whatever mesh it returns is\n * mapped back to an interactable id through the registration map, walking up\n * parents so a pick on a child mesh still resolves to the registered root.\n */\nimport type { RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n coneHitForSpheres,\n rayPointDistance,\n type HitTester,\n type InteractableHit,\n type SphereTarget,\n} from \"@realitycollective/webxr-interactions\";\nimport { nodeShowing, parentOf, toVec3, type BabylonTransformNodeLike } from \"./babylon-types.js\";\n\n/** What an app's mesh-accurate pick reports back. */\nexport interface BabylonPickResult {\n /** The node that was hit. Mapped to an interactable through registration. */\n mesh: BabylonTransformNodeLike | null | undefined;\n /** Distance from the ray origin, in metres. */\n distance: number;\n /** World-space hit point. */\n point: Vec3Tuple;\n}\n\n/**\n * A mesh-accurate pick supplied by the app - `scene.pickWithRay` with a Ray\n * built from these arguments. Return null for a miss.\n */\nexport type BabylonPickWithRay = (\n origin: Vec3Tuple,\n direction: Vec3Tuple,\n maxDistance: number,\n) => BabylonPickResult | null;\n\nexport interface BabylonHitTesterOptions {\n /** Targeting radius for a node registered without one. Default 0.1 m. */\n defaultRadius?: number;\n /** Ray length handed to `pickWithRay`, in metres. Default 100. */\n maxRayDistance?: number;\n /** Optional mesh-accurate pick. Absent, the sphere test is used. */\n pickWithRay?: BabylonPickWithRay;\n}\n\nconst DEFAULT_RADIUS = 0.1;\nconst DEFAULT_MAX_RAY_DISTANCE = 100;\n\nexport class BabylonHitTester implements HitTester {\n private readonly entries = new Map<string, { node: BabylonTransformNodeLike; radius: number }>();\n private readonly byNode = new Map<BabylonTransformNodeLike, string>();\n private readonly defaultRadius: number;\n private readonly maxRayDistance: number;\n private pickWithRay: BabylonPickWithRay | null;\n\n constructor(options: BabylonHitTesterOptions = {}) {\n this.defaultRadius = options.defaultRadius ?? DEFAULT_RADIUS;\n this.maxRayDistance = options.maxRayDistance ?? DEFAULT_MAX_RAY_DISTANCE;\n this.pickWithRay = options.pickWithRay ?? null;\n }\n\n /** Supply or replace the mesh-accurate pick after construction. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.pickWithRay = pick;\n }\n\n register(id: string, node: BabylonTransformNodeLike, radius?: number): void {\n this.unregister(id);\n this.entries.set(id, { node, radius: radius ?? this.defaultRadius });\n this.byNode.set(node, id);\n }\n\n unregister(id: string): void {\n const entry = this.entries.get(id);\n if (entry) this.byNode.delete(entry.node);\n this.entries.delete(id);\n }\n\n getNode(id: string): BabylonTransformNodeLike | undefined {\n return this.entries.get(id)?.node;\n }\n\n hitRay(ray: RayTuple): InteractableHit | null {\n // An app-supplied pick REPLACES the sphere test for rays - it does not\n // sit in front of it. A miss, or a hit on scenery, is the answer: the\n // app's own pick already said what the ray reached first, and testing\n // spheres behind it would target something the ray never got to.\n // Proximity queries stay on the sphere test either way.\n if (this.pickWithRay) {\n const picked = this.pickWithRay(ray.origin, ray.direction, this.maxRayDistance);\n if (!picked?.mesh) return null;\n const id = this.ownerOf(picked.mesh);\n return id === null\n ? null\n : { interactableId: id, distance: picked.distance, point: picked.point };\n }\n\n let best: InteractableHit | null = null;\n for (const [id, { node, radius }] of this.entries) {\n if (!nodeShowing(node)) continue;\n const point = toVec3(node.getAbsolutePosition());\n if (!point) continue;\n const { distance, t } = rayPointDistance(ray, point);\n if (t > 0 && distance <= radius && (best === null || t < best.distance)) {\n best = { interactableId: id, distance: t, point };\n }\n }\n return best;\n }\n\n hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const [id, { node, radius: targetRadius }] of this.entries) {\n if (!nodeShowing(node)) continue;\n const at = toVec3(node.getAbsolutePosition());\n if (!at) continue;\n const distance =\n Math.hypot(at[0] - point[0], at[1] - point[1], at[2] - point[2]) - targetRadius;\n if (distance <= radius && (best === null || distance < best.distance)) {\n best = { interactableId: id, distance: Math.max(0, distance), point: at };\n }\n }\n return best;\n }\n\n /**\n * The eye-gaze cone (`ports.ts`, `hitCone`) over the registered spheres,\n * the core's `coneHitForSpheres`. With an app-supplied `pickWithRay`, a\n * mesh the ray itself reaches wins first, as it does for `hitRay`.\n */\n hitCone(ray: RayTuple, halfAngle: number, maxLength: number): InteractableHit | null {\n if (this.pickWithRay) {\n const direct = this.hitRay(ray);\n if (direct && direct.distance <= maxLength) return direct;\n }\n const spheres: SphereTarget[] = [];\n for (const [id, { node, radius }] of this.entries) {\n if (!nodeShowing(node)) continue;\n const center = toVec3(node.getAbsolutePosition());\n if (center) spheres.push({ id, center, radius });\n }\n const hit = coneHitForSpheres(ray, spheres, halfAngle, maxLength);\n return hit ? { interactableId: hit.interactableId, distance: hit.distance, point: hit.point } : null;\n }\n\n /** The registered interactable a picked node belongs to, if any. */\n private ownerOf(node: BabylonTransformNodeLike): string | null {\n let current: BabylonTransformNodeLike | null = node;\n while (current) {\n const id = this.byNode.get(current);\n if (id !== undefined) return id;\n current = parentOf(current);\n }\n return null;\n }\n}\n"]}
|
package/dist/host.d.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* `scene.onBeforeRenderObservable` and the engine's own frame delta.
|
|
12
12
|
* Without it, call `update(dt)` yourself with seconds.
|
|
13
13
|
*/
|
|
14
|
-
import { InteractionRuntime, type DwellConfig, type InteractableDescriptor } from "@realitycollective/webxr-interactions";
|
|
15
|
-
import type
|
|
14
|
+
import { InteractionRuntime, type DwellConfig, type InteractableDescriptor, type PhysicsBodySpec, type PhysicsFacility, type PhysicsShapeSpec } from "@realitycollective/webxr-interactions";
|
|
15
|
+
import { type BabylonPhysicsKitLike, type BabylonTransformNodeLike } from "./babylon-types.js";
|
|
16
16
|
import { BabylonHitTester, type BabylonHitTesterOptions, type BabylonPickWithRay } from "./hit-tester.js";
|
|
17
17
|
import { BabylonTransformPort, type BabylonTransformPortOptions } from "./transform-port.js";
|
|
18
18
|
import { BabylonInputProvider, type BabylonProviderOptions } from "./provider.js";
|
|
@@ -24,21 +24,54 @@ export interface BabylonInteractionsOptions extends BabylonProviderOptions, Baby
|
|
|
24
24
|
* `update` from its own loop.
|
|
25
25
|
*/
|
|
26
26
|
attachToScene?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The platform's physics. Havok (Physics V2) is the named default engine
|
|
29
|
+
* for Babylon: pass `{ kit }`, the Havok-era values from
|
|
30
|
+
* `BabylonPhysicsKitLike`, and the binding builds a `BabylonPhysicsFacility`
|
|
31
|
+
* over `options.scene`. Pass any `PhysicsFacility` instead to replace the
|
|
32
|
+
* default engine with your own. Omit it and no node can carry a body:
|
|
33
|
+
* `register` with a `body` or `shape` then throws.
|
|
34
|
+
*/
|
|
35
|
+
physics?: PhysicsFacility | {
|
|
36
|
+
kit: BabylonPhysicsKitLike;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Step the facility from `update(dt)`. Default true. Pass false when the
|
|
40
|
+
* app steps its physics world itself - or lets Babylon's own render loop
|
|
41
|
+
* do it, since Babylon normally steps physics from `scene.render()`.
|
|
42
|
+
*/
|
|
43
|
+
stepPhysics?: boolean;
|
|
27
44
|
}
|
|
28
45
|
export interface BabylonRegisterOptions extends BabylonTransformPortOptions {
|
|
29
46
|
/** Targeting radius for the sphere hit-tester. Default 0.1 m. */
|
|
30
47
|
targetRadius?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Give the node a physics body with these settings (defaults are the
|
|
50
|
+
* core's: dynamic, no damping, gravity factor 1). Needs `physics` on the
|
|
51
|
+
* setup. With a body, the port applies the held-pose rule through it.
|
|
52
|
+
*/
|
|
53
|
+
body?: PhysicsBodySpec;
|
|
54
|
+
/** The body's collider (default `"auto"`: the node's own geometry). Implies `body`. */
|
|
55
|
+
shape?: PhysicsShapeSpec;
|
|
31
56
|
}
|
|
32
57
|
export declare class BabylonInteractions {
|
|
33
58
|
readonly runtime: InteractionRuntime;
|
|
34
59
|
readonly provider: BabylonInputProvider;
|
|
35
60
|
readonly hitTester: BabylonHitTester;
|
|
61
|
+
/** The platform's physics, when the setup was given one. */
|
|
62
|
+
readonly physics: PhysicsFacility | null;
|
|
63
|
+
private readonly stepPhysics;
|
|
36
64
|
private readonly ports;
|
|
65
|
+
private readonly nodes;
|
|
37
66
|
private detachScene;
|
|
38
67
|
constructor(options: BabylonInteractionsOptions);
|
|
39
68
|
/** Supply or replace the app's mesh-accurate pick. */
|
|
40
69
|
setPickWithRay(pick: BabylonPickWithRay | null): void;
|
|
41
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Register an interactable with its Babylon node. With `body` or `shape`
|
|
72
|
+
* the node gets a physics body in the facility at its current world pose,
|
|
73
|
+
* and its port drives the held-pose rule through it.
|
|
74
|
+
*/
|
|
42
75
|
register(descriptor: InteractableDescriptor, node: BabylonTransformNodeLike, options?: BabylonRegisterOptions): BabylonTransformPort;
|
|
43
76
|
unregister(id: string): void;
|
|
44
77
|
getPort(id: string): BabylonTransformPort | undefined;
|
package/dist/host.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* Without it, call `update(dt)` yourself with seconds.
|
|
13
13
|
*/
|
|
14
14
|
import { InteractionRuntime, } from "@realitycollective/webxr-interactions";
|
|
15
|
+
import { nodeWorldPose } from "./babylon-types.js";
|
|
15
16
|
import { BabylonHitTester, } from "./hit-tester.js";
|
|
17
|
+
import { BabylonPhysicsFacility } from "./physics-facility.js";
|
|
16
18
|
import { BabylonTransformPort } from "./transform-port.js";
|
|
17
19
|
import { BabylonInputProvider } from "./provider.js";
|
|
18
20
|
/** Longest frame the scene-attached loop will report, in seconds. */
|
|
@@ -21,7 +23,11 @@ export class BabylonInteractions {
|
|
|
21
23
|
runtime;
|
|
22
24
|
provider;
|
|
23
25
|
hitTester;
|
|
26
|
+
/** The platform's physics, when the setup was given one. */
|
|
27
|
+
physics;
|
|
28
|
+
stepPhysics;
|
|
24
29
|
ports = new Map();
|
|
30
|
+
nodes = new Map();
|
|
25
31
|
detachScene = null;
|
|
26
32
|
constructor(options) {
|
|
27
33
|
this.provider = new BabylonInputProvider(options);
|
|
@@ -31,6 +37,12 @@ export class BabylonInteractions {
|
|
|
31
37
|
hitTester: this.hitTester,
|
|
32
38
|
...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),
|
|
33
39
|
});
|
|
40
|
+
this.physics = options.physics
|
|
41
|
+
? "kit" in options.physics
|
|
42
|
+
? new BabylonPhysicsFacility(options.scene, options.physics.kit, { nodeFor: (id) => this.nodes.get(id) })
|
|
43
|
+
: options.physics
|
|
44
|
+
: null;
|
|
45
|
+
this.stepPhysics = options.stepPhysics ?? true;
|
|
34
46
|
if (options.attachToScene)
|
|
35
47
|
this.attachToScene(options);
|
|
36
48
|
}
|
|
@@ -38,9 +50,28 @@ export class BabylonInteractions {
|
|
|
38
50
|
setPickWithRay(pick) {
|
|
39
51
|
this.hitTester.setPickWithRay(pick);
|
|
40
52
|
}
|
|
41
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Register an interactable with its Babylon node. With `body` or `shape`
|
|
55
|
+
* the node gets a physics body in the facility at its current world pose,
|
|
56
|
+
* and its port drives the held-pose rule through it.
|
|
57
|
+
*/
|
|
42
58
|
register(descriptor, node, options = {}) {
|
|
43
|
-
|
|
59
|
+
this.nodes.set(descriptor.id, node);
|
|
60
|
+
let port;
|
|
61
|
+
if (options.body || options.shape) {
|
|
62
|
+
if (!this.physics) {
|
|
63
|
+
throw new Error(`[babylon-interactions] "${descriptor.id}" asks for a physics body but the setup has no physics; pass { kit } (the default engine) or a PhysicsFacility to createBabylonInteractions`);
|
|
64
|
+
}
|
|
65
|
+
node.computeWorldMatrix?.(true);
|
|
66
|
+
this.physics.addBody(descriptor.id, nodeWorldPose(node), options.body, options.shape);
|
|
67
|
+
port = new BabylonTransformPort(node, {
|
|
68
|
+
...(options.createQuaternion ? { createQuaternion: options.createQuaternion } : {}),
|
|
69
|
+
physics: { facility: this.physics, bodyId: descriptor.id },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
port = new BabylonTransformPort(node, options.createQuaternion ? { createQuaternion: options.createQuaternion } : {});
|
|
74
|
+
}
|
|
44
75
|
this.ports.set(descriptor.id, port);
|
|
45
76
|
this.hitTester.register(descriptor.id, node, options.targetRadius);
|
|
46
77
|
this.runtime.registerInteractable(descriptor, { transform: port });
|
|
@@ -50,18 +81,25 @@ export class BabylonInteractions {
|
|
|
50
81
|
this.runtime.unregisterInteractable(id);
|
|
51
82
|
this.hitTester.unregister(id);
|
|
52
83
|
this.ports.delete(id);
|
|
84
|
+
this.nodes.delete(id);
|
|
85
|
+
this.physics?.removeBody(id);
|
|
53
86
|
}
|
|
54
87
|
getPort(id) {
|
|
55
88
|
return this.ports.get(id);
|
|
56
89
|
}
|
|
57
90
|
update(dt) {
|
|
91
|
+
// The provider's eye-gaze filter and grace integrate over the same step.
|
|
92
|
+
this.provider.setFrameDelta(dt);
|
|
58
93
|
this.runtime.update(dt);
|
|
94
|
+
if (this.stepPhysics)
|
|
95
|
+
this.physics?.step(dt);
|
|
59
96
|
}
|
|
60
97
|
dispose() {
|
|
61
98
|
this.detachScene?.();
|
|
62
99
|
this.detachScene = null;
|
|
63
100
|
this.runtime.dispose();
|
|
64
101
|
this.provider.dispose();
|
|
102
|
+
this.physics?.dispose();
|
|
65
103
|
}
|
|
66
104
|
attachToScene(options) {
|
|
67
105
|
const observable = options.scene.onBeforeRenderObservable;
|
package/dist/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * createBabylonInteractions - one-call setup for the Babylon adapter.\n *\n * ```ts\n * const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] }, buttonMesh);\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * With `attachToScene` the render loop drives itself from\n * `scene.onBeforeRenderObservable` and the engine's own frame delta.\n * Without it, call `update(dt)` yourself with seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n} from \"@realitycollective/webxr-interactions\";\nimport type { BabylonTransformNodeLike } from \"./babylon-types.js\";\nimport {\n BabylonHitTester,\n type BabylonHitTesterOptions,\n type BabylonPickWithRay,\n} from \"./hit-tester.js\";\nimport { BabylonTransformPort, type BabylonTransformPortOptions } from \"./transform-port.js\";\nimport { BabylonInputProvider, type BabylonProviderOptions } from \"./provider.js\";\n\nexport interface BabylonInteractionsOptions\n extends BabylonProviderOptions,\n BabylonHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /**\n * Drive `update(dt)` from `scene.onBeforeRenderObservable`, using the\n * engine's frame delta in seconds. Default false - the app calls\n * `update` from its own loop.\n */\n attachToScene?: boolean;\n}\n\nexport interface BabylonRegisterOptions extends BabylonTransformPortOptions {\n /** Targeting radius for the sphere hit-tester. Default 0.1 m. */\n targetRadius?: number;\n}\n\n/** Longest frame the scene-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class BabylonInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: BabylonInputProvider;\n readonly hitTester: BabylonHitTester;\n private readonly ports = new Map<string, BabylonTransformPort>();\n private detachScene: (() => void) | null = null;\n\n constructor(options: BabylonInteractionsOptions) {\n this.provider = new BabylonInputProvider(options);\n this.hitTester = new BabylonHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n });\n if (options.attachToScene) this.attachToScene(options);\n }\n\n /** Supply or replace the app's mesh-accurate pick. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.hitTester.setPickWithRay(pick);\n }\n\n /** Register an interactable with its Babylon node. */\n register(\n descriptor: InteractableDescriptor,\n node: BabylonTransformNodeLike,\n options: BabylonRegisterOptions = {},\n ): BabylonTransformPort {\n const port = new BabylonTransformPort(\n node,\n options.createQuaternion ? { createQuaternion: options.createQuaternion } : {},\n );\n this.ports.set(descriptor.id, port);\n this.hitTester.register(descriptor.id, node, options.targetRadius);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.hitTester.unregister(id);\n this.ports.delete(id);\n }\n\n getPort(id: string): BabylonTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n this.runtime.update(dt);\n }\n\n dispose(): void {\n this.detachScene?.();\n this.detachScene = null;\n this.runtime.dispose();\n this.provider.dispose();\n }\n\n private attachToScene(options: BabylonInteractionsOptions): void {\n const observable = options.scene.onBeforeRenderObservable;\n if (!observable) return;\n const engine = options.scene.getEngine?.();\n const observer = observable.add(() => {\n // Babylon reports the frame delta in milliseconds. A long frame - a tab\n // that was backgrounded, a slow first frame - is clamped so behaviours\n // integrate over a sane step rather than jumping.\n const ms = engine?.getDeltaTime() ?? 0;\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, ms / 1000)));\n });\n this.detachScene = () => {\n observable.remove(observer);\n };\n }\n}\n\nexport function createBabylonInteractions(\n options: BabylonInteractionsOptions,\n): BabylonInteractions {\n return new BabylonInteractions(options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * createBabylonInteractions - one-call setup for the Babylon adapter.\n *\n * ```ts\n * const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] }, buttonMesh);\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * With `attachToScene` the render loop drives itself from\n * `scene.onBeforeRenderObservable` and the engine's own frame delta.\n * Without it, call `update(dt)` yourself with seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n type PhysicsBodySpec,\n type PhysicsFacility,\n type PhysicsShapeSpec,\n} from \"@realitycollective/webxr-interactions\";\nimport { nodeWorldPose, type BabylonPhysicsKitLike, type BabylonTransformNodeLike } from \"./babylon-types.js\";\nimport {\n BabylonHitTester,\n type BabylonHitTesterOptions,\n type BabylonPickWithRay,\n} from \"./hit-tester.js\";\nimport { BabylonPhysicsFacility } from \"./physics-facility.js\";\nimport { BabylonTransformPort, type BabylonTransformPortOptions } from \"./transform-port.js\";\nimport { BabylonInputProvider, type BabylonProviderOptions } from \"./provider.js\";\n\nexport interface BabylonInteractionsOptions\n extends BabylonProviderOptions,\n BabylonHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /**\n * Drive `update(dt)` from `scene.onBeforeRenderObservable`, using the\n * engine's frame delta in seconds. Default false - the app calls\n * `update` from its own loop.\n */\n attachToScene?: boolean;\n /**\n * The platform's physics. Havok (Physics V2) is the named default engine\n * for Babylon: pass `{ kit }`, the Havok-era values from\n * `BabylonPhysicsKitLike`, and the binding builds a `BabylonPhysicsFacility`\n * over `options.scene`. Pass any `PhysicsFacility` instead to replace the\n * default engine with your own. Omit it and no node can carry a body:\n * `register` with a `body` or `shape` then throws.\n */\n physics?: PhysicsFacility | { kit: BabylonPhysicsKitLike };\n /**\n * Step the facility from `update(dt)`. Default true. Pass false when the\n * app steps its physics world itself - or lets Babylon's own render loop\n * do it, since Babylon normally steps physics from `scene.render()`.\n */\n stepPhysics?: boolean;\n}\n\nexport interface BabylonRegisterOptions extends BabylonTransformPortOptions {\n /** Targeting radius for the sphere hit-tester. Default 0.1 m. */\n targetRadius?: number;\n /**\n * Give the node a physics body with these settings (defaults are the\n * core's: dynamic, no damping, gravity factor 1). Needs `physics` on the\n * setup. With a body, the port applies the held-pose rule through it.\n */\n body?: PhysicsBodySpec;\n /** The body's collider (default `\"auto\"`: the node's own geometry). Implies `body`. */\n shape?: PhysicsShapeSpec;\n}\n\n/** Longest frame the scene-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class BabylonInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: BabylonInputProvider;\n readonly hitTester: BabylonHitTester;\n /** The platform's physics, when the setup was given one. */\n readonly physics: PhysicsFacility | null;\n private readonly stepPhysics: boolean;\n private readonly ports = new Map<string, BabylonTransformPort>();\n private readonly nodes = new Map<string, BabylonTransformNodeLike>();\n private detachScene: (() => void) | null = null;\n\n constructor(options: BabylonInteractionsOptions) {\n this.provider = new BabylonInputProvider(options);\n this.hitTester = new BabylonHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n });\n this.physics = options.physics\n ? \"kit\" in options.physics\n ? new BabylonPhysicsFacility(options.scene, options.physics.kit, { nodeFor: (id) => this.nodes.get(id) })\n : options.physics\n : null;\n this.stepPhysics = options.stepPhysics ?? true;\n if (options.attachToScene) this.attachToScene(options);\n }\n\n /** Supply or replace the app's mesh-accurate pick. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.hitTester.setPickWithRay(pick);\n }\n\n /**\n * Register an interactable with its Babylon node. With `body` or `shape`\n * the node gets a physics body in the facility at its current world pose,\n * and its port drives the held-pose rule through it.\n */\n register(\n descriptor: InteractableDescriptor,\n node: BabylonTransformNodeLike,\n options: BabylonRegisterOptions = {},\n ): BabylonTransformPort {\n this.nodes.set(descriptor.id, node);\n let port: BabylonTransformPort;\n if (options.body || options.shape) {\n if (!this.physics) {\n throw new Error(\n `[babylon-interactions] \"${descriptor.id}\" asks for a physics body but the setup has no physics; pass { kit } (the default engine) or a PhysicsFacility to createBabylonInteractions`,\n );\n }\n node.computeWorldMatrix?.(true);\n this.physics.addBody(descriptor.id, nodeWorldPose(node), options.body, options.shape);\n port = new BabylonTransformPort(node, {\n ...(options.createQuaternion ? { createQuaternion: options.createQuaternion } : {}),\n physics: { facility: this.physics, bodyId: descriptor.id },\n });\n } else {\n port = new BabylonTransformPort(\n node,\n options.createQuaternion ? { createQuaternion: options.createQuaternion } : {},\n );\n }\n this.ports.set(descriptor.id, port);\n this.hitTester.register(descriptor.id, node, options.targetRadius);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.hitTester.unregister(id);\n this.ports.delete(id);\n this.nodes.delete(id);\n this.physics?.removeBody(id);\n }\n\n getPort(id: string): BabylonTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n // The provider's eye-gaze filter and grace integrate over the same step.\n this.provider.setFrameDelta(dt);\n this.runtime.update(dt);\n if (this.stepPhysics) this.physics?.step(dt);\n }\n\n dispose(): void {\n this.detachScene?.();\n this.detachScene = null;\n this.runtime.dispose();\n this.provider.dispose();\n this.physics?.dispose();\n }\n\n private attachToScene(options: BabylonInteractionsOptions): void {\n const observable = options.scene.onBeforeRenderObservable;\n if (!observable) return;\n const engine = options.scene.getEngine?.();\n const observer = observable.add(() => {\n // Babylon reports the frame delta in milliseconds. A long frame - a tab\n // that was backgrounded, a slow first frame - is clamped so behaviours\n // integrate over a sane step rather than jumping.\n const ms = engine?.getDeltaTime() ?? 0;\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, ms / 1000)));\n });\n this.detachScene = () => {\n observable.remove(observer);\n };\n }\n}\n\nexport function createBabylonInteractions(\n options: BabylonInteractionsOptions,\n): BabylonInteractions {\n return new BabylonInteractions(options);\n}\n"]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,uCAAuC,CAAC;AAEtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @realitycollective/babylon-interactions - the Babylon.js engine adapter:\n * Babylon WebXR input, sphere or app-supplied hit-testing, and transform\n * ports over Babylon nodes. The core is re-exported wholesale, so apps\n * depend on exactly this package.\n *\n * Structurally typed against the Babylon API rather than importing\n * `@babylonjs/core`, in the same way as the XR Blocks adapter, so an\n * upstream release cannot break the install.\n */\nexport * from \"@realitycollective/webxr-interactions\";\n\nexport * from \"./babylon-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./host.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,uCAAuC,CAAC;AAEtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @realitycollective/babylon-interactions - the Babylon.js engine adapter:\n * Babylon WebXR input, sphere or app-supplied hit-testing, and transform\n * ports over Babylon nodes. The core is re-exported wholesale, so apps\n * depend on exactly this package.\n *\n * Structurally typed against the Babylon API rather than importing\n * `@babylonjs/core`, in the same way as the XR Blocks adapter, so an\n * upstream release cannot break the install.\n */\nexport * from \"@realitycollective/webxr-interactions\";\n\nexport * from \"./babylon-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./physics-facility.js\";\nexport * from \"./host.js\";\n"]}
|