@realitycollective/babylon-interactions 0.1.1-preview.2 → 0.1.1
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 -2
- package/README.md +6 -3
- 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 +25 -2
- package/dist/hit-tester.js.map +1 -1
- package/dist/host.d.ts +63 -3
- package/dist/host.js +49 -2
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -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/pointer-visuals.d.ts +105 -0
- package/dist/pointer-visuals.js +149 -0
- package/dist/pointer-visuals.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 +7 -4
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** The unit vector along (x, y, z); +Z for a zero vector. */
|
|
2
|
+
function unit(x, y, z) {
|
|
3
|
+
const length = Math.hypot(x, y, z);
|
|
4
|
+
return length < 1e-6 ? [0, 0, 1] : [x / length, y / length, z / length];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The quaternion turning the axis `from` (+Y or +Z) onto the unit vector `to`.
|
|
8
|
+
* Antiparallel vectors turn half a circle about X, which is square to both axes.
|
|
9
|
+
*/
|
|
10
|
+
function rotationBetween(from, to) {
|
|
11
|
+
const dot = from[0] * to[0] + from[1] * to[1] + from[2] * to[2];
|
|
12
|
+
if (dot < -0.999999)
|
|
13
|
+
return [1, 0, 0, 0];
|
|
14
|
+
const x = from[1] * to[2] - from[2] * to[1];
|
|
15
|
+
const y = from[2] * to[0] - from[0] * to[2];
|
|
16
|
+
const z = from[0] * to[1] - from[1] * to[0];
|
|
17
|
+
const w = 1 + dot;
|
|
18
|
+
const length = Math.hypot(x, y, z, w);
|
|
19
|
+
return [x / length, y / length, z / length, w / length];
|
|
20
|
+
}
|
|
21
|
+
function setColour(target, rgb) {
|
|
22
|
+
target.r = rgb[0];
|
|
23
|
+
target.g = rgb[1];
|
|
24
|
+
target.b = rgb[2];
|
|
25
|
+
}
|
|
26
|
+
const Y_AXIS = [0, 1, 0];
|
|
27
|
+
const Z_AXIS = [0, 0, 1];
|
|
28
|
+
export class BabylonPointerVisuals {
|
|
29
|
+
runtime;
|
|
30
|
+
kit;
|
|
31
|
+
scene;
|
|
32
|
+
meshes = new Map();
|
|
33
|
+
unsubscribe;
|
|
34
|
+
disposed = false;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.runtime = options.runtime;
|
|
37
|
+
this.kit = options.kit;
|
|
38
|
+
this.scene = options.scene;
|
|
39
|
+
this.unsubscribe = this.runtime.onPointerDrawing((drawings) => this.apply(drawings));
|
|
40
|
+
}
|
|
41
|
+
/** The meshes drawn for one source, for a test or an app that restyles them. */
|
|
42
|
+
meshesFor(sourceId) {
|
|
43
|
+
return this.meshes.get(sourceId);
|
|
44
|
+
}
|
|
45
|
+
/** Apply one frame's drawings: create meshes for new sources, hide those of sources gone. */
|
|
46
|
+
apply(drawings) {
|
|
47
|
+
if (this.disposed)
|
|
48
|
+
return;
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const drawing of drawings) {
|
|
51
|
+
seen.add(drawing.sourceId);
|
|
52
|
+
const meshes = this.meshes.get(drawing.sourceId) ?? this.create(drawing.sourceId);
|
|
53
|
+
this.applyRay(meshes.ray, drawing);
|
|
54
|
+
this.applyCursor(meshes.cursor, drawing);
|
|
55
|
+
}
|
|
56
|
+
for (const [id, meshes] of this.meshes) {
|
|
57
|
+
if (!seen.has(id)) {
|
|
58
|
+
meshes.ray.isVisible = false;
|
|
59
|
+
meshes.cursor.isVisible = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
create(sourceId) {
|
|
64
|
+
const ray = this.kit.MeshBuilder.CreateCylinder(`pointer-ray-${sourceId}`, { height: 1, diameter: 1, tessellation: 6 }, this.scene);
|
|
65
|
+
ray.material = this.material(`pointer-ray-${sourceId}-material`);
|
|
66
|
+
ray.isVisible = false;
|
|
67
|
+
const cursor = this.kit.MeshBuilder.CreateDisc(`pointer-cursor-${sourceId}`, { radius: 1, tessellation: 32 }, this.scene);
|
|
68
|
+
cursor.material = this.material(`pointer-cursor-${sourceId}-material`);
|
|
69
|
+
cursor.renderingGroupId = 1;
|
|
70
|
+
cursor.isVisible = false;
|
|
71
|
+
const meshes = { ray, cursor };
|
|
72
|
+
this.meshes.set(sourceId, meshes);
|
|
73
|
+
return meshes;
|
|
74
|
+
}
|
|
75
|
+
material(name) {
|
|
76
|
+
const material = new this.kit.StandardMaterial(name, this.scene);
|
|
77
|
+
material.emissiveColor = new this.kit.Color3(1, 1, 1);
|
|
78
|
+
material.diffuseColor = new this.kit.Color3(1, 1, 1);
|
|
79
|
+
material.disableLighting = true;
|
|
80
|
+
material.alpha = 1;
|
|
81
|
+
return material;
|
|
82
|
+
}
|
|
83
|
+
applyRay(mesh, drawing) {
|
|
84
|
+
const ray = this.runtime.getSource(drawing.sourceId)?.ray;
|
|
85
|
+
const length = drawing.rayTo - drawing.rayFrom;
|
|
86
|
+
if (!drawing.ray || !ray || length <= 0) {
|
|
87
|
+
mesh.isVisible = false;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const dir = unit(ray.direction[0], ray.direction[1], ray.direction[2]);
|
|
91
|
+
const centre = (drawing.rayFrom + drawing.rayTo) / 2;
|
|
92
|
+
mesh.position.x = ray.origin[0] + dir[0] * centre;
|
|
93
|
+
mesh.position.y = ray.origin[1] + dir[1] * centre;
|
|
94
|
+
mesh.position.z = ray.origin[2] + dir[2] * centre;
|
|
95
|
+
mesh.scaling.x = drawing.rayRadius * 2;
|
|
96
|
+
mesh.scaling.y = length;
|
|
97
|
+
mesh.scaling.z = drawing.rayRadius * 2;
|
|
98
|
+
const q = rotationBetween(Y_AXIS, dir);
|
|
99
|
+
mesh.rotationQuaternion = new this.kit.Quaternion(q[0], q[1], q[2], q[3]);
|
|
100
|
+
const material = mesh.material;
|
|
101
|
+
setColour(material.emissiveColor, drawing.rayColor);
|
|
102
|
+
setColour(material.diffuseColor, drawing.rayColor);
|
|
103
|
+
material.alpha = 1;
|
|
104
|
+
mesh.isVisible = true;
|
|
105
|
+
}
|
|
106
|
+
applyCursor(mesh, drawing) {
|
|
107
|
+
if (!drawing.cursor || !drawing.cursorPoint) {
|
|
108
|
+
mesh.isVisible = false;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const point = drawing.cursorPoint;
|
|
112
|
+
const source = this.runtime.getSource(drawing.sourceId);
|
|
113
|
+
// Pointing direction: along the ray for a ray hit; from the head for a near hit.
|
|
114
|
+
let dir;
|
|
115
|
+
if (source?.ray && drawing.ray) {
|
|
116
|
+
dir = unit(source.ray.direction[0], source.ray.direction[1], source.ray.direction[2]);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const head = this.runtime.getProvider().getHeadPose?.()?.position;
|
|
120
|
+
dir = head ? unit(point[0] - head[0], point[1] - head[1], point[2] - head[2]) : Z_AXIS;
|
|
121
|
+
}
|
|
122
|
+
// The disc's front face looks down -Z, so turning +Z onto the pointing
|
|
123
|
+
// direction makes it face back at the viewer. It sits `cursorOffset` toward the viewer.
|
|
124
|
+
const q = rotationBetween(Z_AXIS, dir);
|
|
125
|
+
mesh.rotationQuaternion = new this.kit.Quaternion(q[0], q[1], q[2], q[3]);
|
|
126
|
+
mesh.position.x = point[0] - dir[0] * drawing.cursorOffset;
|
|
127
|
+
mesh.position.y = point[1] - dir[1] * drawing.cursorOffset;
|
|
128
|
+
mesh.position.z = point[2] - dir[2] * drawing.cursorOffset;
|
|
129
|
+
mesh.scaling.x = drawing.cursorRadius;
|
|
130
|
+
mesh.scaling.y = drawing.cursorRadius;
|
|
131
|
+
mesh.scaling.z = drawing.cursorRadius;
|
|
132
|
+
mesh.material.alpha = drawing.cursorOpacity;
|
|
133
|
+
mesh.isVisible = true;
|
|
134
|
+
}
|
|
135
|
+
dispose() {
|
|
136
|
+
if (this.disposed)
|
|
137
|
+
return;
|
|
138
|
+
this.disposed = true;
|
|
139
|
+
this.unsubscribe();
|
|
140
|
+
for (const { ray, cursor } of this.meshes.values()) {
|
|
141
|
+
for (const mesh of [ray, cursor]) {
|
|
142
|
+
mesh.material.dispose();
|
|
143
|
+
mesh.dispose();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.meshes.clear();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=pointer-visuals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer-visuals.js","sourceRoot":"","sources":["../src/pointer-visuals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonPointerVisuals - draws the ray stub and the cursor disc for every\n * source, exactly as the core decided them (`InteractionRuntime.onPointerDrawing`:\n * the pointer arbiter's decision under the app's pointer display settings).\n *\n * This is IWSDK 1.0.0's look restated for Babylon (`@iwsdk/xr-input`\n * `ray-pointer.js` and `cursor-visual.js`), so a Babylon app shows what a\n * Quest shows on the web:\n *\n * - the ray is a thin cylinder along the source's ray, spanning `rayFrom` to\n * `rayTo` metres from its origin, so with the defaults it is the 1 mm white\n * stub from 3 cm in front of the hand to `min(0.3 m, the hit)`, blue while\n * selecting;\n * - the cursor is a disc at the hit, facing the viewer along the pointing\n * direction (the ray's, or the line from the head for a near hit), nudged\n * `cursorOffset` off the surface, white, with opacity and radius from the\n * drawing.\n *\n * The renderer decides nothing: it reads each frame's `PointerDrawing` and\n * the source's ray from the runtime and writes them onto its meshes. Meshes\n * are created per source on first sight and disposed with the renderer.\n * Positions and directions arrive in world space as the provider gives them,\n * so nothing is flipped for Babylon's left-handed default. Like the rest of\n * this package it never imports `@babylonjs/core`: the app hands over the\n * constructors it uses as a {@link BabylonPointerVisualsKit}.\n */\nimport type { InteractionRuntime, PointerDrawing, Unsubscribe } from \"@realitycollective/webxr-interactions\";\nimport type { BabylonQuaternionLike, BabylonVector3Like } from \"./babylon-types.js\";\n\n/** Structural slice of Babylon's `Color3`. */\nexport interface BabylonColor3Like {\n r: number;\n g: number;\n b: number;\n}\n\n/** Structural slice of the `StandardMaterial` members the renderer writes. */\nexport interface BabylonPointerMaterialLike {\n emissiveColor: BabylonColor3Like;\n diffuseColor: BabylonColor3Like;\n /** 0 transparent to 1 opaque. */\n alpha: number;\n /** Unlit so the colour is the colour on screen. */\n disableLighting?: boolean;\n dispose(): void;\n}\n\n/** Structural slice of the `Mesh` members the renderer writes. */\nexport interface BabylonPointerMeshLike {\n position: BabylonVector3Like;\n rotationQuaternion: BabylonQuaternionLike | null;\n scaling: BabylonVector3Like;\n isVisible: boolean;\n material: BabylonPointerMaterialLike | null;\n /** Higher groups draw later; the cursor uses 1 so it draws over the scene. */\n renderingGroupId?: number;\n dispose(): void;\n}\n\n/** What the app imports from `@babylonjs/core` and hands to the renderer. */\nexport interface BabylonPointerVisualsKit {\n MeshBuilder: {\n /** A cylinder along the Y axis, centred on the origin. */\n CreateCylinder(\n name: string,\n options: { height: number; diameter: number; tessellation?: number },\n scene: unknown,\n ): BabylonPointerMeshLike;\n /** A disc in the XY plane whose front face looks down -Z. */\n CreateDisc(name: string, options: { radius: number; tessellation?: number }, scene: unknown): BabylonPointerMeshLike;\n };\n StandardMaterial: new (name: string, scene: unknown) => BabylonPointerMaterialLike;\n Color3: new (r: number, g: number, b: number) => BabylonColor3Like;\n Quaternion: new (x: number, y: number, z: number, w: number) => BabylonQuaternionLike;\n}\n\nexport interface BabylonPointerVisualsOptions {\n /** The runtime whose drawings are applied. */\n runtime: InteractionRuntime;\n /** The Babylon constructors the renderer builds its meshes from. */\n kit: BabylonPointerVisualsKit;\n /** The scene the meshes are created in (handed to the kit as is). */\n scene: unknown;\n}\n\ninterface SourceMeshes {\n ray: BabylonPointerMeshLike;\n cursor: BabylonPointerMeshLike;\n}\n\ntype Vec3 = readonly [number, number, number];\n\n/** The unit vector along (x, y, z); +Z for a zero vector. */\nfunction unit(x: number, y: number, z: number): Vec3 {\n const length = Math.hypot(x, y, z);\n return length < 1e-6 ? [0, 0, 1] : [x / length, y / length, z / length];\n}\n\n/**\n * The quaternion turning the axis `from` (+Y or +Z) onto the unit vector `to`.\n * Antiparallel vectors turn half a circle about X, which is square to both axes.\n */\nfunction rotationBetween(from: Vec3, to: Vec3): [number, number, number, number] {\n const dot = from[0] * to[0] + from[1] * to[1] + from[2] * to[2];\n if (dot < -0.999999) return [1, 0, 0, 0];\n const x = from[1] * to[2] - from[2] * to[1];\n const y = from[2] * to[0] - from[0] * to[2];\n const z = from[0] * to[1] - from[1] * to[0];\n const w = 1 + dot;\n const length = Math.hypot(x, y, z, w);\n return [x / length, y / length, z / length, w / length];\n}\n\nfunction setColour(target: BabylonColor3Like, rgb: readonly [number, number, number]): void {\n target.r = rgb[0];\n target.g = rgb[1];\n target.b = rgb[2];\n}\n\nconst Y_AXIS: Vec3 = [0, 1, 0];\nconst Z_AXIS: Vec3 = [0, 0, 1];\n\nexport class BabylonPointerVisuals {\n private readonly runtime: InteractionRuntime;\n private readonly kit: BabylonPointerVisualsKit;\n private readonly scene: unknown;\n private readonly meshes = new Map<string, SourceMeshes>();\n private readonly unsubscribe: Unsubscribe;\n private disposed = false;\n\n constructor(options: BabylonPointerVisualsOptions) {\n this.runtime = options.runtime;\n this.kit = options.kit;\n this.scene = options.scene;\n this.unsubscribe = this.runtime.onPointerDrawing((drawings) => this.apply(drawings));\n }\n\n /** The meshes drawn for one source, for a test or an app that restyles them. */\n meshesFor(sourceId: string): SourceMeshes | undefined {\n return this.meshes.get(sourceId);\n }\n\n /** Apply one frame's drawings: create meshes for new sources, hide those of sources gone. */\n apply(drawings: readonly PointerDrawing[]): void {\n if (this.disposed) return;\n const seen = new Set<string>();\n for (const drawing of drawings) {\n seen.add(drawing.sourceId);\n const meshes = this.meshes.get(drawing.sourceId) ?? this.create(drawing.sourceId);\n this.applyRay(meshes.ray, drawing);\n this.applyCursor(meshes.cursor, drawing);\n }\n for (const [id, meshes] of this.meshes) {\n if (!seen.has(id)) {\n meshes.ray.isVisible = false;\n meshes.cursor.isVisible = false;\n }\n }\n }\n\n private create(sourceId: string): SourceMeshes {\n const ray = this.kit.MeshBuilder.CreateCylinder(\n `pointer-ray-${sourceId}`,\n { height: 1, diameter: 1, tessellation: 6 },\n this.scene,\n );\n ray.material = this.material(`pointer-ray-${sourceId}-material`);\n ray.isVisible = false;\n const cursor = this.kit.MeshBuilder.CreateDisc(`pointer-cursor-${sourceId}`, { radius: 1, tessellation: 32 }, this.scene);\n cursor.material = this.material(`pointer-cursor-${sourceId}-material`);\n cursor.renderingGroupId = 1;\n cursor.isVisible = false;\n const meshes = { ray, cursor };\n this.meshes.set(sourceId, meshes);\n return meshes;\n }\n\n private material(name: string): BabylonPointerMaterialLike {\n const material = new this.kit.StandardMaterial(name, this.scene);\n material.emissiveColor = new this.kit.Color3(1, 1, 1);\n material.diffuseColor = new this.kit.Color3(1, 1, 1);\n material.disableLighting = true;\n material.alpha = 1;\n return material;\n }\n\n private applyRay(mesh: BabylonPointerMeshLike, drawing: PointerDrawing): void {\n const ray = this.runtime.getSource(drawing.sourceId)?.ray;\n const length = drawing.rayTo - drawing.rayFrom;\n if (!drawing.ray || !ray || length <= 0) {\n mesh.isVisible = false;\n return;\n }\n const dir = unit(ray.direction[0], ray.direction[1], ray.direction[2]);\n const centre = (drawing.rayFrom + drawing.rayTo) / 2;\n mesh.position.x = ray.origin[0] + dir[0] * centre;\n mesh.position.y = ray.origin[1] + dir[1] * centre;\n mesh.position.z = ray.origin[2] + dir[2] * centre;\n mesh.scaling.x = drawing.rayRadius * 2;\n mesh.scaling.y = length;\n mesh.scaling.z = drawing.rayRadius * 2;\n const q = rotationBetween(Y_AXIS, dir);\n mesh.rotationQuaternion = new this.kit.Quaternion(q[0], q[1], q[2], q[3]);\n const material = mesh.material!;\n setColour(material.emissiveColor, drawing.rayColor);\n setColour(material.diffuseColor, drawing.rayColor);\n material.alpha = 1;\n mesh.isVisible = true;\n }\n\n private applyCursor(mesh: BabylonPointerMeshLike, drawing: PointerDrawing): void {\n if (!drawing.cursor || !drawing.cursorPoint) {\n mesh.isVisible = false;\n return;\n }\n const point = drawing.cursorPoint;\n const source = this.runtime.getSource(drawing.sourceId);\n // Pointing direction: along the ray for a ray hit; from the head for a near hit.\n let dir: Vec3;\n if (source?.ray && drawing.ray) {\n dir = unit(source.ray.direction[0], source.ray.direction[1], source.ray.direction[2]);\n } else {\n const head = this.runtime.getProvider().getHeadPose?.()?.position;\n dir = head ? unit(point[0] - head[0], point[1] - head[1], point[2] - head[2]) : Z_AXIS;\n }\n // The disc's front face looks down -Z, so turning +Z onto the pointing\n // direction makes it face back at the viewer. It sits `cursorOffset` toward the viewer.\n const q = rotationBetween(Z_AXIS, dir);\n mesh.rotationQuaternion = new this.kit.Quaternion(q[0], q[1], q[2], q[3]);\n mesh.position.x = point[0] - dir[0] * drawing.cursorOffset;\n mesh.position.y = point[1] - dir[1] * drawing.cursorOffset;\n mesh.position.z = point[2] - dir[2] * drawing.cursorOffset;\n mesh.scaling.x = drawing.cursorRadius;\n mesh.scaling.y = drawing.cursorRadius;\n mesh.scaling.z = drawing.cursorRadius;\n mesh.material!.alpha = drawing.cursorOpacity;\n mesh.isVisible = true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.unsubscribe();\n for (const { ray, cursor } of this.meshes.values()) {\n for (const mesh of [ray, cursor]) {\n mesh.material!.dispose();\n mesh.dispose();\n }\n }\n this.meshes.clear();\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"]}
|
package/dist/transform-port.d.ts
CHANGED
|
@@ -9,9 +9,19 @@
|
|
|
9
9
|
* world matrix needs recomputing, so in-place writes are seen; assigning a
|
|
10
10
|
* plain object in their place would break Babylon, which calls methods on
|
|
11
11
|
* them.
|
|
12
|
+
*
|
|
13
|
+
* A node's held pose comes from one of two independent mechanisms. With
|
|
14
|
+
* `options.physics` (a platform `PhysicsFacility` and the node's body id -
|
|
15
|
+
* see `BabylonPhysicsFacility`), the port drives the core's held-pose rule
|
|
16
|
+
* through it: `beginHold`/`endHold` suspend and resume the body there, and
|
|
17
|
+
* every write that moves the node (`setWorldPose`, `setLocalOffset`,
|
|
18
|
+
* `setLocalRotation`) keeps the body's own pose in step. Without it, a node
|
|
19
|
+
* with a bare `node.physicsBody` and `options.physicsMotionTypes` keeps the
|
|
20
|
+
* older, narrower hold this port has always had - see that option's own
|
|
21
|
+
* comment. A construction never needs both.
|
|
12
22
|
*/
|
|
13
23
|
import type { PoseTuple, QuatTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
14
|
-
import { type HoldRelease, type TransformPort } from "@realitycollective/webxr-interactions";
|
|
24
|
+
import { type HoldRelease, type PhysicsFacility, type TransformPort } from "@realitycollective/webxr-interactions";
|
|
15
25
|
import { type BabylonQuaternionLike, type BabylonTransformNodeLike } from "./babylon-types.js";
|
|
16
26
|
export interface BabylonTransformPortOptions {
|
|
17
27
|
/**
|
|
@@ -36,12 +46,27 @@ export interface BabylonTransformPortOptions {
|
|
|
36
46
|
* `beginHold`/`endHold` exist on the port only when BOTH this option and
|
|
37
47
|
* `node.physicsBody` are present at construction - a node with no
|
|
38
48
|
* physics body, or a construction with no motion types, gets neither
|
|
39
|
-
* member and behaves exactly as before.
|
|
49
|
+
* member and behaves exactly as before. Ignored when `physics` is given.
|
|
40
50
|
*/
|
|
41
51
|
physicsMotionTypes?: {
|
|
42
52
|
animated: unknown;
|
|
43
53
|
dynamic: unknown;
|
|
44
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Drive the held-pose rule through the platform's physics facility
|
|
57
|
+
* instead of `node.physicsBody` directly - the body id is the same
|
|
58
|
+
* string id the node was registered under. Present, `beginHold`/`endHold`
|
|
59
|
+
* suspend and resume the body there, and `setWorldPose`, `setLocalOffset`
|
|
60
|
+
* and `setLocalRotation` all keep the body's pose in step with the node's.
|
|
61
|
+
* Takes precedence over `physicsMotionTypes` when both are given.
|
|
62
|
+
*/
|
|
63
|
+
physics?: BabylonTransformPortPhysics;
|
|
64
|
+
}
|
|
65
|
+
/** The body a port drives through the platform's `PhysicsFacility`. */
|
|
66
|
+
export interface BabylonTransformPortPhysics {
|
|
67
|
+
facility: PhysicsFacility;
|
|
68
|
+
/** The body's id in the facility - the interactable's own id. */
|
|
69
|
+
bodyId: string;
|
|
45
70
|
}
|
|
46
71
|
export declare class BabylonTransformPort implements TransformPort {
|
|
47
72
|
private readonly node;
|
|
@@ -54,6 +79,7 @@ export declare class BabylonTransformPort implements TransformPort {
|
|
|
54
79
|
private baseEmissiveColor;
|
|
55
80
|
/** PBRMaterial's `emissiveIntensity` before any pulse, captured on first use - StandardMaterial has none. */
|
|
56
81
|
private baseEmissiveIntensity;
|
|
82
|
+
private readonly physics;
|
|
57
83
|
readonly beginHold?: () => void;
|
|
58
84
|
readonly endHold?: (release: HoldRelease) => void;
|
|
59
85
|
constructor(node: BabylonTransformNodeLike, options?: BabylonTransformPortOptions);
|
|
@@ -70,6 +96,8 @@ export declare class BabylonTransformPort implements TransformPort {
|
|
|
70
96
|
getLocalOffset(): Vec3Tuple;
|
|
71
97
|
setLocalOffset(offset: Vec3Tuple): void;
|
|
72
98
|
setLocalRotation(quaternion: QuatTuple): void;
|
|
99
|
+
/** A behaviour's write to the node reaches its facility-driven body too, as a placement (velocity cleared). */
|
|
100
|
+
private syncBody;
|
|
73
101
|
/**
|
|
74
102
|
* Follow a world pose while grabbed, or place the node directly the rest
|
|
75
103
|
* of the time (reset / teleport) - see `TransformPort.setWorldPose`'s own
|
package/dist/transform-port.js
CHANGED
|
@@ -11,12 +11,20 @@ export class BabylonTransformPort {
|
|
|
11
11
|
baseEmissiveColor = null;
|
|
12
12
|
/** PBRMaterial's `emissiveIntensity` before any pulse, captured on first use - StandardMaterial has none. */
|
|
13
13
|
baseEmissiveIntensity = null;
|
|
14
|
+
physics;
|
|
14
15
|
beginHold;
|
|
15
16
|
endHold;
|
|
16
17
|
constructor(node, options = {}) {
|
|
17
18
|
this.node = node;
|
|
18
19
|
this.createQuaternion = options.createQuaternion ?? (() => ({ x: 0, y: 0, z: 0, w: 1 }));
|
|
19
20
|
this.recaptureRest();
|
|
21
|
+
this.physics = options.physics;
|
|
22
|
+
if (this.physics) {
|
|
23
|
+
const { facility, bodyId } = this.physics;
|
|
24
|
+
this.beginHold = () => facility.suspend(bodyId);
|
|
25
|
+
this.endHold = (release) => facility.resume(bodyId, release);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
20
28
|
const body = node.physicsBody;
|
|
21
29
|
const motionTypes = options.physicsMotionTypes;
|
|
22
30
|
if (body && motionTypes) {
|
|
@@ -71,9 +79,17 @@ export class BabylonTransformPort {
|
|
|
71
79
|
}
|
|
72
80
|
setLocalOffset(offset) {
|
|
73
81
|
writeVec3(this.node.position, vAdd(this.restPosition, vApplyQuat(offset, this.restQuaternion)));
|
|
82
|
+
this.syncBody();
|
|
74
83
|
}
|
|
75
84
|
setLocalRotation(quaternion) {
|
|
76
85
|
this.writeRotation(quatMultiply(this.restQuaternion, quaternion));
|
|
86
|
+
this.syncBody();
|
|
87
|
+
}
|
|
88
|
+
/** A behaviour's write to the node reaches its facility-driven body too, as a placement (velocity cleared). */
|
|
89
|
+
syncBody() {
|
|
90
|
+
if (!this.physics)
|
|
91
|
+
return;
|
|
92
|
+
this.physics.facility.setBodyPose(this.physics.bodyId, this.getWorldPose());
|
|
77
93
|
}
|
|
78
94
|
/**
|
|
79
95
|
* Follow a world pose while grabbed, or place the node directly the rest
|
|
@@ -87,6 +103,13 @@ export class BabylonTransformPort {
|
|
|
87
103
|
* sit under an unscaled parent, which is how the demos build them.
|
|
88
104
|
*/
|
|
89
105
|
setWorldPose(pose) {
|
|
106
|
+
// With a facility-driven body, the facility owns its pose and velocity
|
|
107
|
+
// and also places the node itself; the write below runs regardless, so
|
|
108
|
+
// the node never lags the body even if the facility placed it slightly
|
|
109
|
+
// differently (a scaled parent, for one - see BabylonPhysicsFacility's
|
|
110
|
+
// own resolution).
|
|
111
|
+
if (this.physics)
|
|
112
|
+
this.physics.facility.setBodyPose(this.physics.bodyId, pose);
|
|
90
113
|
const parent = parentOf(this.node);
|
|
91
114
|
if (!parent) {
|
|
92
115
|
writeVec3(this.node.position, pose.position);
|