@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/dist/host.js CHANGED
@@ -12,7 +12,10 @@
12
12
  * Without it, call `update(dt)` yourself with seconds.
13
13
  */
14
14
  import { InteractionRuntime, } from "@realitycollective/webxr-interactions";
15
+ import { nodeWorldPose } from "./babylon-types.js";
15
16
  import { BabylonHitTester, } from "./hit-tester.js";
17
+ import { BabylonPointerVisuals } from "./pointer-visuals.js";
18
+ import { BabylonPhysicsFacility } from "./physics-facility.js";
16
19
  import { BabylonTransformPort } from "./transform-port.js";
17
20
  import { BabylonInputProvider } from "./provider.js";
18
21
  /** Longest frame the scene-attached loop will report, in seconds. */
@@ -21,7 +24,13 @@ export class BabylonInteractions {
21
24
  runtime;
22
25
  provider;
23
26
  hitTester;
27
+ /** The platform's physics, when the setup was given one. */
28
+ physics;
29
+ /** The ray and cursor renderer, when the setup was given `pointerVisuals`. */
30
+ pointerVisuals;
31
+ stepPhysics;
24
32
  ports = new Map();
33
+ nodes = new Map();
25
34
  detachScene = null;
26
35
  constructor(options) {
27
36
  this.provider = new BabylonInputProvider(options);
@@ -30,7 +39,18 @@ export class BabylonInteractions {
30
39
  provider: this.provider,
31
40
  hitTester: this.hitTester,
32
41
  ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),
42
+ ...(options.pointers ? { pointers: options.pointers } : {}),
43
+ ...(options.pointerDisplay ? { pointerDisplay: options.pointerDisplay } : {}),
33
44
  });
45
+ this.pointerVisuals = options.pointerVisuals
46
+ ? new BabylonPointerVisuals({ runtime: this.runtime, kit: options.pointerVisuals.kit, scene: options.scene })
47
+ : null;
48
+ this.physics = options.physics
49
+ ? "kit" in options.physics
50
+ ? new BabylonPhysicsFacility(options.scene, options.physics.kit, { nodeFor: (id) => this.nodes.get(id) })
51
+ : options.physics
52
+ : null;
53
+ this.stepPhysics = options.stepPhysics ?? true;
34
54
  if (options.attachToScene)
35
55
  this.attachToScene(options);
36
56
  }
