@realitycollective/webxr-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 +17 -2
- package/dist/behaviours/behaviour.d.ts +17 -1
- package/dist/behaviours/behaviour.js +12 -2
- package/dist/behaviours/behaviour.js.map +1 -1
- package/dist/behaviours/grab.d.ts +1 -0
- package/dist/behaviours/grab.js +10 -4
- package/dist/behaviours/grab.js.map +1 -1
- package/dist/contract-cases.js +84 -0
- package/dist/contract-cases.js.map +1 -1
- package/dist/descriptor.d.ts +7 -1
- package/dist/descriptor.js.map +1 -1
- package/dist/gaze.d.ts +50 -0
- package/dist/gaze.js +162 -0
- package/dist/gaze.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +8 -0
- package/dist/math.js +17 -0
- package/dist/math.js.map +1 -1
- package/dist/memory-physics.d.ts +41 -0
- package/dist/memory-physics.js +168 -0
- package/dist/memory-physics.js.map +1 -0
- package/dist/memory-ports.d.ts +55 -0
- package/dist/memory-ports.js +128 -0
- package/dist/memory-ports.js.map +1 -0
- package/dist/near-pointer-contract-cases.d.ts +16 -0
- package/dist/near-pointer-contract-cases.js +283 -0
- package/dist/near-pointer-contract-cases.js.map +1 -0
- package/dist/near-pointer.d.ts +88 -0
- package/dist/near-pointer.js +114 -0
- package/dist/near-pointer.js.map +1 -0
- package/dist/physics-contract-cases.d.ts +11 -0
- package/dist/physics-contract-cases.js +229 -0
- package/dist/physics-contract-cases.js.map +1 -0
- package/dist/physics.d.ts +148 -0
- package/dist/physics.js +35 -0
- package/dist/physics.js.map +1 -0
- package/dist/ports.d.ts +15 -0
- package/dist/ports.js.map +1 -1
- package/dist/release-velocity.d.ts +58 -0
- package/dist/release-velocity.js +89 -0
- package/dist/release-velocity.js.map +1 -0
- package/dist/runtime.d.ts +129 -4
- package/dist/runtime.js +362 -40
- package/dist/runtime.js.map +1 -1
- package/dist/trace-synthetic.d.ts +12 -0
- package/dist/trace-synthetic.js +194 -0
- package/dist/trace-synthetic.js.map +1 -0
- package/dist/trace.d.ts +215 -0
- package/dist/trace.js +350 -0
- package/dist/trace.js.map +1 -0
- package/package.json +8 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-cases.js","sourceRoot":"","sources":["../src/contract-cases.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared `HitTester` and `TransformPort` conformance suites, shipped as data\n * rather than as tests - the same idea as `windowHostContractCases()` in\n * WebXR-UIExtensions and `inputProviderContractCases()` in\n * `@realitycollective/webxr-input`. Five adapters implement these two ports\n * against five different scene graphs; running the same checks against every\n * one is what stops an adapter quietly dropping a documented rule.\n *\n * The suites are runner-free on purpose. Every adapter repository already has\n * its own test runner, and an adapter written outside this repository cannot\n * reach into this one's `test/` folder, so the checks ship as plain objects\n * that throw an `Error` on failure and the adapter iterates them.\n *\n * Both suites assert only what `ports.ts` documents, including two rules\n * every platform now shares:\n * - `getWorldPose()` is the LIVE pose and `getRestWorldPose()` the rest pose\n * offsets are measured from.\n * - Every tuple a port returns is fresh and owned by the caller, and a tuple\n * passed in is not kept.\n */\nimport type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { quatMultiply, vAdd, vApplyQuat } from \"./math.js\";\nimport type { HitTester, HoldRelease, TransformPort } from \"./ports.js\";\n\n// ---------------------------------------------------------------------------\n// HitTester\n// ---------------------------------------------------------------------------\n\n/**\n * Places (or replaces) one target for a case to query against. Build a FRESH\n * subject per case: cases place targets of their own and do not clean up.\n */\nexport interface HitTesterContractDriver {\n /**\n * Register a target at `position` with an interaction radius of `radius`,\n * however the platform registers a target - a mesh, a node, an entity.\n */\n place(id: string, position: Vec3Tuple, radius: number): void;\n /**\n * Register a target at `position` with NO declared radius - a bare node\n * with no geometry of its own - on a platform that can register one\n * without a radius (three.js, XR Blocks, Babylon, IWSDK). `hitProximity`\n * must then treat it as a 0.1 m sphere, `ports.ts`'s documented default.\n * Leave this out on a platform whose registration always takes an\n * explicit radius (native); the case that needs it skips without running.\n */\n placeBare?(id: string, position: Vec3Tuple): void;\n /**\n * Hide, or show again, a placed target using the platform's own ordinary\n * visibility flag alone - three.js `Object3D.visible`, Babylon\n * `isVisible`/`setEnabled`, and so on - never by unregistering it. A hit\n * tester must never answer with a target hidden this way, even on an\n * engine whose own raycast ignores that flag by design (three.js's\n * `Raycaster`; see `ThreeHitTester.hitRay`'s own comment). Leave this out\n * on a platform with no such flag to toggle; the case that needs it skips.\n */\n setVisible?(id: string, visible: boolean): void;\n}\n\n/** The tester under test, plus the means of putting targets into its scene. */\nexport interface HitTesterContractSubject {\n hitTester: HitTester;\n driver: HitTesterContractDriver;\n}\n\n/** One check a {@link HitTester} implementation must pass. */\nexport interface HitTesterContractCase {\n name: string;\n run(subject: HitTesterContractSubject): void;\n}\n\n/**\n * The shared `HitTester` conformance suite. An adapter's test file is a loop:\n *\n * ```ts\n * for (const contractCase of hitTesterContractCases()) {\n * it(contractCase.name, () => contractCase.run(makeSubject()));\n * }\n * ```\n */\nexport function hitTesterContractCases(): readonly HitTesterContractCase[] {\n return HIT_TESTER_CASES;\n}\n\nconst DOWN_RAY: RayTuple = { origin: [0, 0, 0], direction: [0, 0, -1] };\nconst AWAY_RAY: RayTuple = { origin: [0, 0, 0], direction: [0, 0, 1] };\n\n/**\n * A ray-based query only has to land WITHIN the target's own radius:\n * `ports.ts` documents `point` as \"world-space hit or closest point\", so a\n * mesh-accurate ray tester (three.js) reporting the surface it actually hit\n * is as conforming as a sphere tester (Babylon, IWSDK) reporting the target's\n * centre. Placing the target exactly on the ray's line makes both answers\n * the same point when a tester has no radius-shaped slack, and this is the\n * tolerance for testers that do.\n */\n/** A surface distance is exact up to 1 mm, the tolerance the master row states. */\nconst SURFACE_EPS = 1e-3;\n\nfunction slack(radius: number): number {\n return radius + 0.05;\n}\n\nconst HIT_TESTER_CASES: readonly HitTesterContractCase[] = [\n {\n name: \"an empty scene answers a ray with null\",\n run({ hitTester }) {\n assert(hitTester.hitRay(DOWN_RAY) === null, \"hitRay over an empty scene must return null\");\n },\n },\n {\n name: \"a ray through a placed target returns its id, a positive distance and a point near the ray\",\n run({ hitTester, driver }) {\n const radius = 0.15;\n driver.place(\"target\", [0, 0, -2], radius);\n const hit = hitTester.hitRay(DOWN_RAY);\n assert(hit !== null, \"a ray through a placed target must not miss\");\n assert(hit.interactableId === \"target\", `expected interactableId \"target\", got \"${hit.interactableId}\"`);\n assert(hit.distance > 0, `distance must be positive, got ${hit.distance}`);\n const eps = slack(radius);\n assert(closeTo(hit.distance, 2, eps), `distance must be close to 2, got ${hit.distance}`);\n assertVec3Close(hit.point, [0, 0, -2], eps, \"point\");\n },\n },\n {\n name: \"a ray pointing away from the target returns null\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.15);\n assert(hitTester.hitRay(AWAY_RAY) === null, \"a ray pointing away from every target must return null\");\n },\n },\n {\n name: \"hitProximity finds a target inside its radius and misses outside it\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.1);\n const inside = hitTester.hitProximity([0, 0, -2], 0.5);\n assert(inside !== null, \"hitProximity at the target's own position must find it\");\n assert(\n inside.interactableId === \"target\",\n `expected interactableId \"target\", got \"${inside.interactableId}\"`,\n );\n const outside = hitTester.hitProximity([5, 5, 5], 0.05);\n assert(outside === null, \"hitProximity far from every target must return null\");\n },\n },\n {\n name: \"hitProximity reports the distance to the target's SURFACE, clamped at zero\",\n run({ hitTester, driver }) {\n // The master row: a point 3 cm outside a 10 cm target reports 0.03, not\n // 0.13. A tester that reports the distance to the centre removes every\n // near interaction, because no fingertip is ever within 5 cm of a\n // target's centre.\n driver.place(\"target\", [0, 0, -2], 0.1);\n const outside = hitTester.hitProximity([0.13, 0, -2], 0.5);\n assert(outside !== null, \"a point 3 cm outside a 10 cm target must be found within 0.5 m\");\n assert(\n closeTo(outside.distance, 0.03, SURFACE_EPS),\n `distance must be to the surface, 0.03 within 1 mm, got ${outside.distance}`,\n );\n const inside = hitTester.hitProximity([0.02, 0, -2], 0.5);\n assert(inside !== null && inside.distance === 0, `a point inside the target is at distance 0, got ${inside?.distance}`);\n },\n },\n {\n name: \"hitProximity misses a target whose surface is farther than the query radius\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.1);\n assert(\n hitTester.hitProximity([0.13, 0, -2], 0.02) === null,\n \"a surface 3 cm away is outside a 2 cm query and must not be found\",\n );\n assert(\n hitTester.hitProximity([0.13, 0, -2], 0.04) !== null,\n \"a surface 3 cm away is inside a 4 cm query and must be found\",\n );\n },\n },\n {\n name: \"a ray that passes a target farther than its radius misses it\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0.5, 0, -2], 0.1);\n assert(hitTester.hitRay(DOWN_RAY) === null, \"a ray 0.5 m from a 0.1 m target must miss it\");\n },\n },\n {\n name: \"with two targets on one ray, the nearer one wins\",\n run({ hitTester, driver }) {\n const radius = 0.1;\n driver.place(\"far\", [0, 0, -3], radius);\n driver.place(\"near\", [0, 0, -1], radius);\n const hit = hitTester.hitRay(DOWN_RAY);\n assert(hit !== null, \"a ray through two targets must not miss\");\n assert(hit.interactableId === \"near\", `the nearer target must win, got \"${hit.interactableId}\"`);\n assert(closeTo(hit.distance, 1, slack(radius)), `distance must be close to 1, got ${hit.distance}`);\n },\n },\n {\n name: \"hitProximity treats a target with no declared radius as a 0.1 m sphere, when the driver can register one\",\n run({ hitTester, driver }) {\n if (!driver.placeBare) return;\n driver.placeBare(\"bare\", [0, 0, -2]);\n // 0.12 m from the centre is 0.02 m outside a 0.1 m default radius -\n // found within a 0.05 m query, and NOT the 0.12 m a zero-radius\n // reading would report.\n const found = hitTester.hitProximity([0.12, 0, -2], 0.05);\n assert(\n found !== null,\n \"a point 0.12 m from a bare target's centre must be found within a 0.05 m query, given the 0.1 m default radius\",\n );\n assert(\n closeTo(found.distance, 0.02, SURFACE_EPS),\n `distance must be to the default 0.1 m surface, 0.02 within 1 mm, got ${found.distance}`,\n );\n },\n },\n {\n name: \"an object hidden by visible alone is never hit, when the driver can toggle it\",\n run({ hitTester, driver }) {\n if (!driver.setVisible) return;\n driver.place(\"target\", [0, 0, -2], 0.15);\n driver.setVisible(\"target\", false);\n assert(\n hitTester.hitRay(DOWN_RAY) === null,\n \"a ray must not hit a target hidden by visible alone\",\n );\n assert(\n hitTester.hitProximity([0, 0, -2], 0.5) === null,\n \"hitProximity must not find a target hidden by visible alone\",\n );\n driver.setVisible(\"target\", true);\n assert(\n hitTester.hitRay(DOWN_RAY) !== null,\n \"showing the target again must restore the hit\",\n );\n },\n },\n {\n name: \"each hit and its point are fresh values the caller owns\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.15);\n for (const [label, query] of [\n [\"hitRay\", () => hitTester.hitRay(DOWN_RAY)],\n [\"hitProximity\", () => hitTester.hitProximity([0, 0, -2], 0.5)],\n ] as const) {\n const first = query();\n const second = query();\n assert(first !== null && second !== null, `${label} must find the placed target`);\n assert(second !== first && second.point !== first.point, `${label} must return a new hit and point each call`);\n const expected = first.point[2];\n first.point[2] = 1e6;\n const third = query();\n assert(\n third !== null && closeTo(third.point[2], expected, POSE_EPS),\n `writing to a returned point must not change the next ${label} answer`,\n );\n }\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// TransformPort\n// ---------------------------------------------------------------------------\n\n/**\n * Advances a subject's physics simulation - a real engine step where a\n * platform's physics can run in a test, otherwise a fake that faithfully\n * reproduces the one behaviour these cases check: gravity moves the object\n * unless held, and a release velocity carries it.\n */\nexport interface TransformPortPhysicsDriver {\n step(dtSeconds: number): void;\n}\n\n/** The port under test, over one object. Build a FRESH one per case. */\nexport interface TransformPortContractSubject {\n port: TransformPort;\n /**\n * The world pose the object sat at when the port captured its rest. Place\n * it away from the origin and turned, under no parent or an unscaled one,\n * so a port that ignores its rest frame cannot pass by accident.\n */\n rest: PoseTuple;\n /**\n * Present only when the object has a physics body - the same condition\n * that gives the port `beginHold`/`endHold`. The held/released/reset\n * cases below skip without one, since there is nothing for them to check.\n */\n physics?: TransformPortPhysicsDriver;\n}\n\n/** One check a {@link TransformPort} implementation must pass. */\nexport interface TransformPortContractCase {\n name: string;\n run(subject: TransformPortContractSubject): void;\n}\n\n/**\n * The shared `TransformPort` conformance suite. An adapter's test file is a\n * loop, the same shape as {@link hitTesterContractCases}.\n */\nexport function transformPortContractCases(): readonly TransformPortContractCase[] {\n return TRANSFORM_PORT_CASES;\n}\n\nconst POSE_EPS = 1e-3;\n// A quarter turn about Y - a non-identity unit quaternion, so the case\n// cannot pass by accident on a port that ignores its argument.\nconst QUARTER_TURN_Y: QuatTuple = [0, Math.SQRT1_2, 0, Math.SQRT1_2];\n\n// ---------------------------------------------------------------------------\n// Held / released / reset - the \"held pose\" rule, checked only when the\n// subject carries a physics driver. One fixed step size and a looser\n// position tolerance than POSE_EPS: real engines differ slightly in their\n// integrator, and a held/reset case allows for up to one step of gravity\n// (see PHYSICS_POSE_EPS's own comment), where the plain pose cases above\n// never touch physics at all.\n// ---------------------------------------------------------------------------\n\nconst PHYSICS_DT = 1 / 60;\nconst HELD_STEPS = 5;\n/** metres/second - small enough that one step's drift is easy to read, large enough not to vanish into PHYSICS_POSE_EPS. */\nconst RELEASE_SPEED = 2;\n/** metres/second - about 25x RELEASE_SPEED, so \"kept the old velocity\" and \"one step of gravity\" cannot be confused. */\nconst LARGE_PRIOR_SPEED = 50;\n/**\n * 2 cm. One step of gravity at PHYSICS_DT droops roughly\n * `0.5 * 9.8 * PHYSICS_DT^2` ≈ 1.4 mm, well inside this; a full step of\n * LARGE_PRIOR_SPEED (≈ 0.83 m) or RELEASE_SPEED (≈ 33 mm) is well outside it.\n */\nconst PHYSICS_POSE_EPS = 0.02;\n\nfunction assertPhysicsPoseClose(value: PoseTuple, expected: PoseTuple, label: string): void {\n assertVec3Close(value.position, expected.position, PHYSICS_POSE_EPS, `${label}.position`);\n assertQuatClose(value.quaternion, expected.quaternion, `${label}.quaternion`);\n}\n\nconst TRANSFORM_PORT_CASES: readonly TransformPortContractCase[] = [\n {\n name: \"setLocalOffset then getLocalOffset returns the same offset\",\n run({ port }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n assertVec3Close(port.getLocalOffset(), offset, POSE_EPS, \"getLocalOffset()\");\n },\n },\n {\n name: \"getWorldPose returns finite numbers and a unit quaternion\",\n run({ port }) {\n const pose = port.getWorldPose();\n for (const value of [...pose.position, ...pose.quaternion]) {\n assert(Number.isFinite(value), `getWorldPose() must return finite numbers, got ${value}`);\n }\n const length = quatLength(pose.quaternion);\n assert(\n closeTo(length, 1, POSE_EPS),\n `getWorldPose().quaternion must be a unit quaternion, length was ${length}`,\n );\n },\n },\n {\n name: \"setLocalRotation with a unit quaternion does not throw\",\n run({ port }) {\n try {\n port.setLocalRotation(QUARTER_TURN_Y);\n } catch (error) {\n throw new Error(`setLocalRotation(unit quaternion) must not throw, it threw: ${String(error)}`);\n }\n },\n },\n {\n name: \"getRestWorldPose returns the pose captured at registration\",\n run({ port, rest }) {\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose()\");\n },\n },\n {\n name: \"before any write, the live pose is the rest pose\",\n run({ port, rest }) {\n assertPoseClose(port.getWorldPose(), rest, \"getWorldPose()\");\n },\n },\n {\n name: \"getWorldPose follows setLocalOffset while getRestWorldPose stays put\",\n run({ port, rest }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n assertVec3Close(\n port.getWorldPose().position,\n vAdd(rest.position, vApplyQuat(offset, rest.quaternion)),\n POSE_EPS,\n \"getWorldPose().position after setLocalOffset\",\n );\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setLocalOffset\");\n },\n },\n {\n name: \"getWorldPose follows setLocalRotation while getRestWorldPose stays put\",\n run({ port, rest }) {\n port.setLocalRotation(QUARTER_TURN_Y);\n assertQuatClose(\n port.getWorldPose().quaternion,\n quatMultiply(rest.quaternion, QUARTER_TURN_Y),\n \"getWorldPose().quaternion after setLocalRotation\",\n );\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setLocalRotation\");\n },\n },\n {\n name: \"setWorldPose, when present, is what getWorldPose reads back\",\n run({ port, rest }) {\n if (!port.setWorldPose) return;\n const target: PoseTuple = { position: [0.5, 1.2, -0.4], quaternion: QUARTER_TURN_Y };\n port.setWorldPose(target);\n assertPoseClose(port.getWorldPose(), target, \"getWorldPose() after setWorldPose\");\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setWorldPose\");\n },\n },\n {\n name: \"every returned tuple is a fresh value the caller owns\",\n run({ port }) {\n const pose = port.getWorldPose();\n const restPose = port.getRestWorldPose();\n const offset = port.getLocalOffset();\n pose.position[0] = 1e6;\n pose.quaternion[3] = 1e6;\n restPose.position[0] = 1e6;\n offset[0] = 1e6;\n assert(port.getWorldPose().position[0] !== 1e6, \"writing to a returned getWorldPose() tuple must not change the port\");\n assert(port.getWorldPose().quaternion[3] !== 1e6, \"writing to a returned getWorldPose() quaternion must not change the port\");\n assert(port.getRestWorldPose().position[0] !== 1e6, \"writing to a returned getRestWorldPose() tuple must not change the port\");\n assert(port.getLocalOffset()[0] !== 1e6, \"writing to a returned getLocalOffset() tuple must not change the port\");\n },\n },\n {\n name: \"a tuple passed in is not kept\",\n run({ port }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n offset[0] = 9;\n assertVec3Close(port.getLocalOffset(), [0.3, -0.1, 0.05], POSE_EPS, \"getLocalOffset() after the caller reused its tuple\");\n if (!port.setWorldPose) return;\n const target: PoseTuple = { position: [0.5, 1.2, -0.4], quaternion: [0, 0, 0, 1] };\n port.setWorldPose(target);\n target.position[0] = 9;\n assertVec3Close(port.getWorldPose().position, [0.5, 1.2, -0.4], POSE_EPS, \"getWorldPose() after the caller reused its tuple\");\n },\n },\n {\n name: \"setEffect, when present, accepts scale and emissive without throwing\",\n run({ port }) {\n if (!port.setEffect) return;\n try {\n port.setEffect({ scale: 1.2, emissive: 0.3 });\n } catch (error) {\n throw new Error(`setEffect({ scale, emissive }) must not throw, it threw: ${String(error)}`);\n }\n },\n },\n {\n name: \"Held: beginHold holds a fixed setWorldPose without falling, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.setWorldPose || !port.beginHold) return;\n const held: PoseTuple = {\n position: [rest.position[0], rest.position[1] + 1, rest.position[2]],\n quaternion: rest.quaternion,\n };\n port.beginHold();\n for (let i = 0; i < HELD_STEPS; i++) {\n port.setWorldPose(held);\n physics.step(PHYSICS_DT);\n }\n // A tight tolerance, not PHYSICS_POSE_EPS: a genuinely held body sees\n // no simulation at all, so it matches the written pose exactly bar\n // floating point noise - the same POSE_EPS the non-physics cases\n // above use. One step of unwanted gravity (~2.7 mm at PHYSICS_DT) is\n // what this case exists to catch, and it must not hide inside the\n // tolerance the way it deliberately may in the Reset case below.\n assertPoseClose(port.getWorldPose(), held, `getWorldPose() after ${HELD_STEPS} held steps`);\n },\n },\n {\n name: \"Released: endHold sends the body away with the release velocity, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.beginHold || !port.endHold) return;\n port.beginHold();\n const release: HoldRelease = { linearVelocity: [RELEASE_SPEED, 0, 0], angularVelocity: [0, 0, 0] };\n port.endHold(release);\n physics.step(PHYSICS_DT);\n const moved = port.getWorldPose().position[0] - rest.position[0];\n assert(\n moved > RELEASE_SPEED * PHYSICS_DT * 0.5,\n `endHold({ linearVelocity: [${RELEASE_SPEED}, 0, 0] }) must move the body that way within one step, moved ${moved}`,\n );\n },\n },\n {\n name: \"Reset: setWorldPose while not held teleports to the pose and clears velocity, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.setWorldPose || !port.beginHold || !port.endHold) return;\n // Give the body a large prior velocity and let it run for a step, so\n // carrying that velocity through the reset below is unmistakable -\n // one step of it (~0.83 m) dwarfs PHYSICS_POSE_EPS (2 cm).\n port.beginHold();\n const priorRelease: HoldRelease = { linearVelocity: [LARGE_PRIOR_SPEED, 0, 0], angularVelocity: [0, 0, 0] };\n port.endHold(priorRelease);\n physics.step(PHYSICS_DT);\n const target: PoseTuple = {\n position: [rest.position[0] - 1, rest.position[1] + 2, rest.position[2] + 0.5],\n quaternion: rest.quaternion,\n };\n port.setWorldPose(target);\n // One further step: it must be at `target`, not carrying the prior\n // velocity forward - allowing for this one step's worth of gravity.\n physics.step(PHYSICS_DT);\n assertPhysicsPoseClose(port.getWorldPose(), target, \"getWorldPose() one step after a reset setWorldPose\");\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Shared assertion helpers\n// ---------------------------------------------------------------------------\n\nfunction assert(condition: boolean, message: string): asserts condition {\n if (!condition) throw new Error(message);\n}\n\nfunction closeTo(a: number, b: number, eps: number): boolean {\n return Math.abs(a - b) <= eps;\n}\n\n// Indexed one component at a time, rather than in a loop, so each index is a\n// literal: `noUncheckedIndexedAccess` cannot narrow a tuple read through a\n// variable index back to `number`.\nfunction assertVec3Close(value: Vec3Tuple, expected: Vec3Tuple, eps: number, label: string): void {\n assert(closeTo(value[0], expected[0], eps), `${label}[0] must be close to ${expected[0]}, got ${value[0]}`);\n assert(closeTo(value[1], expected[1], eps), `${label}[1] must be close to ${expected[1]}, got ${value[1]}`);\n assert(closeTo(value[2], expected[2], eps), `${label}[2] must be close to ${expected[2]}, got ${value[2]}`);\n}\n\n// q and -q are the same rotation, so compare by the size of the dot product.\nfunction assertQuatClose(value: QuatTuple, expected: QuatTuple, label: string): void {\n const dot = value[0] * expected[0] + value[1] * expected[1] + value[2] * expected[2] + value[3] * expected[3];\n assert(\n closeTo(Math.abs(dot), 1, POSE_EPS),\n `${label} must be close to [${expected.join(\", \")}], got [${value.join(\", \")}]`,\n );\n}\n\nfunction assertPoseClose(value: PoseTuple, expected: PoseTuple, label: string): void {\n assertVec3Close(value.position, expected.position, POSE_EPS, `${label}.position`);\n assertQuatClose(value.quaternion, expected.quaternion, `${label}.quaternion`);\n}\n\nfunction quatLength(q: QuatTuple): number {\n return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"contract-cases.js","sourceRoot":"","sources":["../src/contract-cases.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared `HitTester` and `TransformPort` conformance suites, shipped as data\n * rather than as tests - the same idea as `windowHostContractCases()` in\n * WebXR-UIExtensions and `inputProviderContractCases()` in\n * `@realitycollective/webxr-input`. Five adapters implement these two ports\n * against five different scene graphs; running the same checks against every\n * one is what stops an adapter quietly dropping a documented rule.\n *\n * The suites are runner-free on purpose. Every adapter repository already has\n * its own test runner, and an adapter written outside this repository cannot\n * reach into this one's `test/` folder, so the checks ship as plain objects\n * that throw an `Error` on failure and the adapter iterates them.\n *\n * Both suites assert only what `ports.ts` documents, including two rules\n * every platform now shares:\n * - `getWorldPose()` is the LIVE pose and `getRestWorldPose()` the rest pose\n * offsets are measured from.\n * - Every tuple a port returns is fresh and owned by the caller, and a tuple\n * passed in is not kept.\n */\nimport type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { quatMultiply, vAdd, vApplyQuat } from \"./math.js\";\nimport type { HitTester, HoldRelease, TransformPort } from \"./ports.js\";\n\n// ---------------------------------------------------------------------------\n// HitTester\n// ---------------------------------------------------------------------------\n\n/**\n * Places (or replaces) one target for a case to query against. Build a FRESH\n * subject per case: cases place targets of their own and do not clean up.\n */\nexport interface HitTesterContractDriver {\n /**\n * Register a target at `position` with an interaction radius of `radius`,\n * however the platform registers a target - a mesh, a node, an entity.\n */\n place(id: string, position: Vec3Tuple, radius: number): void;\n /**\n * Register a target at `position` with NO declared radius - a bare node\n * with no geometry of its own - on a platform that can register one\n * without a radius (three.js, XR Blocks, Babylon, IWSDK). `hitProximity`\n * must then treat it as a 0.1 m sphere, `ports.ts`'s documented default.\n * Leave this out on a platform whose registration always takes an\n * explicit radius (native); the case that needs it skips without running.\n */\n placeBare?(id: string, position: Vec3Tuple): void;\n /**\n * Hide, or show again, a placed target using the platform's own ordinary\n * visibility flag alone - three.js `Object3D.visible`, Babylon\n * `isVisible`/`setEnabled`, and so on - never by unregistering it. A hit\n * tester must never answer with a target hidden this way, even on an\n * engine whose own raycast ignores that flag by design (three.js's\n * `Raycaster`; see `ThreeHitTester.hitRay`'s own comment). Leave this out\n * on a platform with no such flag to toggle; the case that needs it skips.\n */\n setVisible?(id: string, visible: boolean): void;\n}\n\n/** The tester under test, plus the means of putting targets into its scene. */\nexport interface HitTesterContractSubject {\n hitTester: HitTester;\n driver: HitTesterContractDriver;\n}\n\n/** One check a {@link HitTester} implementation must pass. */\nexport interface HitTesterContractCase {\n name: string;\n run(subject: HitTesterContractSubject): void;\n}\n\n/**\n * The shared `HitTester` conformance suite. An adapter's test file is a loop:\n *\n * ```ts\n * for (const contractCase of hitTesterContractCases()) {\n * it(contractCase.name, () => contractCase.run(makeSubject()));\n * }\n * ```\n */\nexport function hitTesterContractCases(): readonly HitTesterContractCase[] {\n return HIT_TESTER_CASES;\n}\n\nconst DOWN_RAY: RayTuple = { origin: [0, 0, 0], direction: [0, 0, -1] };\nconst AWAY_RAY: RayTuple = { origin: [0, 0, 0], direction: [0, 0, 1] };\n\n/**\n * A ray-based query only has to land WITHIN the target's own radius:\n * `ports.ts` documents `point` as \"world-space hit or closest point\", so a\n * mesh-accurate ray tester (three.js) reporting the surface it actually hit\n * is as conforming as a sphere tester (Babylon, IWSDK) reporting the target's\n * centre. Placing the target exactly on the ray's line makes both answers\n * the same point when a tester has no radius-shaped slack, and this is the\n * tolerance for testers that do.\n */\n/** A surface distance is exact up to 1 mm, the tolerance the master row states. */\nconst SURFACE_EPS = 1e-3;\n\nfunction degrees(value: number): number {\n return (value * Math.PI) / 180;\n}\n\nfunction slack(radius: number): number {\n return radius + 0.05;\n}\n\nconst HIT_TESTER_CASES: readonly HitTesterContractCase[] = [\n {\n name: \"an empty scene answers a ray with null\",\n run({ hitTester }) {\n assert(hitTester.hitRay(DOWN_RAY) === null, \"hitRay over an empty scene must return null\");\n },\n },\n {\n name: \"a ray through a placed target returns its id, a positive distance and a point near the ray\",\n run({ hitTester, driver }) {\n const radius = 0.15;\n driver.place(\"target\", [0, 0, -2], radius);\n const hit = hitTester.hitRay(DOWN_RAY);\n assert(hit !== null, \"a ray through a placed target must not miss\");\n assert(hit.interactableId === \"target\", `expected interactableId \"target\", got \"${hit.interactableId}\"`);\n assert(hit.distance > 0, `distance must be positive, got ${hit.distance}`);\n const eps = slack(radius);\n assert(closeTo(hit.distance, 2, eps), `distance must be close to 2, got ${hit.distance}`);\n assertVec3Close(hit.point, [0, 0, -2], eps, \"point\");\n },\n },\n {\n name: \"a ray pointing away from the target returns null\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.15);\n assert(hitTester.hitRay(AWAY_RAY) === null, \"a ray pointing away from every target must return null\");\n },\n },\n {\n name: \"hitProximity finds a target inside its radius and misses outside it\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.1);\n const inside = hitTester.hitProximity([0, 0, -2], 0.5);\n assert(inside !== null, \"hitProximity at the target's own position must find it\");\n assert(\n inside.interactableId === \"target\",\n `expected interactableId \"target\", got \"${inside.interactableId}\"`,\n );\n const outside = hitTester.hitProximity([5, 5, 5], 0.05);\n assert(outside === null, \"hitProximity far from every target must return null\");\n },\n },\n {\n name: \"hitProximity reports the distance to the target's SURFACE, clamped at zero\",\n run({ hitTester, driver }) {\n // The master row: a point 3 cm outside a 10 cm target reports 0.03, not\n // 0.13. A tester that reports the distance to the centre removes every\n // near interaction, because no fingertip is ever within 5 cm of a\n // target's centre.\n driver.place(\"target\", [0, 0, -2], 0.1);\n const outside = hitTester.hitProximity([0.13, 0, -2], 0.5);\n assert(outside !== null, \"a point 3 cm outside a 10 cm target must be found within 0.5 m\");\n assert(\n closeTo(outside.distance, 0.03, SURFACE_EPS),\n `distance must be to the surface, 0.03 within 1 mm, got ${outside.distance}`,\n );\n const inside = hitTester.hitProximity([0.02, 0, -2], 0.5);\n assert(inside !== null && inside.distance === 0, `a point inside the target is at distance 0, got ${inside?.distance}`);\n },\n },\n {\n name: \"hitProximity's point is the surface point nearest the query point, never the centre\",\n run({ hitTester, driver }) {\n // The touch cursor sits at this point (IWSDK's sphere intersector\n // reports the point on the mesh). A host that answered with the centre\n // put the cursor inside the object (Pale Signal handover, G7).\n driver.place(\"target\", [0, 0, -2], 0.1);\n const hit = hitTester.hitProximity([0.13, 0, -2], 0.5);\n assert(hit !== null, \"a point 3 cm outside a 10 cm target must be found within 0.5 m\");\n assertVec3Close(hit.point, [0.1, 0, -2], 0.02, \"the proximity point\");\n const above = hitTester.hitProximity([0, 0.15, -2], 0.5);\n assert(above !== null, \"a point 5 cm above the target must be found\");\n assertVec3Close(above.point, [0, 0.1, -2], 0.02, \"the proximity point from above\");\n },\n },\n {\n name: \"hitProximity misses a target whose surface is farther than the query radius\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.1);\n assert(\n hitTester.hitProximity([0.13, 0, -2], 0.02) === null,\n \"a surface 3 cm away is outside a 2 cm query and must not be found\",\n );\n assert(\n hitTester.hitProximity([0.13, 0, -2], 0.04) !== null,\n \"a surface 3 cm away is inside a 4 cm query and must be found\",\n );\n },\n },\n {\n name: \"a ray that passes a target farther than its radius misses it\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0.5, 0, -2], 0.1);\n assert(hitTester.hitRay(DOWN_RAY) === null, \"a ray 0.5 m from a 0.1 m target must miss it\");\n },\n },\n {\n name: \"with two targets on one ray, the nearer one wins\",\n run({ hitTester, driver }) {\n const radius = 0.1;\n driver.place(\"far\", [0, 0, -3], radius);\n driver.place(\"near\", [0, 0, -1], radius);\n const hit = hitTester.hitRay(DOWN_RAY);\n assert(hit !== null, \"a ray through two targets must not miss\");\n assert(hit.interactableId === \"near\", `the nearer target must win, got \"${hit.interactableId}\"`);\n assert(closeTo(hit.distance, 1, slack(radius)), `distance must be close to 1, got ${hit.distance}`);\n },\n },\n {\n name: \"hitProximity treats a target with no declared radius as a 0.1 m sphere, when the driver can register one\",\n run({ hitTester, driver }) {\n if (!driver.placeBare) return;\n driver.placeBare(\"bare\", [0, 0, -2]);\n // 0.12 m from the centre is 0.02 m outside a 0.1 m default radius -\n // found within a 0.05 m query, and NOT the 0.12 m a zero-radius\n // reading would report.\n const found = hitTester.hitProximity([0.12, 0, -2], 0.05);\n assert(\n found !== null,\n \"a point 0.12 m from a bare target's centre must be found within a 0.05 m query, given the 0.1 m default radius\",\n );\n assert(\n closeTo(found.distance, 0.02, SURFACE_EPS),\n `distance must be to the default 0.1 m surface, 0.02 within 1 mm, got ${found.distance}`,\n );\n },\n },\n {\n name: \"an object hidden by visible alone is never hit, when the driver can toggle it\",\n run({ hitTester, driver }) {\n if (!driver.setVisible) return;\n driver.place(\"target\", [0, 0, -2], 0.15);\n driver.setVisible(\"target\", false);\n assert(\n hitTester.hitRay(DOWN_RAY) === null,\n \"a ray must not hit a target hidden by visible alone\",\n );\n assert(\n hitTester.hitProximity([0, 0, -2], 0.5) === null,\n \"hitProximity must not find a target hidden by visible alone\",\n );\n driver.setVisible(\"target\", true);\n assert(\n hitTester.hitRay(DOWN_RAY) !== null,\n \"showing the target again must restore the hit\",\n );\n },\n },\n {\n name: \"each hit and its point are fresh values the caller owns\",\n run({ hitTester, driver }) {\n driver.place(\"target\", [0, 0, -2], 0.15);\n for (const [label, query] of [\n [\"hitRay\", () => hitTester.hitRay(DOWN_RAY)],\n [\"hitProximity\", () => hitTester.hitProximity([0, 0, -2], 0.5)],\n ] as const) {\n const first = query();\n const second = query();\n assert(first !== null && second !== null, `${label} must find the placed target`);\n assert(second !== first && second.point !== first.point, `${label} must return a new hit and point each call`);\n const expected = first.point[2];\n first.point[2] = 1e6;\n const third = query();\n assert(\n third !== null && closeTo(third.point[2], expected, POSE_EPS),\n `writing to a returned point must not change the next ${label} answer`,\n );\n }\n },\n },\n {\n name: \"hitCone, when present, answers a ray that reaches a target with that target at angular distance zero\",\n run({ hitTester, driver }) {\n if (!hitTester.hitCone) return;\n driver.place(\"cone-direct\", [0, 0, -2], 0.1);\n const hit = hitTester.hitCone(DOWN_RAY, degrees(5), 30);\n assert(hit !== null, \"hitCone answered null for a target on the ray\");\n assert(hit.interactableId === \"cone-direct\", `hitCone named \"${hit.interactableId}\", expected \"cone-direct\"`);\n assert(hit.distance > 0 && hit.distance <= 2 + slack(0.1), `hitCone reported distance ${String(hit.distance)}, expected about 2`);\n assert(dist(hit.point, [0, 0, -2]) <= slack(0.1), \"hitCone's point is not on the target\");\n },\n },\n {\n name: \"hitCone, when present, finds a target inside the cone that the ray itself misses\",\n run({ hitTester, driver }) {\n if (!hitTester.hitCone) return;\n // 2 m ahead and 0.2 m to the side: its silhouette (radius 0.1) is about\n // 2.9 degrees off axis, inside a 5 degree cone, outside the ray.\n driver.place(\"cone-near\", [0.2, 0, -2], 0.1);\n assert(hitTester.hitRay(DOWN_RAY) === null, \"the ray itself must miss the offset target\");\n const hit = hitTester.hitCone(DOWN_RAY, degrees(5), 30);\n assert(hit !== null && hit.interactableId === \"cone-near\", \"hitCone did not find the target inside the cone\");\n assert(dist(hit.point, [0.2, 0, -2]) <= slack(0.1), \"hitCone's point is not on the target\");\n },\n },\n {\n name: \"hitCone, when present, ignores a target outside the cone and one beyond the length\",\n run({ hitTester, driver }) {\n if (!hitTester.hitCone) return;\n driver.place(\"cone-wide\", [0.5, 0, -2], 0.1);\n driver.place(\"cone-far\", [0, 0, -40], 0.1);\n assert(hitTester.hitCone(DOWN_RAY, degrees(5), 30) === null, \"hitCone answered with a target outside the cone or beyond the length\");\n },\n },\n {\n name: \"hitCone, when present, prefers the target nearer the axis, and the nearer of two on the axis\",\n run({ hitTester, driver }) {\n if (!hitTester.hitCone) return;\n driver.place(\"cone-off\", [0.15, 0, -2], 0.05);\n driver.place(\"cone-on\", [0.05, 0, -3], 0.05);\n const byAngle = hitTester.hitCone(DOWN_RAY, degrees(8), 30);\n assert(byAngle !== null && byAngle.interactableId === \"cone-on\", \"hitCone did not prefer the target nearer the axis\");\n driver.place(\"cone-nearer\", [0, 0, -1], 0.05);\n const nearer = hitTester.hitCone(DOWN_RAY, degrees(8), 30);\n assert(nearer !== null && nearer.interactableId === \"cone-nearer\", \"with two targets on the axis, hitCone did not prefer the nearer\");\n },\n },\n {\n name: \"hitCone, when present, never answers with a target hidden by visible alone, when the driver can toggle it\",\n run({ hitTester, driver }) {\n if (!hitTester.hitCone || !driver.setVisible) return;\n driver.place(\"cone-hidden\", [0, 0, -2], 0.1);\n driver.setVisible(\"cone-hidden\", false);\n assert(hitTester.hitCone(DOWN_RAY, degrees(5), 30) === null, \"hitCone answered with a hidden target\");\n driver.setVisible(\"cone-hidden\", true);\n assert(hitTester.hitCone(DOWN_RAY, degrees(5), 30)?.interactableId === \"cone-hidden\", \"hitCone lost the target after it was shown again\");\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// TransformPort\n// ---------------------------------------------------------------------------\n\n/**\n * Advances a subject's physics simulation - a real engine step where a\n * platform's physics can run in a test, otherwise a fake that faithfully\n * reproduces the one behaviour these cases check: gravity moves the object\n * unless held, and a release velocity carries it.\n */\nexport interface TransformPortPhysicsDriver {\n step(dtSeconds: number): void;\n}\n\n/** The port under test, over one object. Build a FRESH one per case. */\nexport interface TransformPortContractSubject {\n port: TransformPort;\n /**\n * The world pose the object sat at when the port captured its rest. Place\n * it away from the origin and turned, under no parent or an unscaled one,\n * so a port that ignores its rest frame cannot pass by accident.\n */\n rest: PoseTuple;\n /**\n * Present only when the object has a physics body - the same condition\n * that gives the port `beginHold`/`endHold`. The held/released/reset\n * cases below skip without one, since there is nothing for them to check.\n */\n physics?: TransformPortPhysicsDriver;\n}\n\n/** One check a {@link TransformPort} implementation must pass. */\nexport interface TransformPortContractCase {\n name: string;\n run(subject: TransformPortContractSubject): void;\n}\n\n/**\n * The shared `TransformPort` conformance suite. An adapter's test file is a\n * loop, the same shape as {@link hitTesterContractCases}.\n */\nexport function transformPortContractCases(): readonly TransformPortContractCase[] {\n return TRANSFORM_PORT_CASES;\n}\n\nconst POSE_EPS = 1e-3;\n// A quarter turn about Y - a non-identity unit quaternion, so the case\n// cannot pass by accident on a port that ignores its argument.\nconst QUARTER_TURN_Y: QuatTuple = [0, Math.SQRT1_2, 0, Math.SQRT1_2];\n\n// ---------------------------------------------------------------------------\n// Held / released / reset - the \"held pose\" rule, checked only when the\n// subject carries a physics driver. One fixed step size and a looser\n// position tolerance than POSE_EPS: real engines differ slightly in their\n// integrator, and a held/reset case allows for up to one step of gravity\n// (see PHYSICS_POSE_EPS's own comment), where the plain pose cases above\n// never touch physics at all.\n// ---------------------------------------------------------------------------\n\nconst PHYSICS_DT = 1 / 60;\nconst HELD_STEPS = 5;\n/** metres/second - small enough that one step's drift is easy to read, large enough not to vanish into PHYSICS_POSE_EPS. */\nconst RELEASE_SPEED = 2;\n/** metres/second - about 25x RELEASE_SPEED, so \"kept the old velocity\" and \"one step of gravity\" cannot be confused. */\nconst LARGE_PRIOR_SPEED = 50;\n/**\n * 2 cm. One step of gravity at PHYSICS_DT droops roughly\n * `0.5 * 9.8 * PHYSICS_DT^2` ≈ 1.4 mm, well inside this; a full step of\n * LARGE_PRIOR_SPEED (≈ 0.83 m) or RELEASE_SPEED (≈ 33 mm) is well outside it.\n */\nconst PHYSICS_POSE_EPS = 0.02;\n\nfunction assertPhysicsPoseClose(value: PoseTuple, expected: PoseTuple, label: string): void {\n assertVec3Close(value.position, expected.position, PHYSICS_POSE_EPS, `${label}.position`);\n assertQuatClose(value.quaternion, expected.quaternion, `${label}.quaternion`);\n}\n\nconst TRANSFORM_PORT_CASES: readonly TransformPortContractCase[] = [\n {\n name: \"setLocalOffset then getLocalOffset returns the same offset\",\n run({ port }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n assertVec3Close(port.getLocalOffset(), offset, POSE_EPS, \"getLocalOffset()\");\n },\n },\n {\n name: \"getWorldPose returns finite numbers and a unit quaternion\",\n run({ port }) {\n const pose = port.getWorldPose();\n for (const value of [...pose.position, ...pose.quaternion]) {\n assert(Number.isFinite(value), `getWorldPose() must return finite numbers, got ${value}`);\n }\n const length = quatLength(pose.quaternion);\n assert(\n closeTo(length, 1, POSE_EPS),\n `getWorldPose().quaternion must be a unit quaternion, length was ${length}`,\n );\n },\n },\n {\n name: \"setLocalRotation with a unit quaternion does not throw\",\n run({ port }) {\n try {\n port.setLocalRotation(QUARTER_TURN_Y);\n } catch (error) {\n throw new Error(`setLocalRotation(unit quaternion) must not throw, it threw: ${String(error)}`);\n }\n },\n },\n {\n name: \"getRestWorldPose returns the pose captured at registration\",\n run({ port, rest }) {\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose()\");\n },\n },\n {\n name: \"before any write, the live pose is the rest pose\",\n run({ port, rest }) {\n assertPoseClose(port.getWorldPose(), rest, \"getWorldPose()\");\n },\n },\n {\n name: \"getWorldPose follows setLocalOffset while getRestWorldPose stays put\",\n run({ port, rest }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n assertVec3Close(\n port.getWorldPose().position,\n vAdd(rest.position, vApplyQuat(offset, rest.quaternion)),\n POSE_EPS,\n \"getWorldPose().position after setLocalOffset\",\n );\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setLocalOffset\");\n },\n },\n {\n name: \"getWorldPose follows setLocalRotation while getRestWorldPose stays put\",\n run({ port, rest }) {\n port.setLocalRotation(QUARTER_TURN_Y);\n assertQuatClose(\n port.getWorldPose().quaternion,\n quatMultiply(rest.quaternion, QUARTER_TURN_Y),\n \"getWorldPose().quaternion after setLocalRotation\",\n );\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setLocalRotation\");\n },\n },\n {\n name: \"setWorldPose, when present, is what getWorldPose reads back\",\n run({ port, rest }) {\n if (!port.setWorldPose) return;\n const target: PoseTuple = { position: [0.5, 1.2, -0.4], quaternion: QUARTER_TURN_Y };\n port.setWorldPose(target);\n assertPoseClose(port.getWorldPose(), target, \"getWorldPose() after setWorldPose\");\n assertPoseClose(port.getRestWorldPose(), rest, \"getRestWorldPose() after setWorldPose\");\n },\n },\n {\n name: \"every returned tuple is a fresh value the caller owns\",\n run({ port }) {\n const pose = port.getWorldPose();\n const restPose = port.getRestWorldPose();\n const offset = port.getLocalOffset();\n pose.position[0] = 1e6;\n pose.quaternion[3] = 1e6;\n restPose.position[0] = 1e6;\n offset[0] = 1e6;\n assert(port.getWorldPose().position[0] !== 1e6, \"writing to a returned getWorldPose() tuple must not change the port\");\n assert(port.getWorldPose().quaternion[3] !== 1e6, \"writing to a returned getWorldPose() quaternion must not change the port\");\n assert(port.getRestWorldPose().position[0] !== 1e6, \"writing to a returned getRestWorldPose() tuple must not change the port\");\n assert(port.getLocalOffset()[0] !== 1e6, \"writing to a returned getLocalOffset() tuple must not change the port\");\n },\n },\n {\n name: \"a tuple passed in is not kept\",\n run({ port }) {\n const offset: Vec3Tuple = [0.3, -0.1, 0.05];\n port.setLocalOffset(offset);\n offset[0] = 9;\n assertVec3Close(port.getLocalOffset(), [0.3, -0.1, 0.05], POSE_EPS, \"getLocalOffset() after the caller reused its tuple\");\n if (!port.setWorldPose) return;\n const target: PoseTuple = { position: [0.5, 1.2, -0.4], quaternion: [0, 0, 0, 1] };\n port.setWorldPose(target);\n target.position[0] = 9;\n assertVec3Close(port.getWorldPose().position, [0.5, 1.2, -0.4], POSE_EPS, \"getWorldPose() after the caller reused its tuple\");\n },\n },\n {\n name: \"setEffect, when present, accepts scale and emissive without throwing\",\n run({ port }) {\n if (!port.setEffect) return;\n try {\n port.setEffect({ scale: 1.2, emissive: 0.3 });\n } catch (error) {\n throw new Error(`setEffect({ scale, emissive }) must not throw, it threw: ${String(error)}`);\n }\n },\n },\n {\n name: \"Held: beginHold holds a fixed setWorldPose without falling, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.setWorldPose || !port.beginHold) return;\n const held: PoseTuple = {\n position: [rest.position[0], rest.position[1] + 1, rest.position[2]],\n quaternion: rest.quaternion,\n };\n port.beginHold();\n for (let i = 0; i < HELD_STEPS; i++) {\n port.setWorldPose(held);\n physics.step(PHYSICS_DT);\n }\n // A tight tolerance, not PHYSICS_POSE_EPS: a genuinely held body sees\n // no simulation at all, so it matches the written pose exactly bar\n // floating point noise - the same POSE_EPS the non-physics cases\n // above use. One step of unwanted gravity (~2.7 mm at PHYSICS_DT) is\n // what this case exists to catch, and it must not hide inside the\n // tolerance the way it deliberately may in the Reset case below.\n assertPoseClose(port.getWorldPose(), held, `getWorldPose() after ${HELD_STEPS} held steps`);\n },\n },\n {\n name: \"Released: endHold sends the body away with the release velocity, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.beginHold || !port.endHold) return;\n port.beginHold();\n const release: HoldRelease = { linearVelocity: [RELEASE_SPEED, 0, 0], angularVelocity: [0, 0, 0] };\n port.endHold(release);\n physics.step(PHYSICS_DT);\n const moved = port.getWorldPose().position[0] - rest.position[0];\n assert(\n moved > RELEASE_SPEED * PHYSICS_DT * 0.5,\n `endHold({ linearVelocity: [${RELEASE_SPEED}, 0, 0] }) must move the body that way within one step, moved ${moved}`,\n );\n },\n },\n {\n name: \"Reset: setWorldPose while not held teleports to the pose and clears velocity, when the subject has physics\",\n run({ port, rest, physics }) {\n if (!physics || !port.setWorldPose || !port.beginHold || !port.endHold) return;\n // Give the body a large prior velocity and let it run for a step, so\n // carrying that velocity through the reset below is unmistakable -\n // one step of it (~0.83 m) dwarfs PHYSICS_POSE_EPS (2 cm).\n port.beginHold();\n const priorRelease: HoldRelease = { linearVelocity: [LARGE_PRIOR_SPEED, 0, 0], angularVelocity: [0, 0, 0] };\n port.endHold(priorRelease);\n physics.step(PHYSICS_DT);\n const target: PoseTuple = {\n position: [rest.position[0] - 1, rest.position[1] + 2, rest.position[2] + 0.5],\n quaternion: rest.quaternion,\n };\n port.setWorldPose(target);\n // One further step: it must be at `target`, not carrying the prior\n // velocity forward - allowing for this one step's worth of gravity.\n physics.step(PHYSICS_DT);\n assertPhysicsPoseClose(port.getWorldPose(), target, \"getWorldPose() one step after a reset setWorldPose\");\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Shared assertion helpers\n// ---------------------------------------------------------------------------\n\nfunction assert(condition: boolean, message: string): asserts condition {\n if (!condition) throw new Error(message);\n}\n\nfunction dist(a: Vec3Tuple, b: Vec3Tuple): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n}\n\nfunction closeTo(a: number, b: number, eps: number): boolean {\n return Math.abs(a - b) <= eps;\n}\n\n// Indexed one component at a time, rather than in a loop, so each index is a\n// literal: `noUncheckedIndexedAccess` cannot narrow a tuple read through a\n// variable index back to `number`.\nfunction assertVec3Close(value: Vec3Tuple, expected: Vec3Tuple, eps: number, label: string): void {\n assert(closeTo(value[0], expected[0], eps), `${label}[0] must be close to ${expected[0]}, got ${value[0]}`);\n assert(closeTo(value[1], expected[1], eps), `${label}[1] must be close to ${expected[1]}, got ${value[1]}`);\n assert(closeTo(value[2], expected[2], eps), `${label}[2] must be close to ${expected[2]}, got ${value[2]}`);\n}\n\n// q and -q are the same rotation, so compare by the size of the dot product.\nfunction assertQuatClose(value: QuatTuple, expected: QuatTuple, label: string): void {\n const dot = value[0] * expected[0] + value[1] * expected[1] + value[2] * expected[2] + value[3] * expected[3];\n assert(\n closeTo(Math.abs(dot), 1, POSE_EPS),\n `${label} must be close to [${expected.join(\", \")}], got [${value.join(\", \")}]`,\n );\n}\n\nfunction assertPoseClose(value: PoseTuple, expected: PoseTuple, label: string): void {\n assertVec3Close(value.position, expected.position, POSE_EPS, `${label}.position`);\n assertQuatClose(value.quaternion, expected.quaternion, `${label}.quaternion`);\n}\n\nfunction quatLength(q: QuatTuple): number {\n return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);\n}\n"]}
|
package/dist/descriptor.d.ts
CHANGED
|
@@ -13,7 +13,13 @@ export interface InteractableDescriptor {
|
|
|
13
13
|
gaze?: GazeConfig;
|
|
14
14
|
/** Registered disabled when false (enable later via the runtime). */
|
|
15
15
|
enabled?: boolean;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The touch hover ENTER distance for this target, metres to its surface:
|
|
18
|
+
* a fingertip within it makes touch the active pointer (default 0.15,
|
|
19
|
+
* IWSDK's `enterHoverDistance`; the exit distance is 0.05 beyond it). The
|
|
20
|
+
* press distance (0.02) is the runtime's `nearPointer` option, not per
|
|
21
|
+
* target. See `near-pointer.ts`.
|
|
22
|
+
*/
|
|
17
23
|
pokeRadius?: number;
|
|
18
24
|
}
|
|
19
25
|
export interface InteractionDescriptor {
|
package/dist/descriptor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../src/descriptor.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * InteractionDescriptor - the portable scene-interaction format. Plain\n * JSON-able data: which interactables exist, which behaviours they carry\n * (with tuning), and their gaze policy. Every adapter builds the SAME\n * playground from the same descriptor - the portability proof inherited\n * from the UI Extensions' SceneDescriptor.\n */\nimport type { BehaviourConfig } from \"./behaviours/factory.js\";\nimport type { GazeConfig } from \"./gaze.js\";\n\nexport interface InteractableDescriptor {\n id: string;\n behaviours: BehaviourConfig[];\n gaze?: GazeConfig;\n /** Registered disabled when false (enable later via the runtime). */\n enabled?: boolean;\n
|
|
1
|
+
{"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../src/descriptor.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * InteractionDescriptor - the portable scene-interaction format. Plain\n * JSON-able data: which interactables exist, which behaviours they carry\n * (with tuning), and their gaze policy. Every adapter builds the SAME\n * playground from the same descriptor - the portability proof inherited\n * from the UI Extensions' SceneDescriptor.\n */\nimport type { BehaviourConfig } from \"./behaviours/factory.js\";\nimport type { GazeConfig } from \"./gaze.js\";\n\nexport interface InteractableDescriptor {\n id: string;\n behaviours: BehaviourConfig[];\n gaze?: GazeConfig;\n /** Registered disabled when false (enable later via the runtime). */\n enabled?: boolean;\n /**\n * The touch hover ENTER distance for this target, metres to its surface:\n * a fingertip within it makes touch the active pointer (default 0.15,\n * IWSDK's `enterHoverDistance`; the exit distance is 0.05 beyond it). The\n * press distance (0.02) is the runtime's `nearPointer` option, not per\n * target. See `near-pointer.ts`.\n */\n pokeRadius?: number;\n}\n\nexport interface InteractionDescriptor {\n interactables: InteractableDescriptor[];\n}\n"]}
|
package/dist/gaze.d.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* The dwell state machine is pure and headless-tested. The runtime feeds it
|
|
12
12
|
* hover booleans; it never sees the engine.
|
|
13
13
|
*/
|
|
14
|
+
import type { RayTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
15
|
+
import type { InteractableHit } from "./ports.js";
|
|
14
16
|
export interface DwellConfig {
|
|
15
17
|
/** Seconds of sustained gaze to fire. */
|
|
16
18
|
holdSeconds?: number;
|
|
@@ -42,3 +44,51 @@ export declare class DwellState {
|
|
|
42
44
|
update(hovered: boolean, dt: number, config: Required<DwellConfig>): DwellTick;
|
|
43
45
|
getProgress(): number;
|
|
44
46
|
}
|
|
47
|
+
/** Comparer tolerance: angular distances within this are "the same", and distance decides. IWSDK `ANGLE_EPSILON`. */
|
|
48
|
+
export declare const GAZE_CONE_ANGLE_EPSILON: number;
|
|
49
|
+
/** A cone hit: {@link InteractableHit} plus how far off the gaze axis the target's silhouette sat, radians (0 when the ray itself hit). */
|
|
50
|
+
export interface GazeConeHit extends InteractableHit {
|
|
51
|
+
angularDistance: number;
|
|
52
|
+
}
|
|
53
|
+
/** A sphere target, as the approximate hit testers and the native host model one. */
|
|
54
|
+
export interface SphereTarget {
|
|
55
|
+
id: string;
|
|
56
|
+
center: Vec3Tuple;
|
|
57
|
+
radius: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* IWSDK's `GazeConecaster.findFrameBest` over sphere targets: the best
|
|
61
|
+
* target inside a cone of `halfAngle` radians about `ray`, no farther than
|
|
62
|
+
* `maxLength`. A sphere the ray passes through is hit where the ray enters
|
|
63
|
+
* it, with an angular distance of 0. Otherwise the angle from the ray to the
|
|
64
|
+
* sphere's silhouette (the angle to its centre less the angle it subtends)
|
|
65
|
+
* must be within the cone, and the point aimed at is the point of the sphere
|
|
66
|
+
* nearest the ray. Closer by angle wins; within {@link GAZE_CONE_ANGLE_EPSILON}
|
|
67
|
+
* the nearer target wins. A sphere the origin is inside counts as on axis.
|
|
68
|
+
*/
|
|
69
|
+
export declare function coneHitForSpheres(ray: RayTuple, targets: Iterable<SphereTarget>, halfAngle: number, maxLength: number): GazeConeHit | null;
|
|
70
|
+
/** IWSDK's default comparer: closer by angle, then closer by distance. Negative when `a` is better. */
|
|
71
|
+
export declare function compareConeHits(a: GazeConeHit, b: GazeConeHit): number;
|
|
72
|
+
/**
|
|
73
|
+
* IWSDK's dwell consensus (`GazeConecaster.recordDwell` / `pruneDwell` /
|
|
74
|
+
* `electDwellWinner` / `resolveDwellWinner`): each frame's best candidate is
|
|
75
|
+
* recorded with its `dt`; samples older than the window are dropped; the
|
|
76
|
+
* candidate with the largest cumulative `dt` inside the window wins. A winner
|
|
77
|
+
* that is no longer available (unregistered, disabled, hidden) is struck
|
|
78
|
+
* from the record and the next is elected. A window of 0 keeps only this
|
|
79
|
+
* frame's sample, so the frame's best wins outright.
|
|
80
|
+
*/
|
|
81
|
+
export declare class GazeConsensus {
|
|
82
|
+
windowSeconds: number;
|
|
83
|
+
private readonly samples;
|
|
84
|
+
private time;
|
|
85
|
+
constructor(windowSeconds?: number);
|
|
86
|
+
/** Record this frame's best (or none), advance the window and elect. */
|
|
87
|
+
update(frameBestId: string | null, dt: number, available: (id: string) => boolean): string | null;
|
|
88
|
+
/** Advance the window with nothing recorded (a frame gaze was suppressed or had no pose). */
|
|
89
|
+
skip(dt: number): void;
|
|
90
|
+
reset(): void;
|
|
91
|
+
private prune;
|
|
92
|
+
private elect;
|
|
93
|
+
private remove;
|
|
94
|
+
}
|
package/dist/gaze.js
CHANGED
|
@@ -58,4 +58,166 @@ export class DwellState {
|
|
|
58
58
|
return this.progress;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Eye gaze: the targeting half of the rule `@realitycollective/webxr-input`'s
|
|
63
|
+
// `eye-gaze.ts` states. IWSDK's `GazeConecaster` picks the frame's best
|
|
64
|
+
// in-cone candidate and then holds a time-weighted vote over the dwell
|
|
65
|
+
// window, so a flick across a neighbour does not steal hover.
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/** Comparer tolerance: angular distances within this are "the same", and distance decides. IWSDK `ANGLE_EPSILON`. */
|
|
68
|
+
export const GAZE_CONE_ANGLE_EPSILON = 0.5 * (Math.PI / 180);
|
|
69
|
+
/**
|
|
70
|
+
* IWSDK's `GazeConecaster.findFrameBest` over sphere targets: the best
|
|
71
|
+
* target inside a cone of `halfAngle` radians about `ray`, no farther than
|
|
72
|
+
* `maxLength`. A sphere the ray passes through is hit where the ray enters
|
|
73
|
+
* it, with an angular distance of 0. Otherwise the angle from the ray to the
|
|
74
|
+
* sphere's silhouette (the angle to its centre less the angle it subtends)
|
|
75
|
+
* must be within the cone, and the point aimed at is the point of the sphere
|
|
76
|
+
* nearest the ray. Closer by angle wins; within {@link GAZE_CONE_ANGLE_EPSILON}
|
|
77
|
+
* the nearer target wins. A sphere the origin is inside counts as on axis.
|
|
78
|
+
*/
|
|
79
|
+
export function coneHitForSpheres(ray, targets, halfAngle, maxLength) {
|
|
80
|
+
const [ox, oy, oz] = ray.origin;
|
|
81
|
+
const [dx, dy, dz] = ray.direction;
|
|
82
|
+
let best = null;
|
|
83
|
+
for (const target of targets) {
|
|
84
|
+
const [cx, cy, cz] = target.center;
|
|
85
|
+
const r = target.radius;
|
|
86
|
+
const vx = cx - ox;
|
|
87
|
+
const vy = cy - oy;
|
|
88
|
+
const vz = cz - oz;
|
|
89
|
+
const dist = Math.hypot(vx, vy, vz);
|
|
90
|
+
if (dist - r > maxLength)
|
|
91
|
+
continue;
|
|
92
|
+
let hit;
|
|
93
|
+
if (dist <= r) {
|
|
94
|
+
// The origin is inside the sphere: fully on axis, aimed at the centre.
|
|
95
|
+
hit = { interactableId: target.id, distance: dist, point: [cx, cy, cz], angularDistance: 0 };
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const t = vx * dx + vy * dy + vz * dz;
|
|
99
|
+
const missSq = Math.max(0, dist * dist - t * t);
|
|
100
|
+
const miss = Math.sqrt(missSq);
|
|
101
|
+
if (t > 0 && miss <= r) {
|
|
102
|
+
// The ray enters the sphere: the raw ray wins, at the entry point.
|
|
103
|
+
const entry = t - Math.sqrt(r * r - missSq);
|
|
104
|
+
if (entry > maxLength)
|
|
105
|
+
continue;
|
|
106
|
+
hit = {
|
|
107
|
+
interactableId: target.id,
|
|
108
|
+
distance: entry,
|
|
109
|
+
point: [ox + dx * entry, oy + dy * entry, oz + dz * entry],
|
|
110
|
+
angularDistance: 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const toCentre = Math.atan2(miss, t);
|
|
115
|
+
const angular = Math.max(0, toCentre - Math.asin(Math.min(1, r / dist)));
|
|
116
|
+
if (angular > halfAngle)
|
|
117
|
+
continue;
|
|
118
|
+
// The point of the sphere nearest the ray: from the centre toward the
|
|
119
|
+
// ray's closest point (never behind the origin), out to the surface.
|
|
120
|
+
const tc = Math.max(0, t);
|
|
121
|
+
let nx = ox + dx * tc - cx;
|
|
122
|
+
let ny = oy + dy * tc - cy;
|
|
123
|
+
let nz = oz + dz * tc - cz;
|
|
124
|
+
const nl = Math.hypot(nx, ny, nz);
|
|
125
|
+
if (nl > 1e-9) {
|
|
126
|
+
nx /= nl;
|
|
127
|
+
ny /= nl;
|
|
128
|
+
nz /= nl;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
nx = -vx / dist;
|
|
132
|
+
ny = -vy / dist;
|
|
133
|
+
nz = -vz / dist;
|
|
134
|
+
}
|
|
135
|
+
const point = [cx + nx * r, cy + ny * r, cz + nz * r];
|
|
136
|
+
const distance = Math.hypot(point[0] - ox, point[1] - oy, point[2] - oz);
|
|
137
|
+
if (distance > maxLength)
|
|
138
|
+
continue;
|
|
139
|
+
hit = { interactableId: target.id, distance, point, angularDistance: angular };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (best === null || compareConeHits(hit, best) < 0)
|
|
143
|
+
best = hit;
|
|
144
|
+
}
|
|
145
|
+
return best;
|
|
146
|
+
}
|
|
147
|
+
/** IWSDK's default comparer: closer by angle, then closer by distance. Negative when `a` is better. */
|
|
148
|
+
export function compareConeHits(a, b) {
|
|
149
|
+
const dAngle = a.angularDistance - b.angularDistance;
|
|
150
|
+
if (Math.abs(dAngle) > GAZE_CONE_ANGLE_EPSILON)
|
|
151
|
+
return dAngle;
|
|
152
|
+
return a.distance - b.distance;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* IWSDK's dwell consensus (`GazeConecaster.recordDwell` / `pruneDwell` /
|
|
156
|
+
* `electDwellWinner` / `resolveDwellWinner`): each frame's best candidate is
|
|
157
|
+
* recorded with its `dt`; samples older than the window are dropped; the
|
|
158
|
+
* candidate with the largest cumulative `dt` inside the window wins. A winner
|
|
159
|
+
* that is no longer available (unregistered, disabled, hidden) is struck
|
|
160
|
+
* from the record and the next is elected. A window of 0 keeps only this
|
|
161
|
+
* frame's sample, so the frame's best wins outright.
|
|
162
|
+
*/
|
|
163
|
+
export class GazeConsensus {
|
|
164
|
+
windowSeconds;
|
|
165
|
+
samples = [];
|
|
166
|
+
time = 0;
|
|
167
|
+
constructor(windowSeconds = 0.15) {
|
|
168
|
+
this.windowSeconds = windowSeconds;
|
|
169
|
+
}
|
|
170
|
+
/** Record this frame's best (or none), advance the window and elect. */
|
|
171
|
+
update(frameBestId, dt, available) {
|
|
172
|
+
this.time += dt;
|
|
173
|
+
if (frameBestId !== null)
|
|
174
|
+
this.samples.push({ id: frameBestId, time: this.time, dt });
|
|
175
|
+
this.prune();
|
|
176
|
+
for (;;) {
|
|
177
|
+
const elected = this.elect();
|
|
178
|
+
if (elected === null)
|
|
179
|
+
return null;
|
|
180
|
+
if (available(elected))
|
|
181
|
+
return elected;
|
|
182
|
+
this.remove(elected);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Advance the window with nothing recorded (a frame gaze was suppressed or had no pose). */
|
|
186
|
+
skip(dt) {
|
|
187
|
+
this.time += dt;
|
|
188
|
+
}
|
|
189
|
+
reset() {
|
|
190
|
+
this.samples.length = 0;
|
|
191
|
+
}
|
|
192
|
+
prune() {
|
|
193
|
+
const cutoff = this.time - this.windowSeconds;
|
|
194
|
+
let drop = 0;
|
|
195
|
+
while (drop < this.samples.length && this.samples[drop].time < cutoff)
|
|
196
|
+
drop += 1;
|
|
197
|
+
if (drop > 0)
|
|
198
|
+
this.samples.splice(0, drop);
|
|
199
|
+
}
|
|
200
|
+
elect() {
|
|
201
|
+
let bestId = null;
|
|
202
|
+
let bestDt = -1;
|
|
203
|
+
const sums = new Map();
|
|
204
|
+
for (const sample of this.samples)
|
|
205
|
+
sums.set(sample.id, (sums.get(sample.id) ?? 0) + sample.dt);
|
|
206
|
+
// First recorded wins a tie, as IWSDK's scan from the ring head does.
|
|
207
|
+
for (const sample of this.samples) {
|
|
208
|
+
const sum = sums.get(sample.id);
|
|
209
|
+
if (sum > bestDt) {
|
|
210
|
+
bestDt = sum;
|
|
211
|
+
bestId = sample.id;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return bestId;
|
|
215
|
+
}
|
|
216
|
+
remove(id) {
|
|
217
|
+
for (let i = this.samples.length - 1; i >= 0; i--) {
|
|
218
|
+
if (this.samples[i].id === id)
|
|
219
|
+
this.samples.splice(i, 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
61
223
|
//# sourceMappingURL=gaze.js.map
|
package/dist/gaze.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gaze.js","sourceRoot":"","sources":["../src/gaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkBH,MAAM,CAAC,MAAM,cAAc,GAA0B;IACnD,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;CAChB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,MAAyC,EACzC,WAAkC,cAAc;IAEhD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACrC,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW;QACvD,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW;QACvD,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU;KACrD,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,UAAU;IACb,QAAQ,GAAG,CAAC,CAAC;IACb,YAAY,GAAG,CAAC,CAAC;IACjB,KAAK,GAAG,IAAI,CAAC;IAErB,MAAM,CAAC,OAAgB,EAAE,EAAU,EAAE,MAA6B;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC7B,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CACtB,CAAC,EACD,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,WAAW,CAC/D,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;YACrC,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC7D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QAED,MAAM,OAAO,GACX,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI;YACnD,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,CAAC;QACxC,IAAI,OAAO;YAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/C,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACrD,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF","sourcesContent":["/**\n * Gaze support - first-class, per the design decision that gaze serves two\n * roles:\n *\n * 1. **Combination gating** (`gaze.required`): input on an interactable only\n * counts while the viewer is looking at it.\n * 2. **Accessibility** (`gaze.dwell`): looking at an interactable fills a\n * dwell meter; a full meter fires a synthesized press - no hands needed.\n * Consumers render the meter from `dwellProgress` events.\n *\n * The dwell state machine is pure and headless-tested. The runtime feeds it\n * hover booleans; it never sees the engine.\n */\n\nexport interface DwellConfig {\n /** Seconds of sustained gaze to fire. */\n holdSeconds?: number;\n /** Drain speed multiplier when looking away. */\n decayFactor?: number;\n /** After firing, progress must fall below this before it can re-arm. */\n rearmBelow?: number;\n}\n\nexport interface GazeConfig {\n /** Non-gaze input on this interactable only counts while gazed at. */\n required?: boolean;\n /** Enable dwell-to-press. `true` uses defaults. */\n dwell?: boolean | DwellConfig;\n}\n\nexport const DWELL_DEFAULTS: Required<DwellConfig> = {\n holdSeconds: 1.8,\n decayFactor: 2.5,\n rearmBelow: 0.1,\n};\n\nexport function resolveDwellConfig(\n config: boolean | DwellConfig | undefined,\n defaults: Required<DwellConfig> = DWELL_DEFAULTS,\n): Required<DwellConfig> | null {\n if (!config) return null;\n if (config === true) return defaults;\n return {\n holdSeconds: config.holdSeconds ?? defaults.holdSeconds,\n decayFactor: config.decayFactor ?? defaults.decayFactor,\n rearmBelow: config.rearmBelow ?? defaults.rearmBelow,\n };\n}\n\nexport interface DwellTick {\n /** Dwell meter 0..1. */\n progress: number;\n /** Progress moved enough to be worth re-rendering (≥ 1%). */\n changed: boolean;\n /** The meter just completed - fire the synthesized press. */\n fired: boolean;\n}\n\nexport class DwellState {\n private progress = 0;\n private lastReported = 0;\n private armed = true;\n\n update(hovered: boolean, dt: number, config: Required<DwellConfig>): DwellTick {\n const before = this.progress;\n if (hovered) {\n this.progress = Math.min(1, this.progress + dt / config.holdSeconds);\n } else {\n this.progress = Math.max(\n 0,\n this.progress - (dt * config.decayFactor) / config.holdSeconds,\n );\n }\n\n let fired = false;\n if (this.armed && this.progress >= 1) {\n fired = true;\n this.armed = false;\n this.progress = 0;\n } else if (!this.armed && this.progress <= config.rearmBelow) {\n this.armed = true;\n }\n\n const changed =\n Math.abs(this.progress - this.lastReported) >= 0.01 ||\n (this.progress === 0 && before !== 0);\n if (changed) this.lastReported = this.progress;\n return { progress: this.progress, changed, fired };\n }\n\n getProgress(): number {\n return this.progress;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gaze.js","sourceRoot":"","sources":["../src/gaze.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gaze support - first-class, per the design decision that gaze serves two\n * roles:\n *\n * 1. **Combination gating** (`gaze.required`): input on an interactable only\n * counts while the viewer is looking at it.\n * 2. **Accessibility** (`gaze.dwell`): looking at an interactable fills a\n * dwell meter; a full meter fires a synthesized press - no hands needed.\n * Consumers render the meter from `dwellProgress` events.\n *\n * The dwell state machine is pure and headless-tested. The runtime feeds it\n * hover booleans; it never sees the engine.\n */\n\nimport type { RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { InteractableHit } from \"./ports.js\";\n\nexport interface DwellConfig {\n /** Seconds of sustained gaze to fire. */\n holdSeconds?: number;\n /** Drain speed multiplier when looking away. */\n decayFactor?: number;\n /** After firing, progress must fall below this before it can re-arm. */\n rearmBelow?: number;\n}\n\nexport interface GazeConfig {\n /** Non-gaze input on this interactable only counts while gazed at. */\n required?: boolean;\n /** Enable dwell-to-press. `true` uses defaults. */\n dwell?: boolean | DwellConfig;\n}\n\nexport const DWELL_DEFAULTS: Required<DwellConfig> = {\n holdSeconds: 1.8,\n decayFactor: 2.5,\n rearmBelow: 0.1,\n};\n\nexport function resolveDwellConfig(\n config: boolean | DwellConfig | undefined,\n defaults: Required<DwellConfig> = DWELL_DEFAULTS,\n): Required<DwellConfig> | null {\n if (!config) return null;\n if (config === true) return defaults;\n return {\n holdSeconds: config.holdSeconds ?? defaults.holdSeconds,\n decayFactor: config.decayFactor ?? defaults.decayFactor,\n rearmBelow: config.rearmBelow ?? defaults.rearmBelow,\n };\n}\n\nexport interface DwellTick {\n /** Dwell meter 0..1. */\n progress: number;\n /** Progress moved enough to be worth re-rendering (≥ 1%). */\n changed: boolean;\n /** The meter just completed - fire the synthesized press. */\n fired: boolean;\n}\n\nexport class DwellState {\n private progress = 0;\n private lastReported = 0;\n private armed = true;\n\n update(hovered: boolean, dt: number, config: Required<DwellConfig>): DwellTick {\n const before = this.progress;\n if (hovered) {\n this.progress = Math.min(1, this.progress + dt / config.holdSeconds);\n } else {\n this.progress = Math.max(\n 0,\n this.progress - (dt * config.decayFactor) / config.holdSeconds,\n );\n }\n\n let fired = false;\n if (this.armed && this.progress >= 1) {\n fired = true;\n this.armed = false;\n this.progress = 0;\n } else if (!this.armed && this.progress <= config.rearmBelow) {\n this.armed = true;\n }\n\n const changed =\n Math.abs(this.progress - this.lastReported) >= 0.01 ||\n (this.progress === 0 && before !== 0);\n if (changed) this.lastReported = this.progress;\n return { progress: this.progress, changed, fired };\n }\n\n getProgress(): number {\n return this.progress;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Eye gaze: the targeting half of the rule `@realitycollective/webxr-input`'s\n// `eye-gaze.ts` states. IWSDK's `GazeConecaster` picks the frame's best\n// in-cone candidate and then holds a time-weighted vote over the dwell\n// window, so a flick across a neighbour does not steal hover.\n// ---------------------------------------------------------------------------\n\n\n/** Comparer tolerance: angular distances within this are \"the same\", and distance decides. IWSDK `ANGLE_EPSILON`. */\nexport const GAZE_CONE_ANGLE_EPSILON = 0.5 * (Math.PI / 180);\n\n/** A cone hit: {@link InteractableHit} plus how far off the gaze axis the target's silhouette sat, radians (0 when the ray itself hit). */\nexport interface GazeConeHit extends InteractableHit {\n angularDistance: number;\n}\n\n/** A sphere target, as the approximate hit testers and the native host model one. */\nexport interface SphereTarget {\n id: string;\n center: Vec3Tuple;\n radius: number;\n}\n\n/**\n * IWSDK's `GazeConecaster.findFrameBest` over sphere targets: the best\n * target inside a cone of `halfAngle` radians about `ray`, no farther than\n * `maxLength`. A sphere the ray passes through is hit where the ray enters\n * it, with an angular distance of 0. Otherwise the angle from the ray to the\n * sphere's silhouette (the angle to its centre less the angle it subtends)\n * must be within the cone, and the point aimed at is the point of the sphere\n * nearest the ray. Closer by angle wins; within {@link GAZE_CONE_ANGLE_EPSILON}\n * the nearer target wins. A sphere the origin is inside counts as on axis.\n */\nexport function coneHitForSpheres(\n ray: RayTuple,\n targets: Iterable<SphereTarget>,\n halfAngle: number,\n maxLength: number,\n): GazeConeHit | null {\n const [ox, oy, oz] = ray.origin;\n const [dx, dy, dz] = ray.direction;\n let best: GazeConeHit | null = null;\n for (const target of targets) {\n const [cx, cy, cz] = target.center;\n const r = target.radius;\n const vx = cx - ox;\n const vy = cy - oy;\n const vz = cz - oz;\n const dist = Math.hypot(vx, vy, vz);\n if (dist - r > maxLength) continue;\n let hit: GazeConeHit;\n if (dist <= r) {\n // The origin is inside the sphere: fully on axis, aimed at the centre.\n hit = { interactableId: target.id, distance: dist, point: [cx, cy, cz], angularDistance: 0 };\n } else {\n const t = vx * dx + vy * dy + vz * dz;\n const missSq = Math.max(0, dist * dist - t * t);\n const miss = Math.sqrt(missSq);\n if (t > 0 && miss <= r) {\n // The ray enters the sphere: the raw ray wins, at the entry point.\n const entry = t - Math.sqrt(r * r - missSq);\n if (entry > maxLength) continue;\n hit = {\n interactableId: target.id,\n distance: entry,\n point: [ox + dx * entry, oy + dy * entry, oz + dz * entry],\n angularDistance: 0,\n };\n } else {\n const toCentre = Math.atan2(miss, t);\n const angular = Math.max(0, toCentre - Math.asin(Math.min(1, r / dist)));\n if (angular > halfAngle) continue;\n // The point of the sphere nearest the ray: from the centre toward the\n // ray's closest point (never behind the origin), out to the surface.\n const tc = Math.max(0, t);\n let nx = ox + dx * tc - cx;\n let ny = oy + dy * tc - cy;\n let nz = oz + dz * tc - cz;\n const nl = Math.hypot(nx, ny, nz);\n if (nl > 1e-9) {\n nx /= nl;\n ny /= nl;\n nz /= nl;\n } else {\n nx = -vx / dist;\n ny = -vy / dist;\n nz = -vz / dist;\n }\n const point: Vec3Tuple = [cx + nx * r, cy + ny * r, cz + nz * r];\n const distance = Math.hypot(point[0] - ox, point[1] - oy, point[2] - oz);\n if (distance > maxLength) continue;\n hit = { interactableId: target.id, distance, point, angularDistance: angular };\n }\n }\n if (best === null || compareConeHits(hit, best) < 0) best = hit;\n }\n return best;\n}\n\n/** IWSDK's default comparer: closer by angle, then closer by distance. Negative when `a` is better. */\nexport function compareConeHits(a: GazeConeHit, b: GazeConeHit): number {\n const dAngle = a.angularDistance - b.angularDistance;\n if (Math.abs(dAngle) > GAZE_CONE_ANGLE_EPSILON) return dAngle;\n return a.distance - b.distance;\n}\n\n/**\n * IWSDK's dwell consensus (`GazeConecaster.recordDwell` / `pruneDwell` /\n * `electDwellWinner` / `resolveDwellWinner`): each frame's best candidate is\n * recorded with its `dt`; samples older than the window are dropped; the\n * candidate with the largest cumulative `dt` inside the window wins. A winner\n * that is no longer available (unregistered, disabled, hidden) is struck\n * from the record and the next is elected. A window of 0 keeps only this\n * frame's sample, so the frame's best wins outright.\n */\nexport class GazeConsensus {\n private readonly samples: Array<{ id: string; time: number; dt: number }> = [];\n private time = 0;\n\n constructor(public windowSeconds: number = 0.15) {}\n\n /** Record this frame's best (or none), advance the window and elect. */\n update(frameBestId: string | null, dt: number, available: (id: string) => boolean): string | null {\n this.time += dt;\n if (frameBestId !== null) this.samples.push({ id: frameBestId, time: this.time, dt });\n this.prune();\n for (;;) {\n const elected = this.elect();\n if (elected === null) return null;\n if (available(elected)) return elected;\n this.remove(elected);\n }\n }\n\n /** Advance the window with nothing recorded (a frame gaze was suppressed or had no pose). */\n skip(dt: number): void {\n this.time += dt;\n }\n\n reset(): void {\n this.samples.length = 0;\n }\n\n private prune(): void {\n const cutoff = this.time - this.windowSeconds;\n let drop = 0;\n while (drop < this.samples.length && this.samples[drop]!.time < cutoff) drop += 1;\n if (drop > 0) this.samples.splice(0, drop);\n }\n\n private elect(): string | null {\n let bestId: string | null = null;\n let bestDt = -1;\n const sums = new Map<string, number>();\n for (const sample of this.samples) sums.set(sample.id, (sums.get(sample.id) ?? 0) + sample.dt);\n // First recorded wins a tie, as IWSDK's scan from the ring head does.\n for (const sample of this.samples) {\n const sum = sums.get(sample.id)!;\n if (sum > bestDt) {\n bestDt = sum;\n bestId = sample.id;\n }\n }\n return bestId;\n }\n\n private remove(id: string): void {\n for (let i = this.samples.length - 1; i >= 0; i--) {\n if (this.samples[i]!.id === id) this.samples.splice(i, 1);\n }\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,8 +23,17 @@ export * from "./contract-cases.js";
|
|
|
23
23
|
export * from "./gaze.js";
|
|
24
24
|
export * from "./descriptor.js";
|
|
25
25
|
export * from "./velocity-tracker.js";
|
|
26
|
+
export * from "./release-velocity.js";
|
|
26
27
|
export * from "./runtime.js";
|
|
27
28
|
export * from "./pointer-bridge.js";
|
|
29
|
+
export * from "./near-pointer.js";
|
|
30
|
+
export * from "./near-pointer-contract-cases.js";
|
|
31
|
+
export * from "./physics.js";
|
|
32
|
+
export * from "./physics-contract-cases.js";
|
|
33
|
+
export * from "./memory-physics.js";
|
|
34
|
+
export * from "./memory-ports.js";
|
|
35
|
+
export * from "./trace.js";
|
|
36
|
+
export * from "./trace-synthetic.js";
|
|
28
37
|
export * from "./behaviours/behaviour.js";
|
|
29
38
|
export * from "./behaviours/press.js";
|
|
30
39
|
export * from "./behaviours/pulse.js";
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,17 @@ export * from "./contract-cases.js";
|
|
|
23
23
|
export * from "./gaze.js";
|
|
24
24
|
export * from "./descriptor.js";
|
|
25
25
|
export * from "./velocity-tracker.js";
|
|
26
|
+
export * from "./release-velocity.js";
|
|
26
27
|
export * from "./runtime.js";
|
|
27
28
|
export * from "./pointer-bridge.js";
|
|
29
|
+
export * from "./near-pointer.js";
|
|
30
|
+
export * from "./near-pointer-contract-cases.js";
|
|
31
|
+
export * from "./physics.js";
|
|
32
|
+
export * from "./physics-contract-cases.js";
|
|
33
|
+
export * from "./memory-physics.js";
|
|
34
|
+
export * from "./memory-ports.js";
|
|
35
|
+
export * from "./trace.js";
|
|
36
|
+
export * from "./trace-synthetic.js";
|
|
28
37
|
export * from "./behaviours/behaviour.js";
|
|
29
38
|
export * from "./behaviours/press.js";
|
|
30
39
|
export * from "./behaviours/pulse.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,gCAAgC,CAAC;AAE/C,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,gCAAgC,CAAC;AAE/C,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kCAAkC,CAAC;AACjD,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AAErC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC","sourcesContent":["/**\n * @realitycollective/webxr-interactions - the engine-free interaction core.\n *\n * Layers (each strictly on the one below):\n * - behaviours (press, pulse, hinge, dial, slide, grab, tossScore) -\n * pure state machines driven by `update(dt)`;\n * - the runtime/binder - targeting, press/grab transitions, gaze gating\n * + dwell, capability negotiation, events, feedback intents;\n * - the shared input contracts, re-exported from\n * `@realitycollective/webxr-input`.\n *\n * Pair with an engine adapter: `@realitycollective/threejs-interactions`\n * (standalone WebXR + three.js), `@realitycollective/iwsdk-interactions`\n * (Meta IWSDK) or `@realitycollective/xrblocks-interactions` (Google XR\n * Blocks, experimental).\n */\nexport * from \"@realitycollective/webxr-input\";\n\nexport * from \"./math.js\";\nexport * from \"./events.js\";\nexport * from \"./feedback.js\";\nexport * from \"./ports.js\";\nexport * from \"./contract-cases.js\";\nexport * from \"./gaze.js\";\nexport * from \"./descriptor.js\";\nexport * from \"./velocity-tracker.js\";\nexport * from \"./release-velocity.js\";\nexport * from \"./runtime.js\";\nexport * from \"./pointer-bridge.js\";\nexport * from \"./near-pointer.js\";\nexport * from \"./near-pointer-contract-cases.js\";\nexport * from \"./physics.js\";\nexport * from \"./physics-contract-cases.js\";\nexport * from \"./memory-physics.js\";\nexport * from \"./memory-ports.js\";\nexport * from \"./trace.js\";\nexport * from \"./trace-synthetic.js\";\n\nexport * from \"./behaviours/behaviour.js\";\nexport * from \"./behaviours/press.js\";\nexport * from \"./behaviours/pulse.js\";\nexport * from \"./behaviours/hinge.js\";\nexport * from \"./behaviours/dial.js\";\nexport * from \"./behaviours/slide.js\";\nexport * from \"./behaviours/grab.js\";\nexport * from \"./behaviours/toss-score.js\";\nexport * from \"./behaviours/factory.js\";\n"]}
|
package/dist/math.d.ts
CHANGED
|
@@ -60,6 +60,14 @@ export declare function vApplyQuat(v: Vec3Tuple, q: QuatTuple): Vec3Tuple;
|
|
|
60
60
|
/** Transform a world-space point into the local frame of `pose`. */
|
|
61
61
|
export declare function worldToLocal(point: Vec3Tuple, posePosition: Vec3Tuple, poseQuaternion: QuatTuple): Vec3Tuple;
|
|
62
62
|
/** Closest-approach distance from a ray to a point, and the along-ray t. */
|
|
63
|
+
/**
|
|
64
|
+
* The point on a sphere's surface nearest `query`: the centre moved `radius`
|
|
65
|
+
* toward the query. A query at the centre exactly takes the point straight
|
|
66
|
+
* above it. This is the `point` a proximity hit reports, where the touch
|
|
67
|
+
* cursor sits (IWSDK's sphere intersector reports the point on the mesh,
|
|
68
|
+
* never the centre).
|
|
69
|
+
*/
|
|
70
|
+
export declare function surfacePointOnSphere(centre: Vec3Tuple, radius: number, query: Vec3Tuple): Vec3Tuple;
|
|
63
71
|
export declare function rayPointDistance(ray: RayTuple, point: Vec3Tuple): {
|
|
64
72
|
distance: number;
|
|
65
73
|
t: number;
|
package/dist/math.js
CHANGED
|
@@ -147,6 +147,23 @@ export function worldToLocal(point, posePosition, poseQuaternion) {
|
|
|
147
147
|
return vApplyQuat(vSub(point, posePosition), quatConjugate(poseQuaternion));
|
|
148
148
|
}
|
|
149
149
|
/** Closest-approach distance from a ray to a point, and the along-ray t. */
|
|
150
|
+
/**
|
|
151
|
+
* The point on a sphere's surface nearest `query`: the centre moved `radius`
|
|
152
|
+
* toward the query. A query at the centre exactly takes the point straight
|
|
153
|
+
* above it. This is the `point` a proximity hit reports, where the touch
|
|
154
|
+
* cursor sits (IWSDK's sphere intersector reports the point on the mesh,
|
|
155
|
+
* never the centre).
|
|
156
|
+
*/
|
|
157
|
+
export function surfacePointOnSphere(centre, radius, query) {
|
|
158
|
+
const dx = query[0] - centre[0];
|
|
159
|
+
const dy = query[1] - centre[1];
|
|
160
|
+
const dz = query[2] - centre[2];
|
|
161
|
+
const length = Math.hypot(dx, dy, dz);
|
|
162
|
+
if (length < 1e-9)
|
|
163
|
+
return [centre[0], centre[1] + radius, centre[2]];
|
|
164
|
+
const k = radius / length;
|
|
165
|
+
return [centre[0] + dx * k, centre[1] + dy * k, centre[2] + dz * k];
|
|
166
|
+
}
|
|
150
167
|
export function rayPointDistance(ray, point) {
|
|
151
168
|
const toPoint = vSub(point, ray.origin);
|
|
152
169
|
const t = vDot(toPoint, ray.direction);
|