@realitycollective/babylon-interactions 0.1.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/babylon-types.d.ts +220 -0
- package/dist/babylon-types.js +92 -0
- package/dist/babylon-types.js.map +1 -0
- package/dist/hit-tester.d.ts +57 -0
- package/dist/hit-tester.js +90 -0
- package/dist/hit-tester.js.map +1 -0
- package/dist/host.d.ts +49 -0
- package/dist/host.js +86 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +113 -0
- package/dist/provider.js +371 -0
- package/dist/provider.js.map +1 -0
- package/dist/transform-port.d.ts +72 -0
- package/dist/transform-port.js +90 -0
- package/dist/transform-port.js.map +1 -0
- package/package.json +54 -0
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createBabylonInteractions - one-call setup for the Babylon adapter.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });
|
|
6
|
+
* interactions.register({ id: "button", behaviours: [{ kind: "press" }] }, buttonMesh);
|
|
7
|
+
* interactions.runtime.onEvent((event) => console.log(event.type));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* With `attachToScene` the render loop drives itself from
|
|
11
|
+
* `scene.onBeforeRenderObservable` and the engine's own frame delta.
|
|
12
|
+
* Without it, call `update(dt)` yourself with seconds.
|
|
13
|
+
*/
|
|
14
|
+
import { InteractionRuntime, type DwellConfig, type InteractableDescriptor } from "@realitycollective/webxr-interactions";
|
|
15
|
+
import type { BabylonTransformNodeLike } from "./babylon-types.js";
|
|
16
|
+
import { BabylonHitTester, type BabylonHitTesterOptions, type BabylonPickWithRay } from "./hit-tester.js";
|
|
17
|
+
import { BabylonTransformPort, type BabylonTransformPortOptions } from "./transform-port.js";
|
|
18
|
+
import { BabylonInputProvider, type BabylonProviderOptions } from "./provider.js";
|
|
19
|
+
export interface BabylonInteractionsOptions extends BabylonProviderOptions, BabylonHitTesterOptions {
|
|
20
|
+
dwellDefaults?: DwellConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Drive `update(dt)` from `scene.onBeforeRenderObservable`, using the
|
|
23
|
+
* engine's frame delta in seconds. Default false - the app calls
|
|
24
|
+
* `update` from its own loop.
|
|
25
|
+
*/
|
|
26
|
+
attachToScene?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface BabylonRegisterOptions extends BabylonTransformPortOptions {
|
|
29
|
+
/** Targeting radius for the sphere hit-tester. Default 0.1 m. */
|
|
30
|
+
targetRadius?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class BabylonInteractions {
|
|
33
|
+
readonly runtime: InteractionRuntime;
|
|
34
|
+
readonly provider: BabylonInputProvider;
|
|
35
|
+
readonly hitTester: BabylonHitTester;
|
|
36
|
+
private readonly ports;
|
|
37
|
+
private detachScene;
|
|
38
|
+
constructor(options: BabylonInteractionsOptions);
|
|
39
|
+
/** Supply or replace the app's mesh-accurate pick. */
|
|
40
|
+
setPickWithRay(pick: BabylonPickWithRay | null): void;
|
|
41
|
+
/** Register an interactable with its Babylon node. */
|
|
42
|
+
register(descriptor: InteractableDescriptor, node: BabylonTransformNodeLike, options?: BabylonRegisterOptions): BabylonTransformPort;
|
|
43
|
+
unregister(id: string): void;
|
|
44
|
+
getPort(id: string): BabylonTransformPort | undefined;
|
|
45
|
+
update(dt: number): void;
|
|
46
|
+
dispose(): void;
|
|
47
|
+
private attachToScene;
|
|
48
|
+
}
|
|
49
|
+
export declare function createBabylonInteractions(options: BabylonInteractionsOptions): BabylonInteractions;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createBabylonInteractions - one-call setup for the Babylon adapter.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });
|
|
6
|
+
* interactions.register({ id: "button", behaviours: [{ kind: "press" }] }, buttonMesh);
|
|
7
|
+
* interactions.runtime.onEvent((event) => console.log(event.type));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* With `attachToScene` the render loop drives itself from
|
|
11
|
+
* `scene.onBeforeRenderObservable` and the engine's own frame delta.
|
|
12
|
+
* Without it, call `update(dt)` yourself with seconds.
|
|
13
|
+
*/
|
|
14
|
+
import { InteractionRuntime, } from "@realitycollective/webxr-interactions";
|
|
15
|
+
import { BabylonHitTester, } from "./hit-tester.js";
|
|
16
|
+
import { BabylonTransformPort } from "./transform-port.js";
|
|
17
|
+
import { BabylonInputProvider } from "./provider.js";
|
|
18
|
+
/** Longest frame the scene-attached loop will report, in seconds. */
|
|
19
|
+
const MAX_FRAME_SECONDS = 0.1;
|
|
20
|
+
export class BabylonInteractions {
|
|
21
|
+
runtime;
|
|
22
|
+
provider;
|
|
23
|
+
hitTester;
|
|
24
|
+
ports = new Map();
|
|
25
|
+
detachScene = null;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.provider = new BabylonInputProvider(options);
|
|
28
|
+
this.hitTester = new BabylonHitTester(options);
|
|
29
|
+
this.runtime = new InteractionRuntime({
|
|
30
|
+
provider: this.provider,
|
|
31
|
+
hitTester: this.hitTester,
|
|
32
|
+
...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),
|
|
33
|
+
});
|
|
34
|
+
if (options.attachToScene)
|
|
35
|
+
this.attachToScene(options);
|
|
36
|
+
}
|
|
37
|
+
/** Supply or replace the app's mesh-accurate pick. */
|
|
38
|
+
setPickWithRay(pick) {
|
|
39
|
+
this.hitTester.setPickWithRay(pick);
|
|
40
|
+
}
|
|
41
|
+
/** Register an interactable with its Babylon node. */
|
|
42
|
+
register(descriptor, node, options = {}) {
|
|
43
|
+
const port = new BabylonTransformPort(node, options.createQuaternion ? { createQuaternion: options.createQuaternion } : {});
|
|
44
|
+
this.ports.set(descriptor.id, port);
|
|
45
|
+
this.hitTester.register(descriptor.id, node, options.targetRadius);
|
|
46
|
+
this.runtime.registerInteractable(descriptor, { transform: port });
|
|
47
|
+
return port;
|
|
48
|
+
}
|
|
49
|
+
unregister(id) {
|
|
50
|
+
this.runtime.unregisterInteractable(id);
|
|
51
|
+
this.hitTester.unregister(id);
|
|
52
|
+
this.ports.delete(id);
|
|
53
|
+
}
|
|
54
|
+
getPort(id) {
|
|
55
|
+
return this.ports.get(id);
|
|
56
|
+
}
|
|
57
|
+
update(dt) {
|
|
58
|
+
this.runtime.update(dt);
|
|
59
|
+
}
|
|
60
|
+
dispose() {
|
|
61
|
+
this.detachScene?.();
|
|
62
|
+
this.detachScene = null;
|
|
63
|
+
this.runtime.dispose();
|
|
64
|
+
this.provider.dispose();
|
|
65
|
+
}
|
|
66
|
+
attachToScene(options) {
|
|
67
|
+
const observable = options.scene.onBeforeRenderObservable;
|
|
68
|
+
if (!observable)
|
|
69
|
+
return;
|
|
70
|
+
const engine = options.scene.getEngine?.();
|
|
71
|
+
const observer = observable.add(() => {
|
|
72
|
+
// Babylon reports the frame delta in milliseconds. A long frame - a tab
|
|
73
|
+
// that was backgrounded, a slow first frame - is clamped so behaviours
|
|
74
|
+
// integrate over a sane step rather than jumping.
|
|
75
|
+
const ms = engine?.getDeltaTime() ?? 0;
|
|
76
|
+
this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, ms / 1000)));
|
|
77
|
+
});
|
|
78
|
+
this.detachScene = () => {
|
|
79
|
+
observable.remove(observer);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function createBabylonInteractions(options) {
|
|
84
|
+
return new BabylonInteractions(options);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +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"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/babylon-interactions - the Babylon.js engine adapter:
|
|
3
|
+
* Babylon WebXR input, sphere or app-supplied hit-testing, and transform
|
|
4
|
+
* ports over Babylon nodes. The core is re-exported wholesale, so apps
|
|
5
|
+
* depend on exactly this package.
|
|
6
|
+
*
|
|
7
|
+
* Structurally typed against the Babylon API rather than importing
|
|
8
|
+
* `@babylonjs/core`, in the same way as the XR Blocks adapter, so an
|
|
9
|
+
* upstream release cannot break the install.
|
|
10
|
+
*/
|
|
11
|
+
export * from "@realitycollective/webxr-interactions";
|
|
12
|
+
export * from "./babylon-types.js";
|
|
13
|
+
export * from "./provider.js";
|
|
14
|
+
export * from "./hit-tester.js";
|
|
15
|
+
export * from "./transform-port.js";
|
|
16
|
+
export * from "./host.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/babylon-interactions - the Babylon.js engine adapter:
|
|
3
|
+
* Babylon WebXR input, sphere or app-supplied hit-testing, and transform
|
|
4
|
+
* ports over Babylon nodes. The core is re-exported wholesale, so apps
|
|
5
|
+
* depend on exactly this package.
|
|
6
|
+
*
|
|
7
|
+
* Structurally typed against the Babylon API rather than importing
|
|
8
|
+
* `@babylonjs/core`, in the same way as the XR Blocks adapter, so an
|
|
9
|
+
* upstream release cannot break the install.
|
|
10
|
+
*/
|
|
11
|
+
export * from "@realitycollective/webxr-interactions";
|
|
12
|
+
export * from "./babylon-types.js";
|
|
13
|
+
export * from "./provider.js";
|
|
14
|
+
export * from "./hit-tester.js";
|
|
15
|
+
export * from "./transform-port.js";
|
|
16
|
+
export * from "./host.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BabylonInputProvider - the Babylon.js input provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads a `WebXRDefaultExperience`: its `input.controllers` for poses and
|
|
5
|
+
* motion-controller components, its session manager for what is live, and
|
|
6
|
+
* the hand-tracking feature for fingertip joints. Falls back to the scene's
|
|
7
|
+
* own pointer (`scene.onPointerObservable`) when no immersive session is
|
|
8
|
+
* running, so the same scene is usable in a flat browser.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here imports `@babylonjs/core` - see `babylon-types.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Capabilities are derived from the LIVE session, never from the requested
|
|
13
|
+
* configuration, and are re-published on session start, session end and
|
|
14
|
+
* controller add/remove.
|
|
15
|
+
*/
|
|
16
|
+
import { type HeadPose, type InputCapabilities, type InputProvider, type InputSourceSnapshot, type Unsubscribe } from "@realitycollective/webxr-input";
|
|
17
|
+
import { type BabylonCameraLike, type BabylonSceneLike, type BabylonXRExperienceLike } from "./babylon-types.js";
|
|
18
|
+
export interface BabylonProviderOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The `WebXRDefaultExperience` (or anything with the same surface).
|
|
21
|
+
* Omit it, or pass null, for a desktop-only app: the provider then only
|
|
22
|
+
* ever reports the scene pointer.
|
|
23
|
+
*/
|
|
24
|
+
xr?: BabylonXRExperienceLike | null;
|
|
25
|
+
/** The scene. Supplies the pointer fallback, the camera and the engine. */
|
|
26
|
+
scene: BabylonSceneLike;
|
|
27
|
+
/** Head pose camera. Defaults to `scene.activeCamera`. */
|
|
28
|
+
camera?: BabylonCameraLike | null;
|
|
29
|
+
/**
|
|
30
|
+
* Metres along the pointer ray at which the desktop fallback places its
|
|
31
|
+
* synthetic grip pose. Default 1 metre, matching the three.js adapter, so
|
|
32
|
+
* grab, hinge, dial and slide follow the cursor on desktop. Set it near
|
|
33
|
+
* the distance of the things being manipulated; at 0 the grip sits on the
|
|
34
|
+
* camera and a mouse drag reports camera motion only.
|
|
35
|
+
*/
|
|
36
|
+
desktopGripDistance?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Which side's visual a presence call targets. `"all"` is both. */
|
|
39
|
+
export type PresenceTarget = "left" | "right" | "none" | "all";
|
|
40
|
+
/**
|
|
41
|
+
* Which family of visuals presence shows. Babylon chooses this per input
|
|
42
|
+
* source, so this provider reports every mode as unavailable.
|
|
43
|
+
*/
|
|
44
|
+
export type PresenceModality = "hands" | "controllers" | "auto";
|
|
45
|
+
/** The desktop pointer source's id - stable for the life of the page. */
|
|
46
|
+
export declare const DESKTOP_SOURCE_ID = "babylon-pointer";
|
|
47
|
+
export declare class BabylonInputProvider implements InputProvider {
|
|
48
|
+
private readonly options;
|
|
49
|
+
private capabilities;
|
|
50
|
+
private readonly capsListeners;
|
|
51
|
+
private readonly sourceListeners;
|
|
52
|
+
/** id -> handedness + controller, recorded while sampling, read by pulse. */
|
|
53
|
+
private readonly sampled;
|
|
54
|
+
private readonly detach;
|
|
55
|
+
private readonly gripDistance;
|
|
56
|
+
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
57
|
+
private readonly rightHanded;
|
|
58
|
+
private readonly pointer;
|
|
59
|
+
constructor(options: BabylonProviderOptions);
|
|
60
|
+
private attachPointer;
|
|
61
|
+
private onPointer;
|
|
62
|
+
private attachSession;
|
|
63
|
+
/** The live XR session, or null when none is running. */
|
|
64
|
+
private session;
|
|
65
|
+
private controllers;
|
|
66
|
+
/** The hand-tracking feature, when the app enabled it. */
|
|
67
|
+
private handTracking;
|
|
68
|
+
private handFor;
|
|
69
|
+
private refreshCapabilities;
|
|
70
|
+
getCapabilities(): InputCapabilities;
|
|
71
|
+
onCapabilitiesChanged(listener: (c: InputCapabilities) => void): Unsubscribe;
|
|
72
|
+
onSourcesChanged(listener: () => void): Unsubscribe;
|
|
73
|
+
sample(): readonly InputSourceSnapshot[];
|
|
74
|
+
/**
|
|
75
|
+
* The desktop pointer, shaped like the three.js adapter's mouse fallback:
|
|
76
|
+
* one source, `handedness: "none"`, `kind: "pointer2d"`.
|
|
77
|
+
*/
|
|
78
|
+
private samplePointer;
|
|
79
|
+
/** Pick the scene at the last known cursor position for a ray. */
|
|
80
|
+
private pickRay;
|
|
81
|
+
private camera;
|
|
82
|
+
getHeadPose(): HeadPose;
|
|
83
|
+
/**
|
|
84
|
+
* Babylon routes haptics through the motion controller rather than the
|
|
85
|
+
* gamepad actuator, so a source with no motion controller cannot pulse
|
|
86
|
+
* even when the gamepad reports an actuator.
|
|
87
|
+
*/
|
|
88
|
+
pulse(sourceId: string, intensity: number, durationMs: number): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* True when Babylon has built at least one visual this provider can hide:
|
|
91
|
+
* a motion controller root mesh, or a hand mesh from the hand-tracking
|
|
92
|
+
* feature. Becomes `capabilities.presence` at
|
|
93
|
+
* `@realitycollective/webxr-input` 0.1.1.
|
|
94
|
+
*/
|
|
95
|
+
get supportsPresence(): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Show or hide the visuals Babylon built for the targeted sides. Returns
|
|
98
|
+
* false when the target has nothing to show or hide. `"none"` targets no
|
|
99
|
+
* side, so it reports whether presence is usable at all without changing
|
|
100
|
+
* anything.
|
|
101
|
+
*/
|
|
102
|
+
setPresenceVisible(target: PresenceTarget, visible: boolean): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Always false. Babylon picks the visual per input source - a controller
|
|
105
|
+
* gets its motion-controller model, a tracked hand gets the hand mesh -
|
|
106
|
+
* and exposes no switch between the two. IWSDK builds both families, so
|
|
107
|
+
* its adapter implements this.
|
|
108
|
+
*/
|
|
109
|
+
setPresenceModality(_mode: PresenceModality): boolean;
|
|
110
|
+
/** Every node that represents this controller on screen. */
|
|
111
|
+
private visualsFor;
|
|
112
|
+
dispose(): void;
|
|
113
|
+
}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BabylonInputProvider - the Babylon.js input provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads a `WebXRDefaultExperience`: its `input.controllers` for poses and
|
|
5
|
+
* motion-controller components, its session manager for what is live, and
|
|
6
|
+
* the hand-tracking feature for fingertip joints. Falls back to the scene's
|
|
7
|
+
* own pointer (`scene.onPointerObservable`) when no immersive session is
|
|
8
|
+
* running, so the same scene is usable in a flat browser.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here imports `@babylonjs/core` - see `babylon-types.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Capabilities are derived from the LIVE session, never from the requested
|
|
13
|
+
* configuration, and are re-published on session start, session end and
|
|
14
|
+
* controller add/remove.
|
|
15
|
+
*/
|
|
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
|
+
/** Trigger, then the main component, then nothing. */
|
|
19
|
+
const TRIGGER_COMPONENT = "trigger";
|
|
20
|
+
const SQUEEZE_COMPONENT = "squeeze";
|
|
21
|
+
/** The desktop pointer source's id - stable for the life of the page. */
|
|
22
|
+
export const DESKTOP_SOURCE_ID = "babylon-pointer";
|
|
23
|
+
export class BabylonInputProvider {
|
|
24
|
+
options;
|
|
25
|
+
capabilities;
|
|
26
|
+
capsListeners = new Set();
|
|
27
|
+
sourceListeners = new Set();
|
|
28
|
+
/** id -> handedness + controller, recorded while sampling, read by pulse. */
|
|
29
|
+
sampled = new Map();
|
|
30
|
+
detach = [];
|
|
31
|
+
gripDistance;
|
|
32
|
+
/** Read once from `scene.useRightHandedSystem`; flips forward to -Z. */
|
|
33
|
+
rightHanded;
|
|
34
|
+
pointer = { down: false, x: 0, y: 0, ray: null };
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
this.gripDistance = options.desktopGripDistance ?? 1;
|
|
38
|
+
this.rightHanded = options.scene.useRightHandedSystem === true;
|
|
39
|
+
this.capabilities = { ...NO_CAPABILITIES, headPose: true, gaze: true };
|
|
40
|
+
this.attachPointer();
|
|
41
|
+
this.attachSession();
|
|
42
|
+
this.refreshCapabilities();
|
|
43
|
+
}
|
|
44
|
+
// -- wiring -------------------------------------------------------------------
|
|
45
|
+
attachPointer() {
|
|
46
|
+
const observable = this.options.scene.onPointerObservable;
|
|
47
|
+
if (!observable)
|
|
48
|
+
return;
|
|
49
|
+
const observer = observable.add((info) => this.onPointer(info));
|
|
50
|
+
this.detach.push(() => {
|
|
51
|
+
observable.remove(observer);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
onPointer(info) {
|
|
55
|
+
const event = info.event;
|
|
56
|
+
if (event?.clientX !== undefined)
|
|
57
|
+
this.pointer.x = event.clientX;
|
|
58
|
+
if (event?.clientY !== undefined)
|
|
59
|
+
this.pointer.y = event.clientY;
|
|
60
|
+
// Babylon hands the pick it already did for this event; reuse it rather
|
|
61
|
+
// than picking the scene a second time on the next sample.
|
|
62
|
+
const ray = info.pickInfo?.ray;
|
|
63
|
+
if (ray) {
|
|
64
|
+
this.pointer.ray = {
|
|
65
|
+
origin: toVec3(ray.origin) ?? [0, 0, 0],
|
|
66
|
+
direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Only the primary button selects. Babylon reports it as button 0, the
|
|
70
|
+
// same numbering as a DOM PointerEvent.
|
|
71
|
+
if (info.type === POINTER_EVENT_TYPES.down && (event?.button ?? 0) === 0) {
|
|
72
|
+
this.pointer.down = true;
|
|
73
|
+
}
|
|
74
|
+
if (info.type === POINTER_EVENT_TYPES.up && (event?.button ?? 0) === 0) {
|
|
75
|
+
this.pointer.down = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
attachSession() {
|
|
79
|
+
const manager = this.options.xr?.baseExperience?.sessionManager;
|
|
80
|
+
const input = this.options.xr?.input;
|
|
81
|
+
const republish = () => {
|
|
82
|
+
this.refreshCapabilities();
|
|
83
|
+
for (const listener of [...this.sourceListeners])
|
|
84
|
+
listener();
|
|
85
|
+
};
|
|
86
|
+
const subscribe = (observable, handler) => {
|
|
87
|
+
if (!observable)
|
|
88
|
+
return;
|
|
89
|
+
const observer = observable.add(() => handler());
|
|
90
|
+
this.detach.push(() => {
|
|
91
|
+
observable.remove(observer);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
subscribe(manager?.onXRSessionInit, republish);
|
|
95
|
+
subscribe(manager?.onXRSessionEnded, () => {
|
|
96
|
+
this.sampled.clear();
|
|
97
|
+
republish();
|
|
98
|
+
});
|
|
99
|
+
subscribe(input?.onControllerAddedObservable, republish);
|
|
100
|
+
subscribe(input?.onControllerRemovedObservable, republish);
|
|
101
|
+
}
|
|
102
|
+
// -- capabilities -------------------------------------------------------------
|
|
103
|
+
/** The live XR session, or null when none is running. */
|
|
104
|
+
session() {
|
|
105
|
+
return this.options.xr?.baseExperience?.sessionManager?.session ?? null;
|
|
106
|
+
}
|
|
107
|
+
controllers() {
|
|
108
|
+
return this.options.xr?.input?.controllers ?? [];
|
|
109
|
+
}
|
|
110
|
+
/** The hand-tracking feature, when the app enabled it. */
|
|
111
|
+
handTracking() {
|
|
112
|
+
const manager = this.options.xr?.baseExperience?.featuresManager;
|
|
113
|
+
if (!manager)
|
|
114
|
+
return null;
|
|
115
|
+
const feature = manager.getEnabledFeature(HAND_TRACKING_FEATURE);
|
|
116
|
+
if (!feature || typeof feature !== "object")
|
|
117
|
+
return null;
|
|
118
|
+
const candidate = feature;
|
|
119
|
+
return typeof candidate.getHandByControllerId === "function"
|
|
120
|
+
? feature
|
|
121
|
+
: null;
|
|
122
|
+
}
|
|
123
|
+
handFor(controller) {
|
|
124
|
+
return this.handTracking()?.getHandByControllerId(controller.uniqueId) ?? null;
|
|
125
|
+
}
|
|
126
|
+
refreshCapabilities() {
|
|
127
|
+
const inSession = this.session() !== null;
|
|
128
|
+
const desktop = !inSession && this.options.scene.onPointerObservable !== undefined;
|
|
129
|
+
// Babylon reports no native grab observation, so grabs are always
|
|
130
|
+
// poseOnly: the core drives the transform through the port.
|
|
131
|
+
const next = {
|
|
132
|
+
...NO_CAPABILITIES,
|
|
133
|
+
headPose: true,
|
|
134
|
+
gaze: true,
|
|
135
|
+
pointer2d: desktop,
|
|
136
|
+
// The pointer fallback synthesises a grip on the ray, so hand-driven
|
|
137
|
+
// behaviours negotiate on desktop too. Without this every one of them
|
|
138
|
+
// switches itself off off-headset and only press remains usable.
|
|
139
|
+
grabs: desktop ? "poseOnly" : NO_CAPABILITIES.grabs,
|
|
140
|
+
};
|
|
141
|
+
if (inSession) {
|
|
142
|
+
for (const controller of this.controllers()) {
|
|
143
|
+
if (controller.pointer)
|
|
144
|
+
next.rays = true;
|
|
145
|
+
if (controller.grip ?? controller.pointer)
|
|
146
|
+
next.grabs = "poseOnly";
|
|
147
|
+
if (controller.inputSource.hand) {
|
|
148
|
+
next.handJoints = true;
|
|
149
|
+
next.pokes = true;
|
|
150
|
+
}
|
|
151
|
+
// Pinch strength needs the tracked hand itself, not just the flag on
|
|
152
|
+
// the input source, so it is gated on the feature reporting a hand.
|
|
153
|
+
if (this.handFor(controller))
|
|
154
|
+
next.pinch = true;
|
|
155
|
+
if (controller.motionController)
|
|
156
|
+
next.buttonsAxes = true;
|
|
157
|
+
if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {
|
|
158
|
+
next.haptics = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const changed = JSON.stringify(next) !== JSON.stringify(this.capabilities);
|
|
163
|
+
this.capabilities = next;
|
|
164
|
+
if (changed) {
|
|
165
|
+
for (const listener of [...this.capsListeners])
|
|
166
|
+
listener(next);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
getCapabilities() {
|
|
170
|
+
return this.capabilities;
|
|
171
|
+
}
|
|
172
|
+
onCapabilitiesChanged(listener) {
|
|
173
|
+
this.capsListeners.add(listener);
|
|
174
|
+
return () => this.capsListeners.delete(listener);
|
|
175
|
+
}
|
|
176
|
+
onSourcesChanged(listener) {
|
|
177
|
+
this.sourceListeners.add(listener);
|
|
178
|
+
return () => this.sourceListeners.delete(listener);
|
|
179
|
+
}
|
|
180
|
+
// -- sampling -----------------------------------------------------------------
|
|
181
|
+
sample() {
|
|
182
|
+
this.sampled.clear();
|
|
183
|
+
if (this.session() === null) {
|
|
184
|
+
// A session that ended between frames leaves stale capabilities behind.
|
|
185
|
+
if (this.capabilities.rays)
|
|
186
|
+
this.refreshCapabilities();
|
|
187
|
+
return this.samplePointer();
|
|
188
|
+
}
|
|
189
|
+
const snapshots = [];
|
|
190
|
+
for (const controller of this.controllers()) {
|
|
191
|
+
const handedness = normalizeHandedness(controller.inputSource.handedness);
|
|
192
|
+
const hand = this.handFor(controller);
|
|
193
|
+
const pointerNode = controller.pointer;
|
|
194
|
+
const snapshot = {
|
|
195
|
+
id: controller.uniqueId,
|
|
196
|
+
kind: controller.inputSource.hand ? "hand" : "controller",
|
|
197
|
+
handedness,
|
|
198
|
+
select: componentValue(controller.motionController?.getComponentOfType?.(TRIGGER_COMPONENT) ??
|
|
199
|
+
controller.motionController?.getMainComponent?.() ??
|
|
200
|
+
null),
|
|
201
|
+
squeeze: componentValue(controller.motionController?.getComponentOfType?.(SQUEEZE_COMPONENT) ?? null),
|
|
202
|
+
};
|
|
203
|
+
if (pointerNode) {
|
|
204
|
+
snapshot.ray = {
|
|
205
|
+
origin: toVec3(pointerNode.getAbsolutePosition()) ?? [0, 0, 0],
|
|
206
|
+
direction: nodeForward(pointerNode, this.rightHanded),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Babylon exposes the grip node only for a controller with one; a hand,
|
|
210
|
+
// and a controller without a grip space, carry on the pointer node.
|
|
211
|
+
const gripNode = controller.grip ?? pointerNode;
|
|
212
|
+
if (gripNode)
|
|
213
|
+
snapshot.gripPose = nodeWorldPose(gripNode);
|
|
214
|
+
const tip = jointPosition(hand, INDEX_TIP_JOINT);
|
|
215
|
+
if (tip)
|
|
216
|
+
snapshot.indexTip = tip;
|
|
217
|
+
if ((controller.inputSource.gamepad?.hapticActuators?.length ?? 0) > 0) {
|
|
218
|
+
snapshot.hapticsAvailable = true;
|
|
219
|
+
}
|
|
220
|
+
// Babylon reports no per-pose velocity, so nothing is set here and the
|
|
221
|
+
// core's VelocityTracker derives it from consecutive grip poses.
|
|
222
|
+
snapshots.push(snapshot);
|
|
223
|
+
this.sampled.set(snapshot.id, { handedness, controller });
|
|
224
|
+
}
|
|
225
|
+
return snapshots;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The desktop pointer, shaped like the three.js adapter's mouse fallback:
|
|
229
|
+
* one source, `handedness: "none"`, `kind: "pointer2d"`.
|
|
230
|
+
*/
|
|
231
|
+
samplePointer() {
|
|
232
|
+
if (this.options.scene.onPointerObservable === undefined)
|
|
233
|
+
return [];
|
|
234
|
+
const ray = this.pointer.ray ?? this.pickRay();
|
|
235
|
+
if (!ray)
|
|
236
|
+
return [];
|
|
237
|
+
const grip = [
|
|
238
|
+
ray.origin[0] + ray.direction[0] * this.gripDistance,
|
|
239
|
+
ray.origin[1] + ray.direction[1] * this.gripDistance,
|
|
240
|
+
ray.origin[2] + ray.direction[2] * this.gripDistance,
|
|
241
|
+
];
|
|
242
|
+
return [
|
|
243
|
+
{
|
|
244
|
+
id: DESKTOP_SOURCE_ID,
|
|
245
|
+
kind: "pointer2d",
|
|
246
|
+
handedness: "none",
|
|
247
|
+
ray: { origin: ray.origin, direction: ray.direction },
|
|
248
|
+
gripPose: { position: grip, quaternion: toQuat(this.camera()?.absoluteRotation) },
|
|
249
|
+
select: this.pointer.down ? 1 : 0,
|
|
250
|
+
// A mouse has one button that means select. Squeeze stays 0 rather
|
|
251
|
+
// than doubling the left button up as both, which would fire a press
|
|
252
|
+
// and a grab together on an interactable that carries both.
|
|
253
|
+
squeeze: 0,
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
/** Pick the scene at the last known cursor position for a ray. */
|
|
258
|
+
pickRay() {
|
|
259
|
+
const picked = this.options.scene.pick?.(this.pointer.x, this.pointer.y);
|
|
260
|
+
const ray = picked?.ray;
|
|
261
|
+
if (!ray)
|
|
262
|
+
return null;
|
|
263
|
+
return {
|
|
264
|
+
origin: toVec3(ray.origin) ?? [0, 0, 0],
|
|
265
|
+
direction: toVec3(ray.direction) ?? defaultForward(this.rightHanded),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
camera() {
|
|
269
|
+
return this.options.camera ?? this.options.scene.activeCamera ?? null;
|
|
270
|
+
}
|
|
271
|
+
getHeadPose() {
|
|
272
|
+
const camera = this.camera();
|
|
273
|
+
return {
|
|
274
|
+
position: toVec3(camera?.globalPosition ?? camera?.position) ?? [0, 0, 0],
|
|
275
|
+
quaternion: toQuat(camera?.absoluteRotation),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// -- haptics ------------------------------------------------------------------
|
|
279
|
+
/**
|
|
280
|
+
* Babylon routes haptics through the motion controller rather than the
|
|
281
|
+
* gamepad actuator, so a source with no motion controller cannot pulse
|
|
282
|
+
* even when the gamepad reports an actuator.
|
|
283
|
+
*/
|
|
284
|
+
pulse(sourceId, intensity, durationMs) {
|
|
285
|
+
const controller = this.sampled.get(sourceId)?.controller ??
|
|
286
|
+
this.controllers().find((c) => c.uniqueId === sourceId);
|
|
287
|
+
const motionController = controller?.motionController;
|
|
288
|
+
if (!motionController?.pulse)
|
|
289
|
+
return false;
|
|
290
|
+
void motionController.pulse(Math.min(1, Math.max(0, intensity)), durationMs);
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
// -- presence -----------------------------------------------------------------
|
|
294
|
+
/**
|
|
295
|
+
* True when Babylon has built at least one visual this provider can hide:
|
|
296
|
+
* a motion controller root mesh, or a hand mesh from the hand-tracking
|
|
297
|
+
* feature. Becomes `capabilities.presence` at
|
|
298
|
+
* `@realitycollective/webxr-input` 0.1.1.
|
|
299
|
+
*/
|
|
300
|
+
get supportsPresence() {
|
|
301
|
+
for (const controller of this.controllers()) {
|
|
302
|
+
if (this.visualsFor(controller).length > 0)
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Show or hide the visuals Babylon built for the targeted sides. Returns
|
|
309
|
+
* false when the target has nothing to show or hide. `"none"` targets no
|
|
310
|
+
* side, so it reports whether presence is usable at all without changing
|
|
311
|
+
* anything.
|
|
312
|
+
*/
|
|
313
|
+
setPresenceVisible(target, visible) {
|
|
314
|
+
if (target === "none")
|
|
315
|
+
return this.supportsPresence;
|
|
316
|
+
let applied = false;
|
|
317
|
+
for (const controller of this.controllers()) {
|
|
318
|
+
const side = normalizeHandedness(controller.inputSource.handedness);
|
|
319
|
+
if (target !== "all" && target !== side)
|
|
320
|
+
continue;
|
|
321
|
+
for (const node of this.visualsFor(controller)) {
|
|
322
|
+
node.setEnabled?.(visible);
|
|
323
|
+
applied = true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return applied;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Always false. Babylon picks the visual per input source - a controller
|
|
330
|
+
* gets its motion-controller model, a tracked hand gets the hand mesh -
|
|
331
|
+
* and exposes no switch between the two. IWSDK builds both families, so
|
|
332
|
+
* its adapter implements this.
|
|
333
|
+
*/
|
|
334
|
+
setPresenceModality(_mode) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
/** Every node that represents this controller on screen. */
|
|
338
|
+
visualsFor(controller) {
|
|
339
|
+
const nodes = [];
|
|
340
|
+
const root = controller.motionController?.rootMesh;
|
|
341
|
+
if (root)
|
|
342
|
+
nodes.push(root);
|
|
343
|
+
const handMesh = this.handFor(controller)?.handMesh;
|
|
344
|
+
if (handMesh)
|
|
345
|
+
nodes.push(handMesh);
|
|
346
|
+
return nodes;
|
|
347
|
+
}
|
|
348
|
+
dispose() {
|
|
349
|
+
for (const off of this.detach.splice(0))
|
|
350
|
+
off();
|
|
351
|
+
this.sampled.clear();
|
|
352
|
+
this.capsListeners.clear();
|
|
353
|
+
this.sourceListeners.clear();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** Analog value where the component reports one, else the pressed flag. */
|
|
357
|
+
function componentValue(component) {
|
|
358
|
+
if (!component)
|
|
359
|
+
return 0;
|
|
360
|
+
if (typeof component.value === "number" && component.value > 0)
|
|
361
|
+
return component.value;
|
|
362
|
+
return component.pressed ? 1 : 0;
|
|
363
|
+
}
|
|
364
|
+
function jointPosition(hand, joint) {
|
|
365
|
+
const mesh = hand?.getJointMesh?.(joint);
|
|
366
|
+
return mesh ? toVec3(mesh.getAbsolutePosition()) : null;
|
|
367
|
+
}
|
|
368
|
+
function normalizeHandedness(value) {
|
|
369
|
+
return value === "left" || value === "right" ? value : "none";
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=provider.js.map
|