@@ -38,9 +58,28 @@ export class BabylonInteractions {
38
58
  setPickWithRay(pick) {
39
59
  this.hitTester.setPickWithRay(pick);
40
60
  }
41
- /** Register an interactable with its Babylon node. */
61
+ /**
62
+ * Register an interactable with its Babylon node. With `body` or `shape`
63
+ * the node gets a physics body in the facility at its current world pose,
64
+ * and its port drives the held-pose rule through it.
65
+ */
42
66
  register(descriptor, node, options = {}) {
43
- const port = new BabylonTransformPort(node, options.createQuaternion ? { createQuaternion: options.createQuaternion } : {});
67
+ this.nodes.set(descriptor.id, node);
68
+ let port;
69
+ if (options.body || options.shape) {
70
+ if (!this.physics) {
71
+ throw new Error(`[babylon-interactions] "${descriptor.id}" asks for a physics body but the setup has no physics; pass { kit } (the default engine) or a PhysicsFacility to createBabylonInteractions`);
72
+ }
73
+ node.computeWorldMatrix?.(true);
74
+ this.physics.addBody(descriptor.id, nodeWorldPose(node), options.body, options.shape);
75
+ port = new BabylonTransformPort(node, {
76
+ ...(options.createQuaternion ? { createQuaternion: options.createQuaternion } : {}),
77
+ physics: { facility: this.physics, bodyId: descriptor.id },
78
+ });
79
+ }
80
+ else {
81
+ port = new BabylonTransformPort(node, options.createQuaternion ? { createQuaternion: options.createQuaternion } : {});
82
+ }
44
83
  this.ports.set(descriptor.id, port);
45
84
  this.hitTester.register(descriptor.id, node, options.targetRadius);
46
85
  this.runtime.registerInteractable(descriptor, { transform: port });
@@ -50,18 +89,26 @@ export class BabylonInteractions {
50
89
  this.runtime.unregisterInteractable(id);
51
90
  this.hitTester.unregister(id);
52
91
  this.ports.delete(id);
92
+ this.nodes.delete(id);
93
+ this.physics?.removeBody(id);
53
94
  }
54
95
  getPort(id) {
55
96
  return this.ports.get(id);
56
97
  }
57
98
  update(dt) {
99
+ // The provider's eye-gaze filter and grace integrate over the same step.
100
+ this.provider.setFrameDelta(dt);
58
101
  this.runtime.update(dt);
102
+ if (this.stepPhysics)
103
+ this.physics?.step(dt);
59
104
  }
60
105
  dispose() {
61
106
  this.detachScene?.();
62
107
  this.detachScene = null;
108
+ this.pointerVisuals?.dispose();
63
109
  this.runtime.dispose();
64
110
  this.provider.dispose();
111
+ this.physics?.dispose();
65
112
  }
66
113
  attachToScene(options) {
67
114
  const observable = options.scene.onBeforeRenderObservable;
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * createBabylonInteractions - one-call setup for the Babylon adapter.\n *\n * ```ts\n * const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] }, buttonMesh);\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * With `attachToScene` the render loop drives itself from\n * `scene.onBeforeRenderObservable` and the engine's own frame delta.\n * Without it, call `update(dt)` yourself with seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n} from \"@realitycollective/webxr-interactions\";\nimport type { BabylonTransformNodeLike } from \"./babylon-types.js\";\nimport {\n BabylonHitTester,\n type BabylonHitTesterOptions,\n type BabylonPickWithRay,\n} from \"./hit-tester.js\";\nimport { BabylonTransformPort, type BabylonTransformPortOptions } from \"./transform-port.js\";\nimport { BabylonInputProvider, type BabylonProviderOptions } from \"./provider.js\";\n\nexport interface BabylonInteractionsOptions\n extends BabylonProviderOptions,\n BabylonHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /**\n * Drive `update(dt)` from `scene.onBeforeRenderObservable`, using the\n * engine's frame delta in seconds. Default false - the app calls\n * `update` from its own loop.\n */\n attachToScene?: boolean;\n}\n\nexport interface BabylonRegisterOptions extends BabylonTransformPortOptions {\n /** Targeting radius for the sphere hit-tester. Default 0.1 m. */\n targetRadius?: number;\n}\n\n/** Longest frame the scene-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class BabylonInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: BabylonInputProvider;\n readonly hitTester: BabylonHitTester;\n private readonly ports = new Map<string, BabylonTransformPort>();\n private detachScene: (() => void) | null = null;\n\n constructor(options: BabylonInteractionsOptions) {\n this.provider = new BabylonInputProvider(options);\n this.hitTester = new BabylonHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n });\n if (options.attachToScene) this.attachToScene(options);\n }\n\n /** Supply or replace the app's mesh-accurate pick. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.hitTester.setPickWithRay(pick);\n }\n\n /** Register an interactable with its Babylon node. */\n register(\n descriptor: InteractableDescriptor,\n node: BabylonTransformNodeLike,\n options: BabylonRegisterOptions = {},\n ): BabylonTransformPort {\n const port = new BabylonTransformPort(\n node,\n options.createQuaternion ? { createQuaternion: options.createQuaternion } : {},\n );\n this.ports.set(descriptor.id, port);\n this.hitTester.register(descriptor.id, node, options.targetRadius);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.hitTester.unregister(id);\n this.ports.delete(id);\n }\n\n getPort(id: string): BabylonTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n this.runtime.update(dt);\n }\n\n dispose(): void {\n this.detachScene?.();\n this.detachScene = null;\n this.runtime.dispose();\n this.provider.dispose();\n }\n\n private attachToScene(options: BabylonInteractionsOptions): void {\n const observable = options.scene.onBeforeRenderObservable;\n if (!observable) return;\n const engine = options.scene.getEngine?.();\n const observer = observable.add(() => {\n // Babylon reports the frame delta in milliseconds. A long frame - a tab\n // that was backgrounded, a slow first frame - is clamped so behaviours\n // integrate over a sane step rather than jumping.\n const ms = engine?.getDeltaTime() ?? 0;\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, ms / 1000)));\n });\n this.detachScene = () => {\n observable.remove(observer);\n };\n }\n}\n\nexport function createBabylonInteractions(\n options: BabylonInteractionsOptions,\n): BabylonInteractions {\n return new BabylonInteractions(options);\n}\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * createBabylonInteractions - one-call setup for the Babylon adapter.\n *\n * ```ts\n * const interactions = createBabylonInteractions({ scene, xr, attachToScene: true });\n * interactions.register({ id: \"button\", behaviours: [{ kind: \"press\" }] }, buttonMesh);\n * interactions.runtime.onEvent((event) => console.log(event.type));\n * ```\n *\n * With `attachToScene` the render loop drives itself from\n * `scene.onBeforeRenderObservable` and the engine's own frame delta.\n * Without it, call `update(dt)` yourself with seconds.\n */\nimport {\n InteractionRuntime,\n type DwellConfig,\n type InteractableDescriptor,\n type PhysicsBodySpec,\n type PhysicsFacility,\n type PhysicsShapeSpec,\n type PointerArbiter,\n type PointerDisplay,\n type PointerDisplayConfig,\n} from \"@realitycollective/webxr-interactions\";\nimport { nodeWorldPose, type BabylonPhysicsKitLike, type BabylonTransformNodeLike } from \"./babylon-types.js\";\nimport {\n BabylonHitTester,\n type BabylonHitTesterOptions,\n type BabylonPickWithRay,\n} from \"./hit-tester.js\";\nimport { BabylonPointerVisuals, type BabylonPointerVisualsKit } from \"./pointer-visuals.js\";\nimport { BabylonPhysicsFacility } from \"./physics-facility.js\";\nimport { BabylonTransformPort, type BabylonTransformPortOptions } from \"./transform-port.js\";\nimport { BabylonInputProvider, type BabylonProviderOptions } from \"./provider.js\";\n\nexport interface BabylonInteractionsOptions\n extends BabylonProviderOptions,\n BabylonHitTesterOptions {\n dwellDefaults?: DwellConfig;\n /**\n * Drive `update(dt)` from `scene.onBeforeRenderObservable`, using the\n * engine's frame delta in seconds. Default false - the app calls\n * `update` from its own loop.\n */\n attachToScene?: boolean;\n /**\n * The pointer arbiter shared with the UI Extensions window host\n * (`connectUIExtensions({ pointers })`), so one decision per source covers\n * panels and interactables, as IWSDK's `MultiPointer` does. Omit when the\n * app has no panels.\n */\n pointers?: PointerArbiter;\n /**\n * The app's pointer display settings: ray never, always or while hitting;\n * cursors on objects and on panels; the ray and cursor look. Defaults are\n * IWSDK 1.0.0's. Change them at run time through\n * `runtime.getPointerDisplay().set(...)`.\n */\n pointerDisplay?: PointerDisplay | Partial<PointerDisplayConfig>;\n /**\n * Draw the ray stub and the cursor disc for every source, IWSDK's look,\n * into `options.scene`, exactly as the core decides them each frame. Pass\n * `{ kit }`, the Babylon constructors the renderer builds its meshes from.\n * Omit to draw nothing (an app that draws its own reads\n * `runtime.onPointerDrawing`).\n */\n pointerVisuals?: { kit: BabylonPointerVisualsKit };\n /**\n * The platform's physics. Havok (Physics V2) is the named default engine\n * for Babylon: pass `{ kit }`, the Havok-era values from\n * `BabylonPhysicsKitLike`, and the binding builds a `BabylonPhysicsFacility`\n * over `options.scene`. Pass any `PhysicsFacility` instead to replace the\n * default engine with your own. Omit it and no node can carry a body:\n * `register` with a `body` or `shape` then throws.\n */\n physics?: PhysicsFacility | { kit: BabylonPhysicsKitLike };\n /**\n * Step the facility from `update(dt)`. Default true. Pass false when the\n * app steps its physics world itself - or lets Babylon's own render loop\n * do it, since Babylon normally steps physics from `scene.render()`.\n */\n stepPhysics?: boolean;\n}\n\nexport interface BabylonRegisterOptions extends BabylonTransformPortOptions {\n /** Targeting radius for the sphere hit-tester. Default 0.1 m. */\n targetRadius?: number;\n /**\n * Give the node a physics body with these settings (defaults are the\n * core's: dynamic, no damping, gravity factor 1). Needs `physics` on the\n * setup. With a body, the port applies the held-pose rule through it.\n */\n body?: PhysicsBodySpec;\n /** The body's collider (default `\"auto\"`: the node's own geometry). Implies `body`. */\n shape?: PhysicsShapeSpec;\n}\n\n/** Longest frame the scene-attached loop will report, in seconds. */\nconst MAX_FRAME_SECONDS = 0.1;\n\nexport class BabylonInteractions {\n readonly runtime: InteractionRuntime;\n readonly provider: BabylonInputProvider;\n readonly hitTester: BabylonHitTester;\n /** The platform's physics, when the setup was given one. */\n readonly physics: PhysicsFacility | null;\n /** The ray and cursor renderer, when the setup was given `pointerVisuals`. */\n readonly pointerVisuals: BabylonPointerVisuals | null;\n private readonly stepPhysics: boolean;\n private readonly ports = new Map<string, BabylonTransformPort>();\n private readonly nodes = new Map<string, BabylonTransformNodeLike>();\n private detachScene: (() => void) | null = null;\n\n constructor(options: BabylonInteractionsOptions) {\n this.provider = new BabylonInputProvider(options);\n this.hitTester = new BabylonHitTester(options);\n this.runtime = new InteractionRuntime({\n provider: this.provider,\n hitTester: this.hitTester,\n ...(options.dwellDefaults ? { dwellDefaults: options.dwellDefaults } : {}),\n ...(options.pointers ? { pointers: options.pointers } : {}),\n ...(options.pointerDisplay ? { pointerDisplay: options.pointerDisplay } : {}),\n });\n this.pointerVisuals = options.pointerVisuals\n ? new BabylonPointerVisuals({ runtime: this.runtime, kit: options.pointerVisuals.kit, scene: options.scene })\n : null;\n this.physics = options.physics\n ? \"kit\" in options.physics\n ? new BabylonPhysicsFacility(options.scene, options.physics.kit, { nodeFor: (id) => this.nodes.get(id) })\n : options.physics\n : null;\n this.stepPhysics = options.stepPhysics ?? true;\n if (options.attachToScene) this.attachToScene(options);\n }\n\n /** Supply or replace the app's mesh-accurate pick. */\n setPickWithRay(pick: BabylonPickWithRay | null): void {\n this.hitTester.setPickWithRay(pick);\n }\n\n /**\n * Register an interactable with its Babylon node. With `body` or `shape`\n * the node gets a physics body in the facility at its current world pose,\n * and its port drives the held-pose rule through it.\n */\n register(\n descriptor: InteractableDescriptor,\n node: BabylonTransformNodeLike,\n options: BabylonRegisterOptions = {},\n ): BabylonTransformPort {\n this.nodes.set(descriptor.id, node);\n let port: BabylonTransformPort;\n if (options.body || options.shape) {\n if (!this.physics) {\n throw new Error(\n `[babylon-interactions] \"${descriptor.id}\" asks for a physics body but the setup has no physics; pass { kit } (the default engine) or a PhysicsFacility to createBabylonInteractions`,\n );\n }\n node.computeWorldMatrix?.(true);\n this.physics.addBody(descriptor.id, nodeWorldPose(node), options.body, options.shape);\n port = new BabylonTransformPort(node, {\n ...(options.createQuaternion ? { createQuaternion: options.createQuaternion } : {}),\n physics: { facility: this.physics, bodyId: descriptor.id },\n });\n } else {\n port = new BabylonTransformPort(\n node,\n options.createQuaternion ? { createQuaternion: options.createQuaternion } : {},\n );\n }\n this.ports.set(descriptor.id, port);\n this.hitTester.register(descriptor.id, node, options.targetRadius);\n this.runtime.registerInteractable(descriptor, { transform: port });\n return port;\n }\n\n unregister(id: string): void {\n this.runtime.unregisterInteractable(id);\n this.hitTester.unregister(id);\n this.ports.delete(id);\n this.nodes.delete(id);\n this.physics?.removeBody(id);\n }\n\n getPort(id: string): BabylonTransformPort | undefined {\n return this.ports.get(id);\n }\n\n update(dt: number): void {\n // The provider's eye-gaze filter and grace integrate over the same step.\n this.provider.setFrameDelta(dt);\n this.runtime.update(dt);\n if (this.stepPhysics) this.physics?.step(dt);\n }\n\n dispose(): void {\n this.detachScene?.();\n this.detachScene = null;\n this.pointerVisuals?.dispose();\n this.runtime.dispose();\n this.provider.dispose();\n this.physics?.dispose();\n }\n\n private attachToScene(options: BabylonInteractionsOptions): void {\n const observable = options.scene.onBeforeRenderObservable;\n if (!observable) return;\n const engine = options.scene.getEngine?.();\n const observer = observable.add(() => {\n // Babylon reports the frame delta in milliseconds. A long frame - a tab\n // that was backgrounded, a slow first frame - is clamped so behaviours\n // integrate over a sane step rather than jumping.\n const ms = engine?.getDeltaTime() ?? 0;\n this.update(Math.min(MAX_FRAME_SECONDS, Math.max(0, ms / 1000)));\n });\n this.detachScene = () => {\n observable.remove(observer);\n };\n }\n}\n\nexport function createBabylonInteractions(\n options: BabylonInteractionsOptions,\n): BabylonInteractions {\n return new BabylonInteractions(options);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -13,4 +13,6 @@ export * from "./babylon-types.js";
13
13
  export * from "./provider.js";
14
14
  export * from "./hit-tester.js";
15
15
  export * from "./transform-port.js";
16
+ export * from "./physics-facility.js";
17
+ export * from "./pointer-visuals.js";
16
18
  export * from "./host.js";
package/dist/index.js CHANGED
@@ -13,5 +13,7 @@ export * from "./babylon-types.js";
13
13
  export * from "./provider.js";
14
14
  export * from "./hit-tester.js";
15
15
  export * from "./transform-port.js";
16
+ export * from "./physics-facility.js";
17
+ export * from "./pointer-visuals.js";
16
18
  export * from "./host.js";
17
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,uCAAuC,CAAC;AAEtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @realitycollective/babylon-interactions - the Babylon.js engine adapter:\n * Babylon WebXR input, sphere or app-supplied hit-testing, and transform\n * ports over Babylon nodes. The core is re-exported wholesale, so apps\n * depend on exactly this package.\n *\n * Structurally typed against the Babylon API rather than importing\n * `@babylonjs/core`, in the same way as the XR Blocks adapter, so an\n * upstream release cannot break the install.\n */\nexport * from \"@realitycollective/webxr-interactions\";\n\nexport * from \"./babylon-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./host.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,uCAAuC,CAAC;AAEtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @realitycollective/babylon-interactions - the Babylon.js engine adapter:\n * Babylon WebXR input, sphere or app-supplied hit-testing, and transform\n * ports over Babylon nodes. The core is re-exported wholesale, so apps\n * depend on exactly this package.\n *\n * Structurally typed against the Babylon API rather than importing\n * `@babylonjs/core`, in the same way as the XR Blocks adapter, so an\n * upstream release cannot break the install.\n */\nexport * from \"@realitycollective/webxr-interactions\";\n\nexport * from \"./babylon-types.js\";\nexport * from \"./provider.js\";\nexport * from \"./hit-tester.js\";\nexport * from \"./transform-port.js\";\nexport * from \"./physics-facility.js\";\nexport * from \"./pointer-visuals.js\";\nexport * from \"./host.js\";\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * BabylonPhysicsFacility - the Babylon platform's physics, over Physics V2
3
+ * (Havok, or any plugin behind the same API): `PhysicsBody`, `PhysicsShape`
4
+ * and `PhysicsMotionType`. It implements the core `PhysicsFacility` contract
5
+ * with the core defaults (`PHYSICS_DEFAULTS`: IWSDK's), so a body here
6
+ * behaves as it does under Havok on IWSDK itself, and is proved by
7
+ * `physicsFacilityContractCases()`.
8
+ *
9
+ * `@babylonjs/core` (and `@babylonjs/havok`) are not dependencies of this
10
+ * package, for the same reason the rest of `babylon-types.ts` is structural:
11
+ * the app hands over the Havok-era values it already has, through
12
+ * `BabylonPhysicsKitLike` (`PhysicsBody`, the `PhysicsShapeXxx`
13
+ * constructors, `PhysicsMotionType`, `Vector3`, `Quaternion`) - the same
14
+ * "the app supplies the engine values" rule
15
+ * `BabylonTransformPortOptions.physicsMotionTypes` already follows. An app
16
+ * may replace this facility with its own by passing any `PhysicsFacility` to
17
+ * `createBabylonInteractions({ physics })`.
18
+ *
19
+ * What is Babylon's and what is the core's:
20
+ * - a hold is `PhysicsMotionType.ANIMATED` with `disablePreStep` true (node
21
+ * drives physics - see `BabylonPhysicsBodyLike`'s own comment), restored
22
+ * to the body's own motion type with `disablePreStep` false on resume;
23
+ * - a teleport writes the node (through its parent, as
24
+ * `BabylonTransformPort.setWorldPose` resolves one) and calls the body's
25
+ * own `setTargetTransform` with the velocities cleared;
26
+ * - `"auto"` shapes use `PhysicsShapeMesh` over the node's own geometry, or
27
+ * a 0.1 m sphere for a node with none (`getTotalVertices()` zero, or
28
+ * absent - a bare `TransformNode`);
29
+ * - Babylon steps physics itself from `scene.render()`; this facility's own
30
+ * `step` only forwards to the physics engine's internal `_step`, so a
31
+ * caller with no render loop (a test, or an app ticking physics on its
32
+ * own) can still advance the simulation.
33
+ */
34
+ import type { PoseTuple, Vec3Tuple } from "@realitycollective/webxr-input";
35
+ import { type HoldRelease, type PhysicsBodySpec, type PhysicsBodyState, type PhysicsFacility, type PhysicsShapeSpec, type PhysicsVelocity } from "@realitycollective/webxr-interactions";
36
+ import { type BabylonPhysicsKitLike, type BabylonSceneLike, type BabylonTransformNodeLike } from "./babylon-types.js";
37
+ export interface BabylonPhysicsFacilityOptions {
38
+ /**
39
+ * The node behind a body id - the same registered node
40
+ * `createBabylonInteractions` looks up, needed to place the body and, for
41
+ * an `"auto"` shape, to read its geometry.
42
+ */
43
+ nodeFor(id: string): BabylonTransformNodeLike | undefined;
44
+ }
45
+ export declare class BabylonPhysicsFacility implements PhysicsFacility {
46
+ readonly engine = "havok";
47
+ private readonly scene;
48
+ private readonly kit;
49
+ private readonly nodeFor;
50
+ private readonly entries;
51
+ /** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */
52
+ private fallbackGravity;
53
+ constructor(scene: BabylonSceneLike, kit: BabylonPhysicsKitLike, options: BabylonPhysicsFacilityOptions);
54
+ getGravity(): Vec3Tuple;
55
+ setGravity(gravity: Vec3Tuple): void;
56
+ addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;
57
+ removeBody(id: string): void;
58
+ hasBody(id: string): boolean;
59
+ setBodyState(id: string, state: PhysicsBodyState): void;
60
+ getBodyState(id: string): PhysicsBodyState;
61
+ getBodyPose(id: string): PoseTuple;
62
+ setBodyPose(id: string, pose: PoseTuple): void;
63
+ getVelocity(id: string): PhysicsVelocity;
64
+ setVelocity(id: string, velocity: PhysicsVelocity): void;
65
+ suspend(id: string): void;
66
+ resume(id: string, release: HoldRelease): void;
67
+ isSuspended(id: string): boolean;
68
+ step(dtSeconds: number): void;
69
+ dispose(): void;
70
+ private entry;
71
+ /** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */
72
+ private physicsEngine;
73
+ private motionTypeFor;
74
+ private shapeFor;
75
+ /** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */
76
+ private placeNode;
77
+ }
@@ -0,0 +1,199 @@
1
+ import { PHYSICS_DEFAULTS, missingBody, quatConjugate, quatMultiply, resolvePhysicsBody, resolvePhysicsShape, vApplyQuat, vSub, } from "@realitycollective/webxr-interactions";
2
+ import { nodeWorldPose, parentOf, writeQuat, writeVec3, } from "./babylon-types.js";
3
+ const ZERO_VECTOR3 = { x: 0, y: 0, z: 0 };
4
+ const IDENTITY_QUATERNION = { x: 0, y: 0, z: 0, w: 1 };
5
+ function vector3Like(v) {
6
+ return { x: v[0], y: v[1], z: v[2] };
7
+ }
8
+ function quaternionLike(q) {
9
+ return { x: q[0], y: q[1], z: q[2], w: q[3] };
10
+ }
11
+ function copy3(v) {
12
+ return [v[0], v[1], v[2]];
13
+ }
14
+ export class BabylonPhysicsFacility {
15
+ engine = "havok";
16
+ scene;
17
+ kit;
18
+ nodeFor;
19
+ entries = new Map();
20
+ /** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */
21
+ fallbackGravity = copy3(PHYSICS_DEFAULTS.gravity);
22
+ constructor(scene, kit, options) {
23
+ this.scene = scene;
24
+ this.kit = kit;
25
+ this.nodeFor = options.nodeFor;
26
+ }
27
+ getGravity() {
28
+ const engine = this.physicsEngine();
29
+ if (!engine)
30
+ return copy3(this.fallbackGravity);
31
+ return [engine.gravity.x, engine.gravity.y, engine.gravity.z];
32
+ }
33
+ setGravity(gravity) {
34
+ const engine = this.physicsEngine();
35
+ if (!engine) {
36
+ this.fallbackGravity = copy3(gravity);
37
+ return;
38
+ }
39
+ engine.setGravity(vector3Like(gravity));
40
+ }
41
+ addBody(id, pose, body = {}, shape = {}) {
42
+ this.removeBody(id);
43
+ const node = this.nodeFor(id);
44
+ if (!node) {
45
+ throw new Error(`[babylon-interactions] no node for physics body "${id}"; pass nodeFor to BabylonPhysicsFacility`);
46
+ }
47
+ const spec = resolvePhysicsBody(body);
48
+ const shapeSpec = resolvePhysicsShape(shape);
49
+ // The node must already sit at `pose` before the body is constructed:
50
+ // Havok reads the node's transform as the body's starting one.
51
+ this.placeNode(node, pose);
52
+ const physicsBody = new this.kit.PhysicsBody(node, this.motionTypeFor(spec.state), false, this.scene);
53
+ physicsBody.disablePreStep = false;
54
+ physicsBody.setLinearDamping(spec.linearDamping);
55
+ physicsBody.setAngularDamping(spec.angularDamping);
56
+ physicsBody.setGravityFactor(spec.gravityFactor);
57
+ const physicsShape = this.shapeFor(node, shapeSpec);
58
+ physicsShape.material = { friction: shapeSpec.friction, restitution: shapeSpec.restitution };
59
+ physicsShape.density = shapeSpec.density;
60
+ physicsBody.shape = physicsShape;
61
+ this.entries.set(id, { node, body: physicsBody, shape: physicsShape, state: spec.state, suspended: false });
62
+ }
63
+ removeBody(id) {
64
+ const entry = this.entries.get(id);
65
+ if (!entry)
66
+ return;
67
+ entry.body.dispose();
68
+ entry.shape.dispose();
69
+ this.entries.delete(id);
70
+ }
71
+ hasBody(id) {
72
+ return this.entries.has(id);
73
+ }
74
+ setBodyState(id, state) {
75
+ const entry = this.entry(id);
76
+ entry.state = state;
77
+ if (!entry.suspended)
78
+ entry.body.setMotionType(this.motionTypeFor(state));
79
+ }
80
+ getBodyState(id) {
81
+ return this.entry(id).state;
82
+ }
83
+ getBodyPose(id) {
84
+ const node = this.entry(id).node;
85
+ node.computeWorldMatrix?.(true);
86
+ return nodeWorldPose(node);
87
+ }
88
+ setBodyPose(id, pose) {
89
+ const entry = this.entry(id);
90
+ this.placeNode(entry.node, pose);
91
+ entry.body.setTargetTransform(vector3Like(pose.position), quaternionLike(pose.quaternion));
92
+ entry.body.setLinearVelocity(ZERO_VECTOR3);
93
+ entry.body.setAngularVelocity(ZERO_VECTOR3);
94
+ }
95
+ getVelocity(id) {
96
+ const body = this.entry(id).body;
97
+ const linear = body.getLinearVelocity();
98
+ const angular = body.getAngularVelocity();
99
+ return { linear: [linear.x, linear.y, linear.z], angular: [angular.x, angular.y, angular.z] };
100
+ }
101
+ setVelocity(id, velocity) {
102
+ const body = this.entry(id).body;
103
+ body.setLinearVelocity(vector3Like(velocity.linear));
104
+ body.setAngularVelocity(vector3Like(velocity.angular));
105
+ }
106
+ suspend(id) {
107
+ const entry = this.entry(id);
108
+ if (entry.suspended)
109
+ return;
110
+ entry.suspended = true;
111
+ entry.body.disablePreStep = true;
112
+ entry.body.setMotionType(this.kit.PhysicsMotionType.ANIMATED);
113
+ entry.body.setLinearVelocity(ZERO_VECTOR3);
114
+ entry.body.setAngularVelocity(ZERO_VECTOR3);
115
+ }
116
+ resume(id, release) {
117
+ const entry = this.entry(id);
118
+ if (!entry.suspended)
119
+ return;
120
+ entry.suspended = false;
121
+ entry.body.setMotionType(this.motionTypeFor(entry.state));
122
+ entry.body.disablePreStep = false;
123
+ entry.body.setLinearVelocity(vector3Like(release.linearVelocity));
124
+ entry.body.setAngularVelocity(vector3Like(release.angularVelocity));
125
+ }
126
+ isSuspended(id) {
127
+ return this.entry(id).suspended;
128
+ }
129
+ step(dtSeconds) {
130
+ if (dtSeconds <= 0)
131
+ return;
132
+ this.physicsEngine()?._step?.(dtSeconds);
133
+ }
134
+ dispose() {
135
+ for (const id of [...this.entries.keys()])
136
+ this.removeBody(id);
137
+ }
138
+ entry(id) {
139
+ const entry = this.entries.get(id);
140
+ if (!entry)
141
+ throw missingBody(id);
142
+ return entry;
143
+ }
144
+ /** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */
145
+ physicsEngine() {
146
+ return this.scene.getPhysicsEngine ? this.scene.getPhysicsEngine() : null;
147
+ }
148
+ motionTypeFor(state) {
149
+ if (state === "static")
150
+ return this.kit.PhysicsMotionType.STATIC;
151
+ if (state === "kinematic")
152
+ return this.kit.PhysicsMotionType.ANIMATED;
153
+ return this.kit.PhysicsMotionType.DYNAMIC;
154
+ }
155
+ shapeFor(node, shapeSpec) {
156
+ const [a, b] = shapeSpec.dimensions;
157
+ switch (shapeSpec.kind) {
158
+ case "sphere":
159
+ return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, a, this.scene);
160
+ case "box":
161
+ return new this.kit.PhysicsShapeBox(ZERO_VECTOR3, IDENTITY_QUATERNION, vector3Like(shapeSpec.dimensions), this.scene);
162
+ case "capsule": {
163
+ // pointA/pointB are the capsule's two hemisphere centres along its
164
+ // axis: the cylindrical part is `height - 2 * radius` long.
165
+ const half = Math.max(0, b / 2 - a);
166
+ return new this.kit.PhysicsShapeCapsule({ x: 0, y: half, z: 0 }, { x: 0, y: -half, z: 0 }, a, this.scene);
167
+ }
168
+ case "auto": {
169
+ const vertices = node.getTotalVertices?.() ?? 0;
170
+ if (vertices > 0)
171
+ return new this.kit.PhysicsShapeMesh(node, this.scene);
172
+ // A node with no geometry of its own (a bare TransformNode) is a
173
+ // 10 cm sphere, the same default a bare hit-test target gets.
174
+ return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, 0.1, this.scene);
175
+ }
176
+ }
177
+ }
178
+ /** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */
179
+ placeNode(node, pose) {
180
+ const parent = parentOf(node);
181
+ let position = pose.position;
182
+ let quaternion = pose.quaternion;
183
+ if (parent) {
184
+ const parentPose = nodeWorldPose(parent);
185
+ const inverse = quatConjugate(parentPose.quaternion);
186
+ position = vApplyQuat(vSub(pose.position, parentPose.position), inverse);
187
+ quaternion = quatMultiply(inverse, pose.quaternion);
188
+ }
189
+ writeVec3(node.position, position);
190
+ const rotation = node.rotationQuaternion;
191
+ if (rotation) {
192
+ writeQuat(rotation, quaternion);
193
+ }
194
+ else {
195
+ node.rotationQuaternion = new this.kit.Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
196
+ }
197
+ }
198
+ }
199
+ //# sourceMappingURL=physics-facility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"physics-facility.js","sourceRoot":"","sources":["../src/physics-facility.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BabylonPhysicsFacility - the Babylon platform's physics, over Physics V2\n * (Havok, or any plugin behind the same API): `PhysicsBody`, `PhysicsShape`\n * and `PhysicsMotionType`. It implements the core `PhysicsFacility` contract\n * with the core defaults (`PHYSICS_DEFAULTS`: IWSDK's), so a body here\n * behaves as it does under Havok on IWSDK itself, and is proved by\n * `physicsFacilityContractCases()`.\n *\n * `@babylonjs/core` (and `@babylonjs/havok`) are not dependencies of this\n * package, for the same reason the rest of `babylon-types.ts` is structural:\n * the app hands over the Havok-era values it already has, through\n * `BabylonPhysicsKitLike` (`PhysicsBody`, the `PhysicsShapeXxx`\n * constructors, `PhysicsMotionType`, `Vector3`, `Quaternion`) - the same\n * \"the app supplies the engine values\" rule\n * `BabylonTransformPortOptions.physicsMotionTypes` already follows. An app\n * may replace this facility with its own by passing any `PhysicsFacility` to\n * `createBabylonInteractions({ physics })`.\n *\n * What is Babylon's and what is the core's:\n * - a hold is `PhysicsMotionType.ANIMATED` with `disablePreStep` true (node\n * drives physics - see `BabylonPhysicsBodyLike`'s own comment), restored\n * to the body's own motion type with `disablePreStep` false on resume;\n * - a teleport writes the node (through its parent, as\n * `BabylonTransformPort.setWorldPose` resolves one) and calls the body's\n * own `setTargetTransform` with the velocities cleared;\n * - `\"auto\"` shapes use `PhysicsShapeMesh` over the node's own geometry, or\n * a 0.1 m sphere for a node with none (`getTotalVertices()` zero, or\n * absent - a bare `TransformNode`);\n * - Babylon steps physics itself from `scene.render()`; this facility's own\n * `step` only forwards to the physics engine's internal `_step`, so a\n * caller with no render loop (a test, or an app ticking physics on its\n * own) can still advance the simulation.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport {\n PHYSICS_DEFAULTS,\n missingBody,\n quatConjugate,\n quatMultiply,\n resolvePhysicsBody,\n resolvePhysicsShape,\n vApplyQuat,\n vSub,\n type HoldRelease,\n type PhysicsBodySpec,\n type PhysicsBodyState,\n type PhysicsFacility,\n type PhysicsShapeSpec,\n type PhysicsVelocity,\n} from \"@realitycollective/webxr-interactions\";\nimport {\n nodeWorldPose,\n parentOf,\n writeQuat,\n writeVec3,\n type BabylonPhysicsBodyLike,\n type BabylonPhysicsKitLike,\n type BabylonPhysicsShapeLike,\n type BabylonQuaternionLike,\n type BabylonSceneLike,\n type BabylonTransformNodeLike,\n type BabylonVector3Like,\n} from \"./babylon-types.js\";\n\nexport interface BabylonPhysicsFacilityOptions {\n /**\n * The node behind a body id - the same registered node\n * `createBabylonInteractions` looks up, needed to place the body and, for\n * an `\"auto\"` shape, to read its geometry.\n */\n nodeFor(id: string): BabylonTransformNodeLike | undefined;\n}\n\ninterface Entry {\n node: BabylonTransformNodeLike;\n body: BabylonPhysicsBodyLike;\n shape: BabylonPhysicsShapeLike;\n state: PhysicsBodyState;\n suspended: boolean;\n}\n\nconst ZERO_VECTOR3: BabylonVector3Like = { x: 0, y: 0, z: 0 };\nconst IDENTITY_QUATERNION: BabylonQuaternionLike = { x: 0, y: 0, z: 0, w: 1 };\n\nfunction vector3Like(v: Vec3Tuple): BabylonVector3Like {\n return { x: v[0], y: v[1], z: v[2] };\n}\n\nfunction quaternionLike(q: QuatTuple): BabylonQuaternionLike {\n return { x: q[0], y: q[1], z: q[2], w: q[3] };\n}\n\nfunction copy3(v: Readonly<Vec3Tuple>): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\nexport class BabylonPhysicsFacility implements PhysicsFacility {\n readonly engine = \"havok\";\n private readonly scene: BabylonSceneLike;\n private readonly kit: BabylonPhysicsKitLike;\n private readonly nodeFor: (id: string) => BabylonTransformNodeLike | undefined;\n private readonly entries = new Map<string, Entry>();\n /** Gravity for a scene with no physics engine attached yet - `scene.enablePhysics()` not called. */\n private fallbackGravity: Vec3Tuple = copy3(PHYSICS_DEFAULTS.gravity);\n\n constructor(scene: BabylonSceneLike, kit: BabylonPhysicsKitLike, options: BabylonPhysicsFacilityOptions) {\n this.scene = scene;\n this.kit = kit;\n this.nodeFor = options.nodeFor;\n }\n\n getGravity(): Vec3Tuple {\n const engine = this.physicsEngine();\n if (!engine) return copy3(this.fallbackGravity);\n return [engine.gravity.x, engine.gravity.y, engine.gravity.z];\n }\n\n setGravity(gravity: Vec3Tuple): void {\n const engine = this.physicsEngine();\n if (!engine) {\n this.fallbackGravity = copy3(gravity);\n return;\n }\n engine.setGravity(vector3Like(gravity));\n }\n\n addBody(id: string, pose: PoseTuple, body: PhysicsBodySpec = {}, shape: PhysicsShapeSpec = {}): void {\n this.removeBody(id);\n const node = this.nodeFor(id);\n if (!node) {\n throw new Error(`[babylon-interactions] no node for physics body \"${id}\"; pass nodeFor to BabylonPhysicsFacility`);\n }\n const spec = resolvePhysicsBody(body);\n const shapeSpec = resolvePhysicsShape(shape);\n // The node must already sit at `pose` before the body is constructed:\n // Havok reads the node's transform as the body's starting one.\n this.placeNode(node, pose);\n const physicsBody = new this.kit.PhysicsBody(node, this.motionTypeFor(spec.state), false, this.scene);\n physicsBody.disablePreStep = false;\n physicsBody.setLinearDamping(spec.linearDamping);\n physicsBody.setAngularDamping(spec.angularDamping);\n physicsBody.setGravityFactor(spec.gravityFactor);\n const physicsShape = this.shapeFor(node, shapeSpec);\n physicsShape.material = { friction: shapeSpec.friction, restitution: shapeSpec.restitution };\n physicsShape.density = shapeSpec.density;\n physicsBody.shape = physicsShape;\n this.entries.set(id, { node, body: physicsBody, shape: physicsShape, state: spec.state, suspended: false });\n }\n\n removeBody(id: string): void {\n const entry = this.entries.get(id);\n if (!entry) return;\n entry.body.dispose();\n entry.shape.dispose();\n this.entries.delete(id);\n }\n\n hasBody(id: string): boolean {\n return this.entries.has(id);\n }\n\n setBodyState(id: string, state: PhysicsBodyState): void {\n const entry = this.entry(id);\n entry.state = state;\n if (!entry.suspended) entry.body.setMotionType(this.motionTypeFor(state));\n }\n\n getBodyState(id: string): PhysicsBodyState {\n return this.entry(id).state;\n }\n\n getBodyPose(id: string): PoseTuple {\n const node = this.entry(id).node;\n node.computeWorldMatrix?.(true);\n return nodeWorldPose(node);\n }\n\n setBodyPose(id: string, pose: PoseTuple): void {\n const entry = this.entry(id);\n this.placeNode(entry.node, pose);\n entry.body.setTargetTransform(vector3Like(pose.position), quaternionLike(pose.quaternion));\n entry.body.setLinearVelocity(ZERO_VECTOR3);\n entry.body.setAngularVelocity(ZERO_VECTOR3);\n }\n\n getVelocity(id: string): PhysicsVelocity {\n const body = this.entry(id).body;\n const linear = body.getLinearVelocity();\n const angular = body.getAngularVelocity();\n return { linear: [linear.x, linear.y, linear.z], angular: [angular.x, angular.y, angular.z] };\n }\n\n setVelocity(id: string, velocity: PhysicsVelocity): void {\n const body = this.entry(id).body;\n body.setLinearVelocity(vector3Like(velocity.linear));\n body.setAngularVelocity(vector3Like(velocity.angular));\n }\n\n suspend(id: string): void {\n const entry = this.entry(id);\n if (entry.suspended) return;\n entry.suspended = true;\n entry.body.disablePreStep = true;\n entry.body.setMotionType(this.kit.PhysicsMotionType.ANIMATED);\n entry.body.setLinearVelocity(ZERO_VECTOR3);\n entry.body.setAngularVelocity(ZERO_VECTOR3);\n }\n\n resume(id: string, release: HoldRelease): void {\n const entry = this.entry(id);\n if (!entry.suspended) return;\n entry.suspended = false;\n entry.body.setMotionType(this.motionTypeFor(entry.state));\n entry.body.disablePreStep = false;\n entry.body.setLinearVelocity(vector3Like(release.linearVelocity));\n entry.body.setAngularVelocity(vector3Like(release.angularVelocity));\n }\n\n isSuspended(id: string): boolean {\n return this.entry(id).suspended;\n }\n\n step(dtSeconds: number): void {\n if (dtSeconds <= 0) return;\n this.physicsEngine()?._step?.(dtSeconds);\n }\n\n dispose(): void {\n for (const id of [...this.entries.keys()]) this.removeBody(id);\n }\n\n private entry(id: string): Entry {\n const entry = this.entries.get(id);\n if (!entry) throw missingBody(id);\n return entry;\n }\n\n /** `scene.getPhysicsEngine()` - null on a scene without a method (a fake, or an old build) as well as one that has not enabled physics yet. */\n private physicsEngine() {\n return this.scene.getPhysicsEngine ? this.scene.getPhysicsEngine() : null;\n }\n\n private motionTypeFor(state: PhysicsBodyState): unknown {\n if (state === \"static\") return this.kit.PhysicsMotionType.STATIC;\n if (state === \"kinematic\") return this.kit.PhysicsMotionType.ANIMATED;\n return this.kit.PhysicsMotionType.DYNAMIC;\n }\n\n private shapeFor(node: BabylonTransformNodeLike, shapeSpec: Required<PhysicsShapeSpec>): BabylonPhysicsShapeLike {\n const [a, b] = shapeSpec.dimensions;\n switch (shapeSpec.kind) {\n case \"sphere\":\n return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, a, this.scene);\n case \"box\":\n return new this.kit.PhysicsShapeBox(ZERO_VECTOR3, IDENTITY_QUATERNION, vector3Like(shapeSpec.dimensions), this.scene);\n case \"capsule\": {\n // pointA/pointB are the capsule's two hemisphere centres along its\n // axis: the cylindrical part is `height - 2 * radius` long.\n const half = Math.max(0, b / 2 - a);\n return new this.kit.PhysicsShapeCapsule({ x: 0, y: half, z: 0 }, { x: 0, y: -half, z: 0 }, a, this.scene);\n }\n case \"auto\": {\n const vertices = node.getTotalVertices?.() ?? 0;\n if (vertices > 0) return new this.kit.PhysicsShapeMesh(node, this.scene);\n // A node with no geometry of its own (a bare TransformNode) is a\n // 10 cm sphere, the same default a bare hit-test target gets.\n return new this.kit.PhysicsShapeSphere(ZERO_VECTOR3, 0.1, this.scene);\n }\n }\n }\n\n /** Write a world pose into a node, through its parent's frame - the same resolution `BabylonTransformPort.setWorldPose` applies. */\n private placeNode(node: BabylonTransformNodeLike, pose: PoseTuple): void {\n const parent = parentOf(node);\n let position = pose.position;\n let quaternion = pose.quaternion;\n if (parent) {\n const parentPose = nodeWorldPose(parent);\n const inverse = quatConjugate(parentPose.quaternion);\n position = vApplyQuat(vSub(pose.position, parentPose.position), inverse);\n quaternion = quatMultiply(inverse, pose.quaternion);\n }\n writeVec3(node.position, position);\n const rotation = node.rotationQuaternion;\n if (rotation) {\n writeQuat(rotation, quaternion);\n } else {\n node.rotationQuaternion = new this.kit.Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);\n }\n }\n}\n"]}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * BabylonPointerVisuals - draws the ray stub and the cursor disc for every
3
+ * source, exactly as the core decided them (`InteractionRuntime.onPointerDrawing`:
4
+ * the pointer arbiter's decision under the app's pointer display settings).
5
+ *
6
+ * This is IWSDK 1.0.0's look restated for Babylon (`@iwsdk/xr-input`
7
+ * `ray-pointer.js` and `cursor-visual.js`), so a Babylon app shows what a
8
+ * Quest shows on the web:
9
+ *
10
+ * - the ray is a thin cylinder along the source's ray, spanning `rayFrom` to
11
+ * `rayTo` metres from its origin, so with the defaults it is the 1 mm white
12
+ * stub from 3 cm in front of the hand to `min(0.3 m, the hit)`, blue while
13
+ * selecting;
14
+ * - the cursor is a disc at the hit, facing the viewer along the pointing
15
+ * direction (the ray's, or the line from the head for a near hit), nudged
16
+ * `cursorOffset` off the surface, white, with opacity and radius from the
17
+ * drawing.
18
+ *
19
+ * The renderer decides nothing: it reads each frame's `PointerDrawing` and
20
+ * the source's ray from the runtime and writes them onto its meshes. Meshes
21
+ * are created per source on first sight and disposed with the renderer.
22
+ * Positions and directions arrive in world space as the provider gives them,
23
+ * so nothing is flipped for Babylon's left-handed default. Like the rest of
24
+ * this package it never imports `@babylonjs/core`: the app hands over the
25
+ * constructors it uses as a {@link BabylonPointerVisualsKit}.
26
+ */
27
+ import type { InteractionRuntime, PointerDrawing } from "@realitycollective/webxr-interactions";
28
+ import type { BabylonQuaternionLike, BabylonVector3Like } from "./babylon-types.js";
29
+ /** Structural slice of Babylon's `Color3`. */
30
+ export interface BabylonColor3Like {
31
+ r: number;
32
+ g: number;
33
+ b: number;
34
+ }
35
+ /** Structural slice of the `StandardMaterial` members the renderer writes. */
36
+ export interface BabylonPointerMaterialLike {
37
+ emissiveColor: BabylonColor3Like;
38
+ diffuseColor: BabylonColor3Like;
39
+ /** 0 transparent to 1 opaque. */
40
+ alpha: number;
41
+ /** Unlit so the colour is the colour on screen. */
42
+ disableLighting?: boolean;
43
+ dispose(): void;
44
+ }
45
+ /** Structural slice of the `Mesh` members the renderer writes. */
46
+ export interface BabylonPointerMeshLike {
47
+ position: BabylonVector3Like;
48
+ rotationQuaternion: BabylonQuaternionLike | null;
49
+ scaling: BabylonVector3Like;
50
+ isVisible: boolean;
51
+ material: BabylonPointerMaterialLike | null;
52
+ /** Higher groups draw later; the cursor uses 1 so it draws over the scene. */
53
+ renderingGroupId?: number;
54
+ dispose(): void;
55
+ }
56
+ /** What the app imports from `@babylonjs/core` and hands to the renderer. */
57
+ export interface BabylonPointerVisualsKit {
58
+ MeshBuilder: {
59
+ /** A cylinder along the Y axis, centred on the origin. */
60
+ CreateCylinder(name: string, options: {
61
+ height: number;
62
+ diameter: number;
63
+ tessellation?: number;
64
+ }, scene: unknown): BabylonPointerMeshLike;
65
+ /** A disc in the XY plane whose front face looks down -Z. */
66
+ CreateDisc(name: string, options: {
67
+ radius: number;
68
+ tessellation?: number;
69
+ }, scene: unknown): BabylonPointerMeshLike;
70
+ };
71
+ StandardMaterial: new (name: string, scene: unknown) => BabylonPointerMaterialLike;
72
+ Color3: new (r: number, g: number, b: number) => BabylonColor3Like;
73
+ Quaternion: new (x: number, y: number, z: number, w: number) => BabylonQuaternionLike;
74
+ }
75
+ export interface BabylonPointerVisualsOptions {
76
+ /** The runtime whose drawings are applied. */
77
+ runtime: InteractionRuntime;
78
+ /** The Babylon constructors the renderer builds its meshes from. */
79
+ kit: BabylonPointerVisualsKit;
80
+ /** The scene the meshes are created in (handed to the kit as is). */
81
+ scene: unknown;
82
+ }
83
+ interface SourceMeshes {
84
+ ray: BabylonPointerMeshLike;
85
+ cursor: BabylonPointerMeshLike;
86
+ }
87
+ export declare class BabylonPointerVisuals {
88
+ private readonly runtime;
89
+ private readonly kit;
90
+ private readonly scene;
91
+ private readonly meshes;
92
+ private readonly unsubscribe;
93
+ private disposed;
94
+ constructor(options: BabylonPointerVisualsOptions);
95
+ /** The meshes drawn for one source, for a test or an app that restyles them. */
96
+ meshesFor(sourceId: string): SourceMeshes | undefined;
97
+ /** Apply one frame's drawings: create meshes for new sources, hide those of sources gone. */
98
+ apply(drawings: readonly PointerDrawing[]): void;
99
+ private create;
100
+ private material;
101
+ private applyRay;
102
+ private applyCursor;
103
+ dispose(): void;
104
+ }
105
+ export {};