@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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BabylonPhysicsFacility - the Babylon platform's physics, over Physics V2
|
|
3
|
+
* (Havok, or any plugin behind the same API): `PhysicsBody`, `PhysicsShape`
|
|
4
|
+
* and `PhysicsMotionType`. It implements the core `PhysicsFacility` contract
|
|
5
|
+
* with the core defaults (`PHYSICS_DEFAULTS`: IWSDK's), so a body here
|
|
6
|
+
* behaves as it does under Havok on IWSDK itself, and is proved by
|
|
7
|
+
* `physicsFacilityContractCases()`.
|
|
8
|
+
*
|
|
9
|
+
* `@babylonjs/core` (and `@babylonjs/havok`) are not dependencies of this
|
|
10
|
+
* package, for the same reason the rest of `babylon-types.ts` is structural:
|
|
11
|
+
* the app hands over the Havok-era values it already has, through
|
|
12
|
+
* `BabylonPhysicsKitLike` (`PhysicsBody`, the `PhysicsShapeXxx`
|
|
13
|
+
* constructors, `PhysicsMotionType`, `Vector3`, `Quaternion`) - the same
|
|
14
|
+
* "the app supplies the engine values" rule
|
|
15
|
+
* `BabylonTransformPortOptions.physicsMotionTypes` already follows. An app
|
|
16
|
+
* may replace this facility with its own by passing any `PhysicsFacility` to
|
|
17
|
+
* `createBabylonInteractions({ physics })`.
|
|
18
|
+
*
|
|
19
|
+
* What is Babylon's and what is the core's:
|
|
20
|
+
* - a hold is `PhysicsMotionType.ANIMATED` with `disablePreStep` true (node
|
|
21
|
+
* drives physics - see `BabylonPhysicsBodyLike`'s own comment), restored
|
|
22
|
+
* to the body's own motion type with `disablePreStep` false on resume;
|
|
23
|
+
* - a teleport writes the node (through its parent, as
|
|
24
|
+
* `BabylonTransformPort.setWorldPose` resolves one) and calls the body's
|
|
25
|
+
* own `setTargetTransform` with the velocities cleared;
|
|
26
|
+
* - `"auto"` shapes use `PhysicsShapeMesh` over the node's own geometry, or
|
|
27
|
+
* a 0.1 m sphere for a node with none (`getTotalVertices()` zero, or
|
|
28
|
+
* absent - a bare `TransformNode`);
|
|
29
|
+
* - Babylon steps physics itself from `scene.render()`; this facility's own
|
|
30
|
+
* `step` only forwards to the physics engine's internal `_step`, so a
|
|
31
|
+
* caller with no render loop (a test, or an app ticking physics on its
|
|
32
|
+
* own) can still advance the simulation.
|
|
33
|
+
*/
|
|
34
|
+
import type { PoseTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
35
|
+
import { type HoldRelease, type PhysicsBodySpec, type PhysicsBodyState, type PhysicsFacility, type PhysicsShapeSpec, type PhysicsVelocity } from "@realitycollective/webxr-interactions";
|
|
36
|
+
import { type BabylonPhysicsKitLike, type BabylonSceneLike, type BabylonTransformNodeLike } from "./babylon-types.js";
|
|
37
|
+
export interface BabylonPhysicsFacilityOptions {
|
|
38
|
+
/**
|
|
39
|
+
* The node behind a body id - the same registered node
|
|
40
|
+
* `createBabylonInteractions` looks up, needed to place the body and, for
|
|
41
|
+
* an `"auto"` shape, to read its geometry.
|
|
42
|
+
*/
|
|
43
|
+
nodeFor(id: string): BabylonTransformNodeLike | undefined;
|
|
44
|
+
}
|
|
45
|
+
export declare class BabylonPhysicsFacility implements PhysicsFacility {
|
|
46
|
+
readonly engine = "havok";
|
|
47
|
+
private readonly scene;
|
|
48
|
+
private readonly kit;
|
|
49
|
+
private readonly nodeFor;
|
|
50
|
+
private readonly entries;
|
|
51
|
+
/** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */
|
|
52
|
+
private fallbackGravity;
|
|
53
|
+
constructor(scene: BabylonSceneLike, kit: BabylonPhysicsKitLike, options: BabylonPhysicsFacilityOptions);
|
|
54
|
+
getGravity(): Vec3Tuple;
|
|
55
|
+
setGravity(gravity: Vec3Tuple): void;
|
|
56
|
+
addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;
|
|
57
|
+
removeBody(id: string): void;
|
|
58
|
+
hasBody(id: string): boolean;
|
|
59
|
+
setBodyState(id: string, state: PhysicsBodyState): void;
|
|
60
|
+
getBodyState(id: string): PhysicsBodyState;
|
|
61
|
+
getBodyPose(id: string): PoseTuple;
|
|
62
|
+
setBodyPose(id: string, pose: PoseTuple): void;
|
|
63
|
+
getVelocity(id: string): PhysicsVelocity;
|
|
64
|
+
setVelocity(id: string, velocity: PhysicsVelocity): void;
|
|
65
|
+
suspend(id: string): void;
|
|
66
|
+
resume(id: string, release: HoldRelease): void;
|
|
67
|
+
isSuspended(id: string): boolean;
|
|
68
|
+
step(dtSeconds: number): void;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
private entry;
|
|
71
|
+
/** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */
|
|
72
|
+
private physicsEngine;
|
|
73
|
+
private motionTypeFor;
|
|
74
|
+
private shapeFor;
|
|
75
|
+
/** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */
|
|
76
|
+
private placeNode;
|
|
77
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { PHYSICS_DEFAULTS, missingBody, quatConjugate, quatMultiply, resolvePhysicsBody, resolvePhysicsShape, vApplyQuat, vSub, } from "@realitycollective/webxr-interactions";
|
|
2
|
+
import { nodeWorldPose, parentOf, writeQuat, writeVec3, } from "./babylon-types.js";
|
|
3
|
+
const ZERO_VECTOR3 = { x: 0, y: 0, z: 0 };
|
|
4
|
+
const IDENTITY_QUATERNION = { x: 0, y: 0, z: 0, w: 1 };
|
|
5
|
+
function vector3Like(v) {
|
|
6
|
+
return { x: v[0], y: v[1], z: v[2] };
|
|
7
|
+
}
|
|
8
|
+
function quaternionLike(q) {
|
|
9
|
+
return { x: q[0], y: q[1], z: q[2], w: q[3] };
|
|
10
|
+
}
|
|
11
|
+
function copy3(v) {
|
|
12
|
+
return [v[0], v[1], v[2]];
|
|
13
|
+
}
|
|
14
|
+
export class BabylonPhysicsFacility {
|
|
15
|
+
engine = "havok";
|
|
16
|
+
scene;
|
|
17
|
+
kit;
|
|
18
|
+
nodeFor;
|
|
19
|
+
entries = new Map();
|
|
20
|
+
/** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */
|
|
21
|
+
fallbackGravity = copy3(PHYSICS_DEFAULTS.gravity);
|
|
22
|
+
constructor(scene, kit, options) {
|
|
23
|
+
this.scene = scene;
|
|
24
|
+
this.kit = kit;
|
|
25
|
+
this.nodeFor = options.nodeFor;
|
|
26
|
+
}
|
|
27
|
+
getGravity() {
|
|
28
|
+
const engine = this.physicsEngine();
|
|
29
|
+
if (!engine)
|
|
30
|
+
return copy3(this.fallbackGravity);
|
|
31
|
+
return [engine.gravity.x, engine.gravity.y, engine.gravity.z];
|
|
32
|
+
}
|
|
33
|
+
setGravity(gravity) {
|
|
34
|
+
const engine = this.physicsEngine();
|
|
35
|
+
if (!engine) {
|
|
36
|
+
this.fallbackGravity = copy3(gravity);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
engine.setGravity(vector3Like(gravity));
|
|
40
|
+
}
|
|
41
|
+
addBody(id, pose, body = {}, shape = {}) {
|
|
42
|
+
this.removeBody(id);
|
|
43
|
+
const node = this.nodeFor(id);
|
|
44
|
+
if (!node) {
|
|
45
|
+
throw new Error(`[babylon-interactions] no node for physics body "${id}"; pass nodeFor to BabylonPhysicsFacility`);
|
|
46
|
+
}
|
|
47
|
+
const spec = resolvePhysicsBody(body);
|
|
48
|
+
const shapeSpec = resolvePhysicsShape(shape);
|
|
49
|
+
// The node must already sit at `pose` before the body is constructed:
|
|
50
|
+
// Havok reads the node's transform as the body's starting one.
|
|
51
|
+
this.placeNode(node, pose);
|
|
52
|
+
const physicsBody = new this.kit.PhysicsBody(node, this.motionTypeFor(spec.state), false, this.scene);
|
|
53
|
+
physicsBody.disablePreStep = false;
|
|
54
|
+
physicsBody.setLinearDamping(spec.linearDamping);
|
|
55
|
+
physicsBody.setAngularDamping(spec.angularDamping);
|
|
56
|
+
physicsBody.setGravityFactor(spec.gravityFactor);
|
|
57
|
+
const physicsShape = this.shapeFor(node, shapeSpec);
|
|
58
|
+
physicsShape.material = { friction: shapeSpec.friction, restitution: shapeSpec.restitution };
|
|
59
|
+
physicsShape.density = shapeSpec.density;
|
|
60
|
+
physicsBody.shape = physicsShape;
|
|
61
|
+
this.entries.set(id, { node, body: physicsBody, shape: physicsShape, state: spec.state, suspended: false });
|
|
62
|
+
}
|
|
63
|
+
removeBody(id) {
|
|
64
|
+
const entry = this.entries.get(id);
|
|
65
|
+
if (!entry)
|
|
66
|
+
return;
|
|
67
|
+
entry.body.dispose();
|
|
68
|
+
entry.shape.dispose();
|
|
69
|
+
this.entries.delete(id);
|
|
70
|
+
}
|
|
71
|
+
hasBody(id) {
|
|
72
|
+
return this.entries.has(id);
|
|
73
|
+
}
|
|
74
|
+
setBodyState(id, state) {
|
|
75
|
+
const entry = this.entry(id);
|
|
76
|
+
entry.state = state;
|
|
77
|
+
if (!entry.suspended)
|
|
78
|
+
entry.body.setMotionType(this.motionTypeFor(state));
|
|
79
|
+
}
|
|
80
|
+
getBodyState(id) {
|
|
81
|
+
return this.entry(id).state;
|
|
82
|
+
}
|
|
83
|
+
getBodyPose(id) {
|
|
84
|
+
const node = this.entry(id).node;
|
|
85
|
+
node.computeWorldMatrix?.(true);
|
|
86
|
+
return nodeWorldPose(node);
|
|
87
|
+
}
|
|
88
|
+
setBodyPose(id, pose) {
|
|
89
|
+
const entry = this.entry(id);
|
|
90
|
+
this.placeNode(entry.node, pose);
|
|
91
|
+
entry.body.setTargetTransform(vector3Like(pose.position), quaternionLike(pose.quaternion));
|
|
92
|
+
entry.body.setLinearVelocity(ZERO_VECTOR3);
|
|
93
|
+
entry.body.setAngularVelocity(ZERO_VECTOR3);
|
|
94
|
+
}
|
|
95
|
+
getVelocity(id) {
|
|
96
|
+
const body = this.entry(id).body;
|
|
97
|
+
const linear = body.getLinearVelocity();
|
|
98
|
+
const angular = body.getAngularVelocity();
|
|
99
|
+
return { linear: [linear.x, linear.y, linear.z], angular: [angular.x, angular.y, angular.z] };
|
|
100
|
+
}
|
|
101
|
+
setVelocity(id, velocity) {
|
|
102
|
+
const body = this.entry(id).body;
|
|
103
|
+
body.setLinearVelocity(vector3Like(velocity.linear));
|
|
104
|
+
body.setAngularVelocity(vector3Like(velocity.angular));
|
|
105
|
+
}
|
|
106
|
+
suspend(id) {
|
|
107
|
+
const entry = this.entry(id);
|
|
108
|
+
if (entry.suspended)
|
|
109
|
+
return;
|
|
110
|
+
entry.suspended = true;
|
|
111
|
+
entry.body.disablePreStep = true;
|
|
112
|
+
entry.body.setMotionType(this.kit.PhysicsMotionType.ANIMATED);
|
|
113
|
+
entry.body.setLinearVelocity(ZERO_VECTOR3);
|
|
114
|
+
entry.body.setAngularVelocity(ZERO_VECTOR3);
|
|
115
|
+
}
|
|
116
|
+
resume(id, release) {
|
|
117
|
+
const entry = this.entry(id);
|
|
118
|
+
if (!entry.suspended)
|
|
119
|
+
return;
|
|
120
|
+
entry.suspended = false;
|
|
121
|
+
entry.body.setMotionType(this.motionTypeFor(entry.state));
|
|
122
|
+
entry.body.disablePreStep = false;
|
|
123
|
+
entry.body.setLinearVelocity(vector3Like(release.linearVelocity));
|
|
124
|
+
entry.body.setAngularVelocity(vector3Like(release.angularVelocity));
|
|
125
|
+
}
|
|
126
|
+
isSuspended(id) {
|
|
127
|
+
return this.entry(id).suspended;
|
|
128
|
+
}
|
|
129
|
+
step(dtSeconds) {
|
|
130
|
+
if (dtSeconds <= 0)
|
|
131
|
+
return;
|
|
132
|
+
this.physicsEngine()?._step?.(dtSeconds);
|
|
133
|
+
}
|
|
134
|
+
dispose() {
|
|
135
|
+
for (const id of [...this.entries.keys()])
|
|
136
|
+
this.removeBody(id);
|
|
137
|
+
}
|
|
138
|
+
entry(id) {
|
|
139
|
+
const entry = this.entries.get(id);
|
|
140
|
+
if (!entry)
|
|
141
|
+
throw missingBody(id);
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
/** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */
|
|
145
|
+
physicsEngine() {
|
|
146
|
+
return this.scene.getPhysicsEngine ? this.scene.getPhysicsEngine() : null;
|
|
147
|
+
}
|
|
148
|
+
motionTypeFor(state) {
|
|
149
|
+
if (state === "static")
|
|
150
|
+
return this.kit.PhysicsMotionType.STATIC;
|
|
151
|
+
if (state === "kinematic")
|
|
152
|
+
return this.kit.PhysicsMotionType.ANIMATED;
|
|
153
|
+
return this.kit.PhysicsMotionType.DYNAMIC;
|
|
154
|
+
}
|
|
155
|
+
shapeFor(node, shapeSpec) {
|
|
156
|
+
const [a, b] = shapeSpec.dimensions;
|
|
157
|
+
switch (shapeSpec.kind) {
|
|
158
|
+
case "sphere":
|
|
159
|
+
return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, a, this.scene);
|
|
160
|
+
case "box":
|
|
161
|
+
return new this.kit.PhysicsShapeBox(ZERO_VECTOR3, IDENTITY_QUATERNION, vector3Like(shapeSpec.dimensions), this.scene);
|
|
162
|
+
case "capsule": {
|
|
163
|
+
// pointA/pointB are the capsule's two hemisphere centres along its
|
|
164
|
+
// axis: the cylindrical part is `height - 2 * radius` long.
|
|
165
|
+
const half = Math.max(0, b / 2 - a);
|
|
166
|
+
return new this.kit.PhysicsShapeCapsule({ x: 0, y: half, z: 0 }, { x: 0, y: -half, z: 0 }, a, this.scene);
|
|
167
|
+
}
|
|
168
|
+
case "auto": {
|
|
169
|
+
const vertices = node.getTotalVertices?.() ?? 0;
|
|
170
|
+
if (vertices > 0)
|
|
171
|
+
return new this.kit.PhysicsShapeMesh(node, this.scene);
|
|
172
|
+
// A node with no geometry of its own (a bare TransformNode) is a
|
|
173
|
+
// 10 cm sphere, the same default a bare hit-test target gets.
|
|
174
|
+
return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, 0.1, this.scene);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */
|
|
179
|
+
placeNode(node, pose) {
|
|
180
|
+
const parent = parentOf(node);
|
|
181
|
+
let position = pose.position;
|
|
182
|
+
let quaternion = pose.quaternion;
|
|
183
|
+
if (parent) {
|
|
184
|
+
const parentPose = nodeWorldPose(parent);
|
|
185
|
+
const inverse = quatConjugate(parentPose.quaternion);
|
|
186
|
+
position = vApplyQuat(vSub(pose.position, parentPose.position), inverse);
|
|
187
|
+
quaternion = quatMultiply(inverse, pose.quaternion);
|
|
188
|
+
}
|
|
189
|
+
writeVec3(node.position, position);
|
|
190
|
+
const rotation = node.rotationQuaternion;
|
|
191
|
+
if (rotation) {
|
|
192
|
+
writeQuat(rotation, quaternion);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
node.rotationQuaternion = new this.kit.Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=physics-facility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-facility.js","sourceRoot":"","sources":["../src/physics-facility.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonPhysicsFacility - the Babylon platform's physics, over Physics V2\n * (Havok, or any plugin behind the same API): `PhysicsBody`, `PhysicsShape`\n * and `PhysicsMotionType`. It implements the core `PhysicsFacility` contract\n * with the core defaults (`PHYSICS_DEFAULTS`: IWSDK's), so a body here\n * behaves as it does under Havok on IWSDK itself, and is proved by\n * `physicsFacilityContractCases()`.\n *\n * `@babylonjs/core` (and `@babylonjs/havok`) are not dependencies of this\n * package, for the same reason the rest of `babylon-types.ts` is structural:\n * the app hands over the Havok-era values it already has, through\n * `BabylonPhysicsKitLike` (`PhysicsBody`, the `PhysicsShapeXxx`\n * constructors, `PhysicsMotionType`, `Vector3`, `Quaternion`) - the same\n * \"the app supplies the engine values\" rule\n * `BabylonTransformPortOptions.physicsMotionTypes` already follows. An app\n * may replace this facility with its own by passing any `PhysicsFacility` to\n * `createBabylonInteractions({ physics })`.\n *\n * What is Babylon's and what is the core's:\n * - a hold is `PhysicsMotionType.ANIMATED` with `disablePreStep` true (node\n * drives physics - see `BabylonPhysicsBodyLike`'s own comment), restored\n * to the body's own motion type with `disablePreStep` false on resume;\n * - a teleport writes the node (through its parent, as\n * `BabylonTransformPort.setWorldPose` resolves one) and calls the body's\n * own `setTargetTransform` with the velocities cleared;\n * - `\"auto\"` shapes use `PhysicsShapeMesh` over the node's own geometry, or\n * a 0.1 m sphere for a node with none (`getTotalVertices()` zero, or\n * absent - a bare `TransformNode`);\n * - Babylon steps physics itself from `scene.render()`; this facility's own\n * `step` only forwards to the physics engine's internal `_step`, so a\n * caller with no render loop (a test, or an app ticking physics on its\n * own) can still advance the simulation.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n PHYSICS_DEFAULTS,\n missingBody,\n quatConjugate,\n quatMultiply,\n resolvePhysicsBody,\n resolvePhysicsShape,\n vApplyQuat,\n vSub,\n type HoldRelease,\n type PhysicsBodySpec,\n type PhysicsBodyState,\n type PhysicsFacility,\n type PhysicsShapeSpec,\n type PhysicsVelocity,\n} from \"@realitycollective/webxr-interactions\";\nimport {\n nodeWorldPose,\n parentOf,\n writeQuat,\n writeVec3,\n type BabylonPhysicsBodyLike,\n type BabylonPhysicsKitLike,\n type BabylonPhysicsShapeLike,\n type BabylonQuaternionLike,\n type BabylonSceneLike,\n type BabylonTransformNodeLike,\n type BabylonVector3Like,\n} from \"./babylon-types.js\";\n\nexport interface BabylonPhysicsFacilityOptions {\n /**\n * The node behind a body id - the same registered node\n * `createBabylonInteractions` looks up, needed to place the body and, for\n * an `\"auto\"` shape, to read its geometry.\n */\n nodeFor(id: string): BabylonTransformNodeLike | undefined;\n}\n\ninterface Entry {\n node: BabylonTransformNodeLike;\n body: BabylonPhysicsBodyLike;\n shape: BabylonPhysicsShapeLike;\n state: PhysicsBodyState;\n suspended: boolean;\n}\n\nconst ZERO_VECTOR3: BabylonVector3Like = { x: 0, y: 0, z: 0 };\nconst IDENTITY_QUATERNION: BabylonQuaternionLike = { x: 0, y: 0, z: 0, w: 1 };\n\nfunction vector3Like(v: Vec3Tuple): BabylonVector3Like {\n return { x: v[0], y: v[1], z: v[2] };\n}\n\nfunction quaternionLike(q: QuatTuple): BabylonQuaternionLike {\n return { x: q[0], y: q[1], z: q[2], w: q[3] };\n}\n\nfunction copy3(v: Readonly<Vec3Tuple>): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\nexport class BabylonPhysicsFacility implements PhysicsFacility {\n readonly engine = \"havok\";\n private readonly scene: BabylonSceneLike;\n private readonly kit: BabylonPhysicsKitLike;\n private readonly nodeFor: (id: string) => BabylonTransformNodeLike | undefined;\n private readonly entries = new Map<string, Entry>();\n /** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */\n private fallbackGravity: Vec3Tuple = copy3(PHYSICS_DEFAULTS.gravity);\n\n constructor(scene: BabylonSceneLike, kit: BabylonPhysicsKitLike, options: BabylonPhysicsFacilityOptions) {\n this.scene = scene;\n this.kit = kit;\n this.nodeFor = options.nodeFor;\n }\n\n getGravity(): Vec3Tuple {\n const engine = this.physicsEngine();\n if (!engine) return copy3(this.fallbackGravity);\n return [engine.gravity.x, engine.gravity.y, engine.gravity.z];\n }\n\n setGravity(gravity: Vec3Tuple): void {\n const engine = this.physicsEngine();\n if (!engine) {\n this.fallbackGravity = copy3(gravity);\n return;\n }\n engine.setGravity(vector3Like(gravity));\n }\n\n addBody(id: string, pose: PoseTuple, body: PhysicsBodySpec = {}, shape: PhysicsShapeSpec = {}): void {\n this.removeBody(id);\n const node = this.nodeFor(id);\n if (!node) {\n throw new Error(`[babylon-interactions] no node for physics body \"${id}\"; pass nodeFor to BabylonPhysicsFacility`);\n }\n const spec = resolvePhysicsBody(body);\n const shapeSpec = resolvePhysicsShape(shape);\n // The node must already sit at `pose` before the body is constructed:\n // Havok reads the node's transform as the body's starting one.\n this.placeNode(node, pose);\n const physicsBody = new this.kit.PhysicsBody(node, this.motionTypeFor(spec.state), false, this.scene);\n physicsBody.disablePreStep = false;\n physicsBody.setLinearDamping(spec.linearDamping);\n physicsBody.setAngularDamping(spec.angularDamping);\n physicsBody.setGravityFactor(spec.gravityFactor);\n const physicsShape = this.shapeFor(node, shapeSpec);\n physicsShape.material = { friction: shapeSpec.friction, restitution: shapeSpec.restitution };\n physicsShape.density = shapeSpec.density;\n physicsBody.shape = physicsShape;\n this.entries.set(id, { node, body: physicsBody, shape: physicsShape, state: spec.state, suspended: false });\n }\n\n removeBody(id: string): void {\n const entry = this.entries.get(id);\n if (!entry) return;\n entry.body.dispose();\n entry.shape.dispose();\n this.entries.delete(id);\n }\n\n hasBody(id: string): boolean {\n return this.entries.has(id);\n }\n\n setBodyState(id: string, state: PhysicsBodyState): void {\n const entry = this.entry(id);\n entry.state = state;\n if (!entry.suspended) entry.body.setMotionType(this.motionTypeFor(state));\n }\n\n getBodyState(id: string): PhysicsBodyState {\n return this.entry(id).state;\n }\n\n getBodyPose(id: string): PoseTuple {\n const node = this.entry(id).node;\n node.computeWorldMatrix?.(true);\n return nodeWorldPose(node);\n }\n\n setBodyPose(id: string, pose: PoseTuple): void {\n const entry = this.entry(id);\n this.placeNode(entry.node, pose);\n entry.body.setTargetTransform(vector3Like(pose.position), quaternionLike(pose.quaternion));\n entry.body.setLinearVelocity(ZERO_VECTOR3);\n entry.body.setAngularVelocity(ZERO_VECTOR3);\n }\n\n getVelocity(id: string): PhysicsVelocity {\n const body = this.entry(id).body;\n const linear = body.getLinearVelocity();\n const angular = body.getAngularVelocity();\n return { linear: [linear.x, linear.y, linear.z], angular: [angular.x, angular.y, angular.z] };\n }\n\n setVelocity(id: string, velocity: PhysicsVelocity): void {\n const body = this.entry(id).body;\n body.setLinearVelocity(vector3Like(velocity.linear));\n body.setAngularVelocity(vector3Like(velocity.angular));\n }\n\n suspend(id: string): void {\n const entry = this.entry(id);\n if (entry.suspended) return;\n entry.suspended = true;\n entry.body.disablePreStep = true;\n entry.body.setMotionType(this.kit.PhysicsMotionType.ANIMATED);\n entry.body.setLinearVelocity(ZERO_VECTOR3);\n entry.body.setAngularVelocity(ZERO_VECTOR3);\n }\n\n resume(id: string, release: HoldRelease): void {\n const entry = this.entry(id);\n if (!entry.suspended) return;\n entry.suspended = false;\n entry.body.setMotionType(this.motionTypeFor(entry.state));\n entry.body.disablePreStep = false;\n entry.body.setLinearVelocity(vector3Like(release.linearVelocity));\n entry.body.setAngularVelocity(vector3Like(release.angularVelocity));\n }\n\n isSuspended(id: string): boolean {\n return this.entry(id).suspended;\n }\n\n step(dtSeconds: number): void {\n if (dtSeconds <= 0) return;\n this.physicsEngine()?._step?.(dtSeconds);\n }\n\n dispose(): void {\n for (const id of [...this.entries.keys()]) this.removeBody(id);\n }\n\n private entry(id: string): Entry {\n const entry = this.entries.get(id);\n if (!entry) throw missingBody(id);\n return entry;\n }\n\n /** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */\n private physicsEngine() {\n return this.scene.getPhysicsEngine ? this.scene.getPhysicsEngine() : null;\n }\n\n private motionTypeFor(state: PhysicsBodyState): unknown {\n if (state === \"static\") return this.kit.PhysicsMotionType.STATIC;\n if (state === \"kinematic\") return this.kit.PhysicsMotionType.ANIMATED;\n return this.kit.PhysicsMotionType.DYNAMIC;\n }\n\n private shapeFor(node: BabylonTransformNodeLike, shapeSpec: Required<PhysicsShapeSpec>): BabylonPhysicsShapeLike {\n const [a, b] = shapeSpec.dimensions;\n switch (shapeSpec.kind) {\n case \"sphere\":\n return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, a, this.scene);\n case \"box\":\n return new this.kit.PhysicsShapeBox(ZERO_VECTOR3, IDENTITY_QUATERNION, vector3Like(shapeSpec.dimensions), this.scene);\n case \"capsule\": {\n // pointA/pointB are the capsule's two hemisphere centres along its\n // axis: the cylindrical part is `height - 2 * radius` long.\n const half = Math.max(0, b / 2 - a);\n return new this.kit.PhysicsShapeCapsule({ x: 0, y: half, z: 0 }, { x: 0, y: -half, z: 0 }, a, this.scene);\n }\n case \"auto\": {\n const vertices = node.getTotalVertices?.() ?? 0;\n if (vertices > 0) return new this.kit.PhysicsShapeMesh(node, this.scene);\n // A node with no geometry of its own (a bare TransformNode) is a\n // 10 cm sphere, the same default a bare hit-test target gets.\n return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, 0.1, this.scene);\n }\n }\n }\n\n /** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */\n private placeNode(node: BabylonTransformNodeLike, pose: PoseTuple): void {\n const parent = parentOf(node);\n let position = pose.position;\n let quaternion = pose.quaternion;\n if (parent) {\n const parentPose = nodeWorldPose(parent);\n const inverse = quatConjugate(parentPose.quaternion);\n position = vApplyQuat(vSub(pose.position, parentPose.position), inverse);\n quaternion = quatMultiply(inverse, pose.quaternion);\n }\n writeVec3(node.position, position);\n const rotation = node.rotationQuaternion;\n if (rotation) {\n writeQuat(rotation, quaternion);\n } else {\n node.rotationQuaternion = new this.kit.Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);\n }\n }\n}\n"]}
|
package/dist/provider.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* session exists but is not `"visible"`, the same rule IWSDK's provider
|
|
16
16
|
* applies.
|
|
17
17
|
*/
|
|
18
|
-
import { type Handedness, type HeadPose, type InputCapabilities, type InputProvider, type InputSourceSnapshot, type PresenceModality, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
18
|
+
import { type EyeGazeOptions, type Handedness, type HeadPose, type InputCapabilities, type InputProvider, type InputSourceSnapshot, type PresenceModality, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
19
19
|
import { type BabylonCameraLike, type BabylonSceneLike, type BabylonXRExperienceLike } from "./babylon-types.js";
|
|
20
20
|
export interface BabylonProviderOptions {
|
|
21
21
|
/**
|
|
@@ -36,6 +36,11 @@ export interface BabylonProviderOptions {
|
|
|
36
36
|
* camera and a mouse drag reports camera motion only.
|
|
37
37
|
*/
|
|
38
38
|
desktopGripDistance?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Tuning for the eye-gaze rule this provider applies (the filter, the
|
|
41
|
+
* tracking-loss grace). Defaults are IWSDK 1.0.0's (`EYE_GAZE_DEFAULTS`).
|
|
42
|
+
*/
|
|
43
|
+
eyeGaze?: EyeGazeOptions;
|
|
39
44
|
}
|
|
40
45
|
/** The desktop pointer source's id - stable for the life of the page. */
|
|
41
46
|
export declare const DESKTOP_SOURCE_ID = "babylon-pointer";
|
|
@@ -51,6 +56,8 @@ export declare class BabylonInputProvider implements InputProvider {
|
|
|
51
56
|
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
52
57
|
private readonly rightHanded;
|
|
53
58
|
private readonly pointer;
|
|
59
|
+
private readonly eyeGaze;
|
|
60
|
+
private frameSeconds;
|
|
54
61
|
constructor(options: BabylonProviderOptions);
|
|
55
62
|
private attachPointer;
|
|
56
63
|
private onPointer;
|
|
@@ -60,6 +67,14 @@ export declare class BabylonInputProvider implements InputProvider {
|
|
|
60
67
|
private controllers;
|
|
61
68
|
/** The hand-tracking feature, when the app enabled it. */
|
|
62
69
|
private handTracking;
|
|
70
|
+
/**
|
|
71
|
+
* The eye-tracking feature (`WebXREyeTracking`, `"xr-eye-tracking"`), when
|
|
72
|
+
* the app enabled it. Its presence in a live session is the gaze source:
|
|
73
|
+
* IWSDK's `findGazeSource` finding a `targetRayMode === "gaze"` source.
|
|
74
|
+
*/
|
|
75
|
+
private eyeTracking;
|
|
76
|
+
/** The seconds the next `sample()` integrates over (the eye-gaze filter and grace); the host sets it each frame. */
|
|
77
|
+
setFrameDelta(seconds: number): void;
|
|
63
78
|
private handFor;
|
|
64
79
|
private refreshCapabilities;
|
|
65
80
|
getCapabilities(): InputCapabilities;
|
package/dist/provider.js
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* session exists but is not `"visible"`, the same rule IWSDK's provider
|
|
16
16
|
* applies.
|
|
17
17
|
*/
|
|
18
|
-
import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
|
|
19
|
-
import { HAND_TRACKING_FEATURE, INDEX_TIP_JOINT, POINTER_EVENT_TYPES, nodeForward, defaultForward, nodeWorldPose, toQuat, toVec3, } from "./babylon-types.js";
|
|
18
|
+
import { EyeGazeInput, NO_CAPABILITIES, rayPoseFromRay, } from "@realitycollective/webxr-input";
|
|
19
|
+
import { EYE_TRACKING_FEATURE, HAND_TRACKING_FEATURE, INDEX_TIP_JOINT, POINTER_EVENT_TYPES, nodeForward, defaultForward, nodeWorldPose, toQuat, toVec3, } from "./babylon-types.js";
|
|
20
|
+
/** The seconds a sample integrates over until the host reports the real delta. */
|
|
21
|
+
const DEFAULT_FRAME_SECONDS = 1 / 60;
|
|
20
22
|
/** Trigger, then the main component, then nothing. */
|
|
21
23
|
const TRIGGER_COMPONENT = "trigger";
|
|
22
24
|
const SQUEEZE_COMPONENT = "squeeze";
|
|
@@ -34,8 +36,11 @@ export class BabylonInputProvider {
|
|
|
34
36
|
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
35
37
|
rightHanded;
|
|
36
38
|
pointer = { down: false, x: 0, y: 0, ray: null };
|
|
39
|
+
eyeGaze;
|
|
40
|
+
frameSeconds = DEFAULT_FRAME_SECONDS;
|
|
37
41
|
constructor(options) {
|
|
38
42
|
this.options = options;
|
|
43
|
+
this.eyeGaze = new EyeGazeInput(options.eyeGaze);
|
|
39
44
|
this.gripDistance = options.desktopGripDistance ?? 1;
|
|
40
45
|
this.rightHanded = options.scene.useRightHandedSystem === true;
|
|
41
46
|
this.capabilities = { ...NO_CAPABILITIES, headPose: true, gaze: true };
|
|
@@ -122,6 +127,25 @@ export class BabylonInputProvider {
|
|
|
122
127
|
? feature
|
|
123
128
|
: null;
|
|
124
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* The eye-tracking feature (`WebXREyeTracking`, `"xr-eye-tracking"`), when
|
|
132
|
+
* the app enabled it. Its presence in a live session is the gaze source:
|
|
133
|
+
* IWSDK's `findGazeSource` finding a `targetRayMode === "gaze"` source.
|
|
134
|
+
*/
|
|
135
|
+
eyeTracking() {
|
|
136
|
+
const manager = this.options.xr?.baseExperience?.featuresManager;
|
|
137
|
+
if (!manager)
|
|
138
|
+
return null;
|
|
139
|
+
const feature = manager.getEnabledFeature(EYE_TRACKING_FEATURE);
|
|
140
|
+
if (!feature || typeof feature !== "object")
|
|
141
|
+
return null;
|
|
142
|
+
const candidate = feature;
|
|
143
|
+
return typeof candidate.getEyeGaze === "function" ? feature : null;
|
|
144
|
+
}
|
|
145
|
+
/** The seconds the next `sample()` integrates over (the eye-gaze filter and grace); the host sets it each frame. */
|
|
146
|
+
setFrameDelta(seconds) {
|
|
147
|
+
this.frameSeconds = seconds;
|
|
148
|
+
}
|
|
125
149
|
handFor(controller) {
|
|
126
150
|
return this.handTracking()?.getHandByControllerId(controller.uniqueId) ?? null;
|
|
127
151
|
}
|
|
@@ -145,6 +169,7 @@ export class BabylonInputProvider {
|
|
|
145
169
|
presence: false,
|
|
146
170
|
};
|
|
147
171
|
if (inSession) {
|
|
172
|
+
next.eyeGaze = this.eyeTracking() !== null;
|
|
148
173
|
for (const controller of this.controllers()) {
|
|
149
174
|
if (controller.pointer)
|
|
150
175
|
next.rays = true;
|
|
@@ -193,6 +218,7 @@ export class BabylonInputProvider {
|
|
|
193
218
|
// A session that ended between frames leaves stale capabilities behind.
|
|
194
219
|
if (this.capabilities.rays)
|
|
195
220
|
this.refreshCapabilities();
|
|
221
|
+
this.eyeGaze.reset();
|
|
196
222
|
return this.samplePointer();
|
|
197
223
|
}
|
|
198
224
|
// A session that exists but is not visible (backgrounded, or the
|
|
@@ -201,9 +227,12 @@ export class BabylonInputProvider {
|
|
|
201
227
|
// Babylon's session manager passes the raw `XRSession` through, so its
|
|
202
228
|
// standard `visibilityState` is read straight off it.
|
|
203
229
|
const visibilityState = session.visibilityState;
|
|
204
|
-
if (visibilityState !== undefined && visibilityState !== "visible")
|
|
230
|
+
if (visibilityState !== undefined && visibilityState !== "visible") {
|
|
231
|
+
this.eyeGaze.reset();
|
|
205
232
|
return [];
|
|
233
|
+
}
|
|
206
234
|
const snapshots = [];
|
|
235
|
+
const rayPoses = {};
|
|
207
236
|
for (const controller of this.controllers()) {
|
|
208
237
|
const handedness = normalizeHandedness(controller.inputSource.handedness);
|
|
209
238
|
const hand = this.handFor(controller);
|
|
@@ -222,6 +251,10 @@ export class BabylonInputProvider {
|
|
|
222
251
|
origin: toVec3(pointerNode.getAbsolutePosition()) ?? [0, 0, 0],
|
|
223
252
|
direction: nodeForward(pointerNode, this.rightHanded),
|
|
224
253
|
};
|
|
254
|
+
// The selector pose is built from the ray (world up): the ray
|
|
255
|
+
// convention is -Z forward whatever the scene's handedness.
|
|
256
|
+
if (handedness !== "none" && !rayPoses[handedness])
|
|
257
|
+
rayPoses[handedness] = rayPoseFromRay(snapshot.ray);
|
|
225
258
|
}
|
|
226
259
|
// Babylon exposes the grip node only for a controller with one; a hand,
|
|
227
260
|
// and a controller without a grip space, carry on the pointer node.
|
|
@@ -243,7 +276,18 @@ export class BabylonInputProvider {
|
|
|
243
276
|
snapshots.push(snapshot);
|
|
244
277
|
this.sampled.set(snapshot.id, { handedness, controller });
|
|
245
278
|
}
|
|
246
|
-
|
|
279
|
+
if (!this.capabilities.eyeGaze)
|
|
280
|
+
return snapshots;
|
|
281
|
+
const eye = this.eyeTracking();
|
|
282
|
+
let pose = null;
|
|
283
|
+
const gaze = eye && eye.isEyeGazeValid !== false ? eye.getEyeGaze() : null;
|
|
284
|
+
if (gaze) {
|
|
285
|
+
const origin = toVec3(gaze.origin);
|
|
286
|
+
const direction = toVec3(gaze.direction);
|
|
287
|
+
if (origin && direction)
|
|
288
|
+
pose = rayPoseFromRay({ origin, direction });
|
|
289
|
+
}
|
|
290
|
+
return this.eyeGaze.update({ present: eye !== null, pose, rayPoses }, snapshots, this.frameSeconds);
|
|
247
291
|
}
|
|
248
292
|
/**
|
|
249
293
|
* The desktop pointer, shaped like the three.js adapter's mouse fallback:
|
|
@@ -354,6 +398,7 @@ export class BabylonInputProvider {
|
|
|
354
398
|
return nodes;
|
|
355
399
|
}
|
|
356
400
|
dispose() {
|
|
401
|
+
this.eyeGaze.reset();
|
|
357
402
|
for (const off of this.detach.splice(0))
|
|
358
403
|
off();
|
|
359
404
|
this.sampled.clear();
|
package/dist/provider.js.map
CHANGED
|
@@ -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. `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"]}
|
|
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 EyeGazeInput,\n NO_CAPABILITIES,\n rayPoseFromRay,\n type EyeGazeFrame,\n type EyeGazeOptions,\n type Handedness,\n type HeadPose,\n type InputCapabilities,\n type InputProvider,\n type InputSourceSnapshot,\n type PoseTuple,\n type PresenceModality,\n type Unsubscribe,\n type Vec3Tuple,\n} from \"@realitycollective/webxr-input\";\nimport {\n EYE_TRACKING_FEATURE,\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 BabylonEyeTrackingLike,\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 * Tuning for the eye-gaze rule this provider applies (the filter, the\n * tracking-loss grace). Defaults are IWSDK 1.0.0's (`EYE_GAZE_DEFAULTS`).\n */\n eyeGaze?: EyeGazeOptions;\n}\n\n/** The seconds a sample integrates over until the host reports the real delta. */\nconst DEFAULT_FRAME_SECONDS = 1 / 60;\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 private readonly eyeGaze: EyeGazeInput;\n private frameSeconds = DEFAULT_FRAME_SECONDS;\n\n constructor(options: BabylonProviderOptions) {\n this.options = options;\n this.eyeGaze = new EyeGazeInput(options.eyeGaze);\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 /**\n * The eye-tracking feature (`WebXREyeTracking`, `\"xr-eye-tracking\"`), when\n * the app enabled it. Its presence in a live session is the gaze source:\n * IWSDK's `findGazeSource` finding a `targetRayMode === \"gaze\"` source.\n */\n private eyeTracking(): BabylonEyeTrackingLike | null {\n const manager = this.options.xr?.baseExperience?.featuresManager;\n if (!manager) return null;\n const feature = manager.getEnabledFeature(EYE_TRACKING_FEATURE);\n if (!feature || typeof feature !== \"object\") return null;\n const candidate = feature as Partial<BabylonEyeTrackingLike>;\n return typeof candidate.getEyeGaze === \"function\" ? (feature as BabylonEyeTrackingLike) : null;\n }\n\n /** The seconds the next `sample()` integrates over (the eye-gaze filter and grace); the host sets it each frame. */\n setFrameDelta(seconds: number): void {\n this.frameSeconds = seconds;\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 next.eyeGaze = this.eyeTracking() !== null;\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 this.eyeGaze.reset();\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\") {\n this.eyeGaze.reset();\n return [];\n }\n\n const snapshots: InputSourceSnapshot[] = [];\n const rayPoses: EyeGazeFrame[\"rayPoses\"] = {};\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 // The selector pose is built from the ray (world up): the ray\n // convention is -Z forward whatever the scene's handedness.\n if (handedness !== \"none\" && !rayPoses[handedness]) rayPoses[handedness] = rayPoseFromRay(snapshot.ray);\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 if (!this.capabilities.eyeGaze) return snapshots;\n const eye = this.eyeTracking();\n let pose: PoseTuple | null = null;\n const gaze = eye && eye.isEyeGazeValid !== false ? eye.getEyeGaze() : null;\n if (gaze) {\n const origin = toVec3(gaze.origin);\n const direction = toVec3(gaze.direction);\n if (origin && direction) pose = rayPoseFromRay({ origin, direction });\n }\n return this.eyeGaze.update({ present: eye !== null, pose, rayPoses }, snapshots, this.frameSeconds);\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 this.eyeGaze.reset();\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"]}
|