@realitycollective/babylon-interactions 0.1.1-preview.1 → 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 +29 -0
- package/dist/babylon-types.d.ts +121 -2
- 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 +19 -2
- package/dist/provider.js +65 -5
- package/dist/provider.js.map +1 -1
- package/dist/transform-port.d.ts +45 -6
- package/dist/transform-port.js +71 -14
- package/dist/transform-port.js.map +1 -1
- package/package.json +3 -3
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"]}
|
|
@@ -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
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Capabilities are derived from the LIVE session, never from the requested
|
|
13
13
|
* configuration, and are re-published on session start, session end and
|
|
14
|
-
* controller add/remove.
|
|
14
|
+
* controller add/remove. `sample()` reports no sources at all while the
|
|
15
|
+
* session exists but is not `"visible"`, the same rule IWSDK's provider
|
|
16
|
+
* applies.
|
|
15
17
|
*/
|
|
16
|
-
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";
|
|
17
19
|
import { type BabylonCameraLike, type BabylonSceneLike, type BabylonXRExperienceLike } from "./babylon-types.js";
|
|
18
20
|
export interface BabylonProviderOptions {
|
|
19
21
|
/**
|
|
@@ -34,6 +36,11 @@ export interface BabylonProviderOptions {
|
|
|
34
36
|
* camera and a mouse drag reports camera motion only.
|
|
35
37
|
*/
|
|
36
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;
|
|
37
44
|
}
|
|
38
45
|
/** The desktop pointer source's id - stable for the life of the page. */
|
|
39
46
|
export declare const DESKTOP_SOURCE_ID = "babylon-pointer";
|
|
@@ -49,6 +56,8 @@ export declare class BabylonInputProvider implements InputProvider {
|
|
|
49
56
|
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
50
57
|
private readonly rightHanded;
|
|
51
58
|
private readonly pointer;
|
|
59
|
+
private readonly eyeGaze;
|
|
60
|
+
private frameSeconds;
|
|
52
61
|
constructor(options: BabylonProviderOptions);
|
|
53
62
|
private attachPointer;
|
|
54
63
|
private onPointer;
|
|
@@ -58,6 +67,14 @@ export declare class BabylonInputProvider implements InputProvider {
|
|
|
58
67
|
private controllers;
|
|
59
68
|
/** The hand-tracking feature, when the app enabled it. */
|
|
60
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;
|
|
61
78
|
private handFor;
|
|
62
79
|
private refreshCapabilities;
|
|
63
80
|
getCapabilities(): InputCapabilities;
|
package/dist/provider.js
CHANGED
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Capabilities are derived from the LIVE session, never from the requested
|
|
13
13
|
* configuration, and are re-published on session start, session end and
|
|
14
|
-
* controller add/remove.
|
|
14
|
+
* controller add/remove. `sample()` reports no sources at all while the
|
|
15
|
+
* session exists but is not `"visible"`, the same rule IWSDK's provider
|
|
16
|
+
* applies.
|
|
15
17
|
*/
|
|
16
|
-
import { NO_CAPABILITIES, } from "@realitycollective/webxr-input";
|
|
17
|
-
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;
|
|
18
22
|
/** Trigger, then the main component, then nothing. */
|
|
19
23
|
const TRIGGER_COMPONENT = "trigger";
|
|
20
24
|
const SQUEEZE_COMPONENT = "squeeze";
|
|
@@ -32,8 +36,11 @@ export class BabylonInputProvider {
|
|
|
32
36
|
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
33
37
|
rightHanded;
|
|
34
38
|
pointer = { down: false, x: 0, y: 0, ray: null };
|
|
39
|
+
eyeGaze;
|
|
40
|
+
frameSeconds = DEFAULT_FRAME_SECONDS;
|
|
35
41
|
constructor(options) {
|
|
36
42
|
this.options = options;
|
|
43
|
+
this.eyeGaze = new EyeGazeInput(options.eyeGaze);
|
|
37
44
|
this.gripDistance = options.desktopGripDistance ?? 1;
|
|
38
45
|
this.rightHanded = options.scene.useRightHandedSystem === true;
|
|
39
46
|
this.capabilities = { ...NO_CAPABILITIES, headPose: true, gaze: true };
|
|
@@ -120,6 +127,25 @@ export class BabylonInputProvider {
|
|
|
120
127
|
? feature
|
|
121
128
|
: null;
|
|
122
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
|
+
}
|
|
123
149
|
handFor(controller) {
|
|
124
150
|
return this.handTracking()?.getHandByControllerId(controller.uniqueId) ?? null;
|
|
125
151
|
}
|
|
@@ -143,6 +169,7 @@ export class BabylonInputProvider {
|
|
|
143
169
|
presence: false,
|
|
144
170
|
};
|
|
145
171
|
if (inSession) {
|
|
172
|
+
next.eyeGaze = this.eyeTracking() !== null;
|
|
146
173
|
for (const controller of this.controllers()) {
|
|
147
174
|
if (controller.pointer)
|
|
148
175
|
next.rays = true;
|
|
@@ -186,13 +213,26 @@ export class BabylonInputProvider {
|
|
|
186
213
|
// -- sampling -----------------------------------------------------------------
|
|
187
214
|
sample() {
|
|
188
215
|
this.sampled.clear();
|
|
189
|
-
|
|
216
|
+
const session = this.session();
|
|
217
|
+
if (session === null) {
|
|
190
218
|
// A session that ended between frames leaves stale capabilities behind.
|
|
191
219
|
if (this.capabilities.rays)
|
|
192
220
|
this.refreshCapabilities();
|
|
221
|
+
this.eyeGaze.reset();
|
|
193
222
|
return this.samplePointer();
|
|
194
223
|
}
|
|
224
|
+
// A session that exists but is not visible (backgrounded, or the
|
|
225
|
+
// browser's own "content is obscured" state) reports no sources at all,
|
|
226
|
+
// the same rule IWSDK's provider applies to `world.visibilityState`.
|
|
227
|
+
// Babylon's session manager passes the raw `XRSession` through, so its
|
|
228
|
+
// standard `visibilityState` is read straight off it.
|
|
229
|
+
const visibilityState = session.visibilityState;
|
|
230
|
+
if (visibilityState !== undefined && visibilityState !== "visible") {
|
|
231
|
+
this.eyeGaze.reset();
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
195
234
|
const snapshots = [];
|
|
235
|
+
const rayPoses = {};
|
|
196
236
|
for (const controller of this.controllers()) {
|
|
197
237
|
const handedness = normalizeHandedness(controller.inputSource.handedness);
|
|
198
238
|
const hand = this.handFor(controller);
|
|
@@ -211,6 +251,10 @@ export class BabylonInputProvider {
|
|
|
211
251
|
origin: toVec3(pointerNode.getAbsolutePosition()) ?? [0, 0, 0],
|
|
212
252
|
direction: nodeForward(pointerNode, this.rightHanded),
|
|
213
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);
|
|
214
258
|
}
|
|
215
259
|
// Babylon exposes the grip node only for a controller with one; a hand,
|
|
216
260
|
// and a controller without a grip space, carry on the pointer node.
|
|
@@ -220,6 +264,10 @@ export class BabylonInputProvider {
|
|
|
220
264
|
const tip = jointPosition(hand, INDEX_TIP_JOINT);
|
|
221
265
|
if (tip)
|
|
222
266
|
snapshot.indexTip = tip;
|
|
267
|
+
// A controller has no fingertip: its index tip is its ray origin, as
|
|
268
|
+
// IWSDK's input rig makes it, so a controller pokes as a finger does.
|
|
269
|
+
else if (!controller.inputSource.hand && snapshot.ray)
|
|
270
|
+
snapshot.indexTip = [...snapshot.ray.origin];
|
|
223
271
|
if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {
|
|
224
272
|
snapshot.hapticsAvailable = true;
|
|
225
273
|
}
|
|
@@ -228,7 +276,18 @@ export class BabylonInputProvider {
|
|
|
228
276
|
snapshots.push(snapshot);
|
|
229
277
|
this.sampled.set(snapshot.id, { handedness, controller });
|
|
230
278
|
}
|
|
231
|
-
|
|
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);
|
|
232
291
|
}
|
|
233
292
|
/**
|
|
234
293
|
* The desktop pointer, shaped like the three.js adapter's mouse fallback:
|
|
@@ -339,6 +398,7 @@ export class BabylonInputProvider {
|
|
|
339
398
|
return nodes;
|
|
340
399
|
}
|
|
341
400
|
dispose() {
|
|
401
|
+
this.eyeGaze.reset();
|
|
342
402
|
for (const off of this.detach.splice(0))
|
|
343
403
|
off();
|
|
344
404
|
this.sampled.clear();
|