@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
package/dist/math.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"math.js","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure interaction math - the proven motion functions extracted from the\n * Pale Signal research (springs, hinge/dial normalisation, hoop scoring),\n * plus the tuple vector/quaternion helpers the binder and behaviours need.\n * No engine imports; everything is unit-tested headless.\n */\nimport type { QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\n\n/** Clamp to the unit interval. */\nexport function clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n\n/**\n * Normalize a measured displacement/angle against a signed range to 0..1.\n * Sign-agnostic: a range of `-π/2` maps `-π/2 → 1`.\n */\nexport function normalize01(value: number, range: number): number {\n if (range === 0) return 0;\n return clamp01(value / range);\n}\n\n/**\n * Did a tossed object pass *through* a hoop this frame? True when it crossed\n * the hoop plane in the scoring direction (downward - `prev` above, `curr`\n * at/below) while inside the ring (`radial <= radius`).\n */\nexport function passedThroughHoop(\n prevAlong: number,\n currAlong: number,\n planeAlong: number,\n radial: number,\n radius: number,\n): boolean {\n const crossedDown = prevAlong > planeAlong && currAlong <= planeAlong;\n return crossedDown && radial <= radius;\n}\n\n/**\n * One step of a damped harmonic oscillator (semi-implicit Euler).\n * `out[0]` = next value, `out[1]` = next velocity. An under-damped spring\n * (damping ratio ≈ 0.3-0.5) is what gives a pressed button its bounce.\n * `dt` is clamped to 1/30 s so a long frame can't blow the integrator up.\n */\nexport function springStep(\n value: number,\n velocity: number,\n target: number,\n stiffness: number,\n damping: number,\n dt: number,\n out: Float32Array = new Float32Array(2),\n): Float32Array {\n const h = Math.min(dt, 1 / 30);\n const accel = -stiffness * (value - target) - damping * velocity;\n const nextVelocity = velocity + accel * h;\n out[0] = value + nextVelocity * h;\n out[1] = nextVelocity;\n return out;\n}\n\n/** Exponential decay of `value` toward 0 over `dt` at `ratePerSec`. */\nexport function decay(value: number, ratePerSec: number, dt: number): number {\n const next = value * Math.exp(-ratePerSec * Math.max(dt, 0));\n return next < 1e-4 ? 0 : next;\n}\n\n/** Penner's ease-out-bounce on `t ∈ [0,1]`. */\nexport function easeOutBounce(t: number): number {\n const n1 = 7.5625;\n const d1 = 2.75;\n let x = clamp01(t);\n if (x < 1 / d1) return n1 * x * x;\n if (x < 2 / d1) return n1 * (x -= 1.5 / d1) * x + 0.75;\n if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + 0.9375;\n return n1 * (x -= 2.625 / d1) * x + 0.984375;\n}\n\n/** Clamp a value to the symmetric range `[-|mag|, +|mag|]`. */\nexport function clampSym(x: number, mag: number): number {\n const m = Math.abs(mag);\n return x < -m ? -m : x > m ? m : x;\n}\n\n/**\n * Hinge angle for a position-driven lever: given the hand's offset from the\n * pivot resolved into the hinge plane - `alongRest` (toward the arm's rest\n * direction) and `alongSwing` (the in-plane perpendicular) - the angle the\n * arm should take to point at the hand, clamped to `±|maxMag|`.\n */\nexport function hingeAngleToHand(alongRest: number, alongSwing: number, maxMag: number): number {\n return clampSym(Math.atan2(alongSwing, alongRest), maxMag);\n}\n\n/**\n * Map a centered hinge angle in `[-|mag|, +|mag|]` to a 0..1 analog value\n * with the rest (0) at 0.5 - a bidirectional lever as one normalized value.\n */\nexport function centeredUnit(angle: number, mag: number): number {\n const m = Math.abs(mag);\n if (m < 1e-6) return 0.5;\n return clamp01(0.5 + (0.5 * angle) / m);\n}\n\n// ---------------------------------------------------------------------------\n// Tuple vector / quaternion helpers (allocation-light, no engine types).\n// ---------------------------------------------------------------------------\n\nexport function vSub(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\n}\n\nexport function vAdd(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];\n}\n\nexport function vScale(a: Vec3Tuple, s: number): Vec3Tuple {\n return [a[0] * s, a[1] * s, a[2] * s];\n}\n\nexport function vDot(a: Vec3Tuple, b: Vec3Tuple): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\nexport function vCross(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n ];\n}\n\nexport function vLength(a: Vec3Tuple): number {\n return Math.hypot(a[0], a[1], a[2]);\n}\n\nexport function vDistance(a: Vec3Tuple, b: Vec3Tuple): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n}\n\nexport function vNormalize(a: Vec3Tuple): Vec3Tuple {\n const l = vLength(a);\n return l < 1e-9 ? [0, 0, 0] : [a[0] / l, a[1] / l, a[2] / l];\n}\n\n/** Quaternion from an axis (normalised) and an angle in radians. */\nexport function quatFromAxisAngle(axis: Vec3Tuple, angle: number): QuatTuple {\n const half = angle / 2;\n const s = Math.sin(half);\n return [axis[0] * s, axis[1] * s, axis[2] * s, Math.cos(half)];\n}\n\nexport function quatMultiply(a: QuatTuple, b: QuatTuple): QuatTuple {\n const [ax, ay, az, aw] = a;\n const [bx, by, bz, bw] = b;\n return [\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n aw * bw - ax * bx - ay * by - az * bz,\n ];\n}\n\nexport function quatConjugate(q: QuatTuple): QuatTuple {\n return [-q[0], -q[1], -q[2], q[3]];\n}\n\n/** Rotate a vector by a quaternion. */\nexport function vApplyQuat(v: Vec3Tuple, q: QuatTuple): Vec3Tuple {\n const [x, y, z] = v;\n const [qx, qy, qz, qw] = q;\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (qy * z - qz * y);\n const ty = 2 * (qz * x - qx * z);\n const tz = 2 * (qx * y - qy * x);\n // v + qw * t + cross(q.xyz, t)\n return [\n x + qw * tx + qy * tz - qz * ty,\n y + qw * ty + qz * tx - qx * tz,\n z + qw * tz + qx * ty - qy * tx,\n ];\n}\n\n/** Transform a world-space point into the local frame of `pose`. */\nexport function worldToLocal(\n point: Vec3Tuple,\n posePosition: Vec3Tuple,\n poseQuaternion: QuatTuple,\n): Vec3Tuple {\n return vApplyQuat(vSub(point, posePosition), quatConjugate(poseQuaternion));\n}\n\n/** Closest-approach distance from a ray to a point, and the along-ray t. */\nexport function rayPointDistance(ray: RayTuple, point: Vec3Tuple): { distance: number; t: number } {\n const toPoint = vSub(point, ray.origin);\n const t = vDot(toPoint, ray.direction);\n if (t <= 0) return { distance: vLength(toPoint), t: 0 };\n const closest = vAdd(ray.origin, vScale(ray.direction, t));\n return { distance: vDistance(closest, point), t };\n}\n\n/** Angular offset (radians) between a ray direction and the direction to a point. */\nexport function rayAngleTo(ray: RayTuple, point: Vec3Tuple): number {\n const dir = vNormalize(vSub(point, ray.origin));\n const d = vDot(vNormalize(ray.direction), dir);\n return Math.acos(Math.min(1, Math.max(-1, d)));\n}\n\n/*\n * Velocity between two poses lives in `@realitycollective/webxr-input` as\n * `velocityBetween`, re-exported from this package's index. The local copy\n * that stood in for it before Input 0.1.1 is gone.\n */\n\n/** Zero out a signal below `threshold`, leaving larger values untouched. */\nexport function clampDeadzone(value: number, threshold: number): number {\n return Math.abs(value) < threshold ? 0 : value;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"math.js","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure interaction math - the proven motion functions extracted from the\n * Pale Signal research (springs, hinge/dial normalisation, hoop scoring),\n * plus the tuple vector/quaternion helpers the binder and behaviours need.\n * No engine imports; everything is unit-tested headless.\n */\nimport type { QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\n\n/** Clamp to the unit interval. */\nexport function clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n\n/**\n * Normalize a measured displacement/angle against a signed range to 0..1.\n * Sign-agnostic: a range of `-π/2` maps `-π/2 → 1`.\n */\nexport function normalize01(value: number, range: number): number {\n if (range === 0) return 0;\n return clamp01(value / range);\n}\n\n/**\n * Did a tossed object pass *through* a hoop this frame? True when it crossed\n * the hoop plane in the scoring direction (downward - `prev` above, `curr`\n * at/below) while inside the ring (`radial <= radius`).\n */\nexport function passedThroughHoop(\n prevAlong: number,\n currAlong: number,\n planeAlong: number,\n radial: number,\n radius: number,\n): boolean {\n const crossedDown = prevAlong > planeAlong && currAlong <= planeAlong;\n return crossedDown && radial <= radius;\n}\n\n/**\n * One step of a damped harmonic oscillator (semi-implicit Euler).\n * `out[0]` = next value, `out[1]` = next velocity. An under-damped spring\n * (damping ratio ≈ 0.3-0.5) is what gives a pressed button its bounce.\n * `dt` is clamped to 1/30 s so a long frame can't blow the integrator up.\n */\nexport function springStep(\n value: number,\n velocity: number,\n target: number,\n stiffness: number,\n damping: number,\n dt: number,\n out: Float32Array = new Float32Array(2),\n): Float32Array {\n const h = Math.min(dt, 1 / 30);\n const accel = -stiffness * (value - target) - damping * velocity;\n const nextVelocity = velocity + accel * h;\n out[0] = value + nextVelocity * h;\n out[1] = nextVelocity;\n return out;\n}\n\n/** Exponential decay of `value` toward 0 over `dt` at `ratePerSec`. */\nexport function decay(value: number, ratePerSec: number, dt: number): number {\n const next = value * Math.exp(-ratePerSec * Math.max(dt, 0));\n return next < 1e-4 ? 0 : next;\n}\n\n/** Penner's ease-out-bounce on `t ∈ [0,1]`. */\nexport function easeOutBounce(t: number): number {\n const n1 = 7.5625;\n const d1 = 2.75;\n let x = clamp01(t);\n if (x < 1 / d1) return n1 * x * x;\n if (x < 2 / d1) return n1 * (x -= 1.5 / d1) * x + 0.75;\n if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + 0.9375;\n return n1 * (x -= 2.625 / d1) * x + 0.984375;\n}\n\n/** Clamp a value to the symmetric range `[-|mag|, +|mag|]`. */\nexport function clampSym(x: number, mag: number): number {\n const m = Math.abs(mag);\n return x < -m ? -m : x > m ? m : x;\n}\n\n/**\n * Hinge angle for a position-driven lever: given the hand's offset from the\n * pivot resolved into the hinge plane - `alongRest` (toward the arm's rest\n * direction) and `alongSwing` (the in-plane perpendicular) - the angle the\n * arm should take to point at the hand, clamped to `±|maxMag|`.\n */\nexport function hingeAngleToHand(alongRest: number, alongSwing: number, maxMag: number): number {\n return clampSym(Math.atan2(alongSwing, alongRest), maxMag);\n}\n\n/**\n * Map a centered hinge angle in `[-|mag|, +|mag|]` to a 0..1 analog value\n * with the rest (0) at 0.5 - a bidirectional lever as one normalized value.\n */\nexport function centeredUnit(angle: number, mag: number): number {\n const m = Math.abs(mag);\n if (m < 1e-6) return 0.5;\n return clamp01(0.5 + (0.5 * angle) / m);\n}\n\n// ---------------------------------------------------------------------------\n// Tuple vector / quaternion helpers (allocation-light, no engine types).\n// ---------------------------------------------------------------------------\n\nexport function vSub(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];\n}\n\nexport function vAdd(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];\n}\n\nexport function vScale(a: Vec3Tuple, s: number): Vec3Tuple {\n return [a[0] * s, a[1] * s, a[2] * s];\n}\n\nexport function vDot(a: Vec3Tuple, b: Vec3Tuple): number {\n return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];\n}\n\nexport function vCross(a: Vec3Tuple, b: Vec3Tuple): Vec3Tuple {\n return [\n a[1] * b[2] - a[2] * b[1],\n a[2] * b[0] - a[0] * b[2],\n a[0] * b[1] - a[1] * b[0],\n ];\n}\n\nexport function vLength(a: Vec3Tuple): number {\n return Math.hypot(a[0], a[1], a[2]);\n}\n\nexport function vDistance(a: Vec3Tuple, b: Vec3Tuple): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);\n}\n\nexport function vNormalize(a: Vec3Tuple): Vec3Tuple {\n const l = vLength(a);\n return l < 1e-9 ? [0, 0, 0] : [a[0] / l, a[1] / l, a[2] / l];\n}\n\n/** Quaternion from an axis (normalised) and an angle in radians. */\nexport function quatFromAxisAngle(axis: Vec3Tuple, angle: number): QuatTuple {\n const half = angle / 2;\n const s = Math.sin(half);\n return [axis[0] * s, axis[1] * s, axis[2] * s, Math.cos(half)];\n}\n\nexport function quatMultiply(a: QuatTuple, b: QuatTuple): QuatTuple {\n const [ax, ay, az, aw] = a;\n const [bx, by, bz, bw] = b;\n return [\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n aw * bw - ax * bx - ay * by - az * bz,\n ];\n}\n\nexport function quatConjugate(q: QuatTuple): QuatTuple {\n return [-q[0], -q[1], -q[2], q[3]];\n}\n\n/** Rotate a vector by a quaternion. */\nexport function vApplyQuat(v: Vec3Tuple, q: QuatTuple): Vec3Tuple {\n const [x, y, z] = v;\n const [qx, qy, qz, qw] = q;\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (qy * z - qz * y);\n const ty = 2 * (qz * x - qx * z);\n const tz = 2 * (qx * y - qy * x);\n // v + qw * t + cross(q.xyz, t)\n return [\n x + qw * tx + qy * tz - qz * ty,\n y + qw * ty + qz * tx - qx * tz,\n z + qw * tz + qx * ty - qy * tx,\n ];\n}\n\n/** Transform a world-space point into the local frame of `pose`. */\nexport function worldToLocal(\n point: Vec3Tuple,\n posePosition: Vec3Tuple,\n poseQuaternion: QuatTuple,\n): Vec3Tuple {\n return vApplyQuat(vSub(point, posePosition), quatConjugate(poseQuaternion));\n}\n\n/** Closest-approach distance from a ray to a point, and the along-ray t. */\n/**\n * The point on a sphere's surface nearest `query`: the centre moved `radius`\n * toward the query. A query at the centre exactly takes the point straight\n * above it. This is the `point` a proximity hit reports, where the touch\n * cursor sits (IWSDK's sphere intersector reports the point on the mesh,\n * never the centre).\n */\nexport function surfacePointOnSphere(centre: Vec3Tuple, radius: number, query: Vec3Tuple): Vec3Tuple {\n const dx = query[0] - centre[0];\n const dy = query[1] - centre[1];\n const dz = query[2] - centre[2];\n const length = Math.hypot(dx, dy, dz);\n if (length < 1e-9) return [centre[0], centre[1] + radius, centre[2]];\n const k = radius / length;\n return [centre[0] + dx * k, centre[1] + dy * k, centre[2] + dz * k];\n}\n\nexport function rayPointDistance(ray: RayTuple, point: Vec3Tuple): { distance: number; t: number } {\n const toPoint = vSub(point, ray.origin);\n const t = vDot(toPoint, ray.direction);\n if (t <= 0) return { distance: vLength(toPoint), t: 0 };\n const closest = vAdd(ray.origin, vScale(ray.direction, t));\n return { distance: vDistance(closest, point), t };\n}\n\n/** Angular offset (radians) between a ray direction and the direction to a point. */\nexport function rayAngleTo(ray: RayTuple, point: Vec3Tuple): number {\n const dir = vNormalize(vSub(point, ray.origin));\n const d = vDot(vNormalize(ray.direction), dir);\n return Math.acos(Math.min(1, Math.max(-1, d)));\n}\n\n/*\n * Velocity between two poses lives in `@realitycollective/webxr-input` as\n * `velocityBetween`, re-exported from this package's index. The local copy\n * that stood in for it before Input 0.1.1 is gone.\n */\n\n/** Zero out a signal below `threshold`, leaving larger values untouched. */\nexport function clampDeadzone(value: number, threshold: number): number {\n return Math.abs(value) < threshold ? 0 : value;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryPhysicsFacility - the family's in-memory reference physics: an
|
|
3
|
+
* engine-free `PhysicsFacility` that runs headlessly with the core defaults,
|
|
4
|
+
* so the contract suite has a correct subject to prove itself against and a
|
|
5
|
+
* new binding has a reference to compare with (the same idea as
|
|
6
|
+
* `MemoryInputProvider` in `@realitycollective/webxr-input`).
|
|
7
|
+
*
|
|
8
|
+
* It simulates exactly what the contract cases need and states its limits:
|
|
9
|
+
* gravity (with per-body factor and damping) integrated per step, spheres
|
|
10
|
+
* against axis-aligned static or kinematic boxes with restitution combined
|
|
11
|
+
* as the average of the two bodies (Rapier's default rule), and nothing
|
|
12
|
+
* else: no sphere-to-sphere contact, no friction dynamics, no rotation of
|
|
13
|
+
* boxes. It is not a game engine; a platform binding names a real one.
|
|
14
|
+
*/
|
|
15
|
+
import type { PoseTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
16
|
+
import type { HoldRelease } from "./ports.js";
|
|
17
|
+
import { type PhysicsBodySpec, type PhysicsBodyState, type PhysicsFacility, type PhysicsShapeSpec, type PhysicsVelocity } from "./physics.js";
|
|
18
|
+
export declare class MemoryPhysicsFacility implements PhysicsFacility {
|
|
19
|
+
readonly engine: string;
|
|
20
|
+
private gravity;
|
|
21
|
+
private readonly bodies;
|
|
22
|
+
getGravity(): Vec3Tuple;
|
|
23
|
+
setGravity(gravity: Vec3Tuple): void;
|
|
24
|
+
addBody(id: string, pose: PoseTuple, body?: PhysicsBodySpec, shape?: PhysicsShapeSpec): void;
|
|
25
|
+
removeBody(id: string): void;
|
|
26
|
+
hasBody(id: string): boolean;
|
|
27
|
+
setBodyState(id: string, state: PhysicsBodyState): void;
|
|
28
|
+
getBodyState(id: string): PhysicsBodyState;
|
|
29
|
+
getBodyPose(id: string): PoseTuple;
|
|
30
|
+
setBodyPose(id: string, pose: PoseTuple): void;
|
|
31
|
+
getVelocity(id: string): PhysicsVelocity;
|
|
32
|
+
setVelocity(id: string, velocity: PhysicsVelocity): void;
|
|
33
|
+
suspend(id: string): void;
|
|
34
|
+
resume(id: string, release: HoldRelease): void;
|
|
35
|
+
isSuspended(id: string): boolean;
|
|
36
|
+
step(dtSeconds: number): void;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
private body;
|
|
39
|
+
/** A moving sphere against every non-dynamic axis-aligned box: push out along the least-penetrated axis and reflect the normal velocity. */
|
|
40
|
+
private collideSphere;
|
|
41
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { PHYSICS_DEFAULTS, missingBody, resolvePhysicsBody, resolvePhysicsShape, } from "./physics.js";
|
|
2
|
+
function copy3(v) {
|
|
3
|
+
return [v[0], v[1], v[2]];
|
|
4
|
+
}
|
|
5
|
+
export class MemoryPhysicsFacility {
|
|
6
|
+
engine = "memory";
|
|
7
|
+
gravity = copy3(PHYSICS_DEFAULTS.gravity);
|
|
8
|
+
bodies = new Map();
|
|
9
|
+
getGravity() {
|
|
10
|
+
return copy3(this.gravity);
|
|
11
|
+
}
|
|
12
|
+
setGravity(gravity) {
|
|
13
|
+
this.gravity = copy3(gravity);
|
|
14
|
+
}
|
|
15
|
+
addBody(id, pose, body = {}, shape = {}) {
|
|
16
|
+
const spec = resolvePhysicsBody(body);
|
|
17
|
+
this.bodies.set(id, {
|
|
18
|
+
state: spec.state,
|
|
19
|
+
spec,
|
|
20
|
+
shape: resolvePhysicsShape(shape),
|
|
21
|
+
position: copy3(pose.position),
|
|
22
|
+
quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]],
|
|
23
|
+
linear: [0, 0, 0],
|
|
24
|
+
angular: [0, 0, 0],
|
|
25
|
+
suspended: false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
removeBody(id) {
|
|
29
|
+
this.bodies.delete(id);
|
|
30
|
+
}
|
|
31
|
+
hasBody(id) {
|
|
32
|
+
return this.bodies.has(id);
|
|
33
|
+
}
|
|
34
|
+
setBodyState(id, state) {
|
|
35
|
+
const body = this.body(id);
|
|
36
|
+
body.state = state;
|
|
37
|
+
if (state !== "dynamic") {
|
|
38
|
+
body.linear = [0, 0, 0];
|
|
39
|
+
body.angular = [0, 0, 0];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getBodyState(id) {
|
|
43
|
+
return this.body(id).state;
|
|
44
|
+
}
|
|
45
|
+
getBodyPose(id) {
|
|
46
|
+
const body = this.body(id);
|
|
47
|
+
return { position: copy3(body.position), quaternion: [...body.quaternion] };
|
|
48
|
+
}
|
|
49
|
+
setBodyPose(id, pose) {
|
|
50
|
+
const body = this.body(id);
|
|
51
|
+
body.position = copy3(pose.position);
|
|
52
|
+
body.quaternion = [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]];
|
|
53
|
+
// A teleport rests the body; a write while held carries nothing either.
|
|
54
|
+
body.linear = [0, 0, 0];
|
|
55
|
+
body.angular = [0, 0, 0];
|
|
56
|
+
}
|
|
57
|
+
getVelocity(id) {
|
|
58
|
+
const body = this.body(id);
|
|
59
|
+
return { linear: copy3(body.linear), angular: copy3(body.angular) };
|
|
60
|
+
}
|
|
61
|
+
setVelocity(id, velocity) {
|
|
62
|
+
const body = this.body(id);
|
|
63
|
+
body.linear = copy3(velocity.linear);
|
|
64
|
+
body.angular = copy3(velocity.angular);
|
|
65
|
+
}
|
|
66
|
+
suspend(id) {
|
|
67
|
+
const body = this.body(id);
|
|
68
|
+
if (body.suspended)
|
|
69
|
+
return;
|
|
70
|
+
body.suspended = true;
|
|
71
|
+
body.linear = [0, 0, 0];
|
|
72
|
+
body.angular = [0, 0, 0];
|
|
73
|
+
}
|
|
74
|
+
resume(id, release) {
|
|
75
|
+
const body = this.body(id);
|
|
76
|
+
if (!body.suspended)
|
|
77
|
+
return;
|
|
78
|
+
body.suspended = false;
|
|
79
|
+
body.linear = copy3(release.linearVelocity);
|
|
80
|
+
body.angular = copy3(release.angularVelocity);
|
|
81
|
+
}
|
|
82
|
+
isSuspended(id) {
|
|
83
|
+
return this.body(id).suspended;
|
|
84
|
+
}
|
|
85
|
+
step(dtSeconds) {
|
|
86
|
+
if (dtSeconds <= 0)
|
|
87
|
+
return;
|
|
88
|
+
for (const body of this.bodies.values()) {
|
|
89
|
+
if (body.state !== "dynamic" || body.suspended)
|
|
90
|
+
continue;
|
|
91
|
+
const g = body.spec.gravityFactor;
|
|
92
|
+
body.linear[0] += this.gravity[0] * g * dtSeconds;
|
|
93
|
+
body.linear[1] += this.gravity[1] * g * dtSeconds;
|
|
94
|
+
body.linear[2] += this.gravity[2] * g * dtSeconds;
|
|
95
|
+
const linearKeep = Math.max(0, 1 - body.spec.linearDamping * dtSeconds);
|
|
96
|
+
const angularKeep = Math.max(0, 1 - body.spec.angularDamping * dtSeconds);
|
|
97
|
+
body.linear = [body.linear[0] * linearKeep, body.linear[1] * linearKeep, body.linear[2] * linearKeep];
|
|
98
|
+
body.angular = [body.angular[0] * angularKeep, body.angular[1] * angularKeep, body.angular[2] * angularKeep];
|
|
99
|
+
body.position = [
|
|
100
|
+
body.position[0] + body.linear[0] * dtSeconds,
|
|
101
|
+
body.position[1] + body.linear[1] * dtSeconds,
|
|
102
|
+
body.position[2] + body.linear[2] * dtSeconds,
|
|
103
|
+
];
|
|
104
|
+
body.quaternion = integrateRotation(body.quaternion, body.angular, dtSeconds);
|
|
105
|
+
if (body.shape.kind === "sphere")
|
|
106
|
+
this.collideSphere(body);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
dispose() {
|
|
110
|
+
this.bodies.clear();
|
|
111
|
+
}
|
|
112
|
+
body(id) {
|
|
113
|
+
const body = this.bodies.get(id);
|
|
114
|
+
if (!body)
|
|
115
|
+
throw missingBody(id);
|
|
116
|
+
return body;
|
|
117
|
+
}
|
|
118
|
+
/** A moving sphere against every non-dynamic axis-aligned box: push out along the least-penetrated axis and reflect the normal velocity. */
|
|
119
|
+
collideSphere(sphere) {
|
|
120
|
+
const radius = sphere.shape.dimensions[0];
|
|
121
|
+
for (const other of this.bodies.values()) {
|
|
122
|
+
if (other === sphere || other.shape.kind !== "box" || other.state === "dynamic")
|
|
123
|
+
continue;
|
|
124
|
+
const half = [other.shape.dimensions[0] / 2, other.shape.dimensions[1] / 2, other.shape.dimensions[2] / 2];
|
|
125
|
+
let bestAxis = -1;
|
|
126
|
+
let bestDepth = Number.POSITIVE_INFINITY;
|
|
127
|
+
let bestSign = 1;
|
|
128
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
129
|
+
const delta = sphere.position[axis] - other.position[axis];
|
|
130
|
+
const depth = half[axis] + radius - Math.abs(delta);
|
|
131
|
+
if (depth <= 0) {
|
|
132
|
+
bestAxis = -1;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
if (depth < bestDepth) {
|
|
136
|
+
bestDepth = depth;
|
|
137
|
+
bestAxis = axis;
|
|
138
|
+
bestSign = delta >= 0 ? 1 : -1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (bestAxis < 0)
|
|
142
|
+
continue;
|
|
143
|
+
sphere.position[bestAxis] = sphere.position[bestAxis] + bestSign * bestDepth;
|
|
144
|
+
const normalVelocity = sphere.linear[bestAxis] * bestSign;
|
|
145
|
+
if (normalVelocity < 0) {
|
|
146
|
+
const restitution = (sphere.shape.restitution + other.shape.restitution) / 2;
|
|
147
|
+
sphere.linear[bestAxis] = -normalVelocity * restitution * bestSign;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Advance a quaternion by an angular velocity (axis scaled by rad/s) over dt, first order. */
|
|
153
|
+
function integrateRotation(q, angular, dt) {
|
|
154
|
+
const angle = Math.hypot(angular[0], angular[1], angular[2]) * dt;
|
|
155
|
+
if (angle < 1e-9)
|
|
156
|
+
return q;
|
|
157
|
+
const half = angle / 2;
|
|
158
|
+
const s = Math.sin(half) / (angle / dt) * dt;
|
|
159
|
+
const d = [angular[0] * s, angular[1] * s, angular[2] * s, Math.cos(half)];
|
|
160
|
+
// d * q
|
|
161
|
+
return [
|
|
162
|
+
d[3] * q[0] + d[0] * q[3] + d[1] * q[2] - d[2] * q[1],
|
|
163
|
+
d[3] * q[1] - d[0] * q[2] + d[1] * q[3] + d[2] * q[0],
|
|
164
|
+
d[3] * q[2] + d[0] * q[1] - d[1] * q[0] + d[2] * q[3],
|
|
165
|
+
d[3] * q[3] - d[0] * q[0] - d[1] * q[1] - d[2] * q[2],
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=memory-physics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-physics.js","sourceRoot":"","sources":["../src/memory-physics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MemoryPhysicsFacility - the family's in-memory reference physics: an\n * engine-free `PhysicsFacility` that runs headlessly with the core defaults,\n * so the contract suite has a correct subject to prove itself against and a\n * new binding has a reference to compare with (the same idea as\n * `MemoryInputProvider` in `@realitycollective/webxr-input`).\n *\n * It simulates exactly what the contract cases need and states its limits:\n * gravity (with per-body factor and damping) integrated per step, spheres\n * against axis-aligned static or kinematic boxes with restitution combined\n * as the average of the two bodies (Rapier's default rule), and nothing\n * else: no sphere-to-sphere contact, no friction dynamics, no rotation of\n * boxes. It is not a game engine; a platform binding names a real one.\n */\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { HoldRelease } from \"./ports.js\";\nimport {\n PHYSICS_DEFAULTS,\n missingBody,\n resolvePhysicsBody,\n resolvePhysicsShape,\n type PhysicsBodySpec,\n type PhysicsBodyState,\n type PhysicsFacility,\n type PhysicsShapeSpec,\n type PhysicsVelocity,\n} from \"./physics.js\";\n\ninterface Body {\n state: PhysicsBodyState;\n spec: Required<PhysicsBodySpec>;\n shape: Required<PhysicsShapeSpec>;\n position: Vec3Tuple;\n quaternion: QuatTuple;\n linear: Vec3Tuple;\n angular: Vec3Tuple;\n suspended: boolean;\n}\n\nfunction copy3(v: Readonly<Vec3Tuple>): Vec3Tuple {\n return [v[0], v[1], v[2]];\n}\n\nexport class MemoryPhysicsFacility implements PhysicsFacility {\n readonly engine: string = \"memory\";\n private gravity: Vec3Tuple = copy3(PHYSICS_DEFAULTS.gravity);\n private readonly bodies = new Map<string, Body>();\n\n getGravity(): Vec3Tuple {\n return copy3(this.gravity);\n }\n\n setGravity(gravity: Vec3Tuple): void {\n this.gravity = copy3(gravity);\n }\n\n addBody(id: string, pose: PoseTuple, body: PhysicsBodySpec = {}, shape: PhysicsShapeSpec = {}): void {\n const spec = resolvePhysicsBody(body);\n this.bodies.set(id, {\n state: spec.state,\n spec,\n shape: resolvePhysicsShape(shape),\n position: copy3(pose.position),\n quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]],\n linear: [0, 0, 0],\n angular: [0, 0, 0],\n suspended: false,\n });\n }\n\n removeBody(id: string): void {\n this.bodies.delete(id);\n }\n\n hasBody(id: string): boolean {\n return this.bodies.has(id);\n }\n\n setBodyState(id: string, state: PhysicsBodyState): void {\n const body = this.body(id);\n body.state = state;\n if (state !== \"dynamic\") {\n body.linear = [0, 0, 0];\n body.angular = [0, 0, 0];\n }\n }\n\n getBodyState(id: string): PhysicsBodyState {\n return this.body(id).state;\n }\n\n getBodyPose(id: string): PoseTuple {\n const body = this.body(id);\n return { position: copy3(body.position), quaternion: [...body.quaternion] as QuatTuple };\n }\n\n setBodyPose(id: string, pose: PoseTuple): void {\n const body = this.body(id);\n body.position = copy3(pose.position);\n body.quaternion = [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]];\n // A teleport rests the body; a write while held carries nothing either.\n body.linear = [0, 0, 0];\n body.angular = [0, 0, 0];\n }\n\n getVelocity(id: string): PhysicsVelocity {\n const body = this.body(id);\n return { linear: copy3(body.linear), angular: copy3(body.angular) };\n }\n\n setVelocity(id: string, velocity: PhysicsVelocity): void {\n const body = this.body(id);\n body.linear = copy3(velocity.linear);\n body.angular = copy3(velocity.angular);\n }\n\n suspend(id: string): void {\n const body = this.body(id);\n if (body.suspended) return;\n body.suspended = true;\n body.linear = [0, 0, 0];\n body.angular = [0, 0, 0];\n }\n\n resume(id: string, release: HoldRelease): void {\n const body = this.body(id);\n if (!body.suspended) return;\n body.suspended = false;\n body.linear = copy3(release.linearVelocity);\n body.angular = copy3(release.angularVelocity);\n }\n\n isSuspended(id: string): boolean {\n return this.body(id).suspended;\n }\n\n step(dtSeconds: number): void {\n if (dtSeconds <= 0) return;\n for (const body of this.bodies.values()) {\n if (body.state !== \"dynamic\" || body.suspended) continue;\n const g = body.spec.gravityFactor;\n body.linear[0] += this.gravity[0] * g * dtSeconds;\n body.linear[1] += this.gravity[1] * g * dtSeconds;\n body.linear[2] += this.gravity[2] * g * dtSeconds;\n const linearKeep = Math.max(0, 1 - body.spec.linearDamping * dtSeconds);\n const angularKeep = Math.max(0, 1 - body.spec.angularDamping * dtSeconds);\n body.linear = [body.linear[0] * linearKeep, body.linear[1] * linearKeep, body.linear[2] * linearKeep];\n body.angular = [body.angular[0] * angularKeep, body.angular[1] * angularKeep, body.angular[2] * angularKeep];\n body.position = [\n body.position[0] + body.linear[0] * dtSeconds,\n body.position[1] + body.linear[1] * dtSeconds,\n body.position[2] + body.linear[2] * dtSeconds,\n ];\n body.quaternion = integrateRotation(body.quaternion, body.angular, dtSeconds);\n if (body.shape.kind === \"sphere\") this.collideSphere(body);\n }\n }\n\n dispose(): void {\n this.bodies.clear();\n }\n\n private body(id: string): Body {\n const body = this.bodies.get(id);\n if (!body) throw missingBody(id);\n return body;\n }\n\n /** A moving sphere against every non-dynamic axis-aligned box: push out along the least-penetrated axis and reflect the normal velocity. */\n private collideSphere(sphere: Body): void {\n const radius = sphere.shape.dimensions[0];\n for (const other of this.bodies.values()) {\n if (other === sphere || other.shape.kind !== \"box\" || other.state === \"dynamic\") continue;\n const half: Vec3Tuple = [other.shape.dimensions[0] / 2, other.shape.dimensions[1] / 2, other.shape.dimensions[2] / 2];\n let bestAxis = -1;\n let bestDepth = Number.POSITIVE_INFINITY;\n let bestSign = 1;\n for (let axis = 0; axis < 3; axis++) {\n const delta = sphere.position[axis]! - other.position[axis]!;\n const depth = half[axis]! + radius - Math.abs(delta);\n if (depth <= 0) {\n bestAxis = -1;\n break;\n }\n if (depth < bestDepth) {\n bestDepth = depth;\n bestAxis = axis;\n bestSign = delta >= 0 ? 1 : -1;\n }\n }\n if (bestAxis < 0) continue;\n sphere.position[bestAxis] = sphere.position[bestAxis]! + bestSign * bestDepth;\n const normalVelocity = sphere.linear[bestAxis]! * bestSign;\n if (normalVelocity < 0) {\n const restitution = (sphere.shape.restitution + other.shape.restitution) / 2;\n sphere.linear[bestAxis] = -normalVelocity * restitution * bestSign;\n }\n }\n }\n}\n\n/** Advance a quaternion by an angular velocity (axis scaled by rad/s) over dt, first order. */\nfunction integrateRotation(q: QuatTuple, angular: Vec3Tuple, dt: number): QuatTuple {\n const angle = Math.hypot(angular[0], angular[1], angular[2]) * dt;\n if (angle < 1e-9) return q;\n const half = angle / 2;\n const s = Math.sin(half) / (angle / dt) * dt;\n const d: QuatTuple = [angular[0] * s, angular[1] * s, angular[2] * s, Math.cos(half)];\n // d * q\n return [\n d[3] * q[0] + d[0] * q[3] + d[1] * q[2] - d[2] * q[1],\n d[3] * q[1] - d[0] * q[2] + d[1] * q[3] + d[2] * q[0],\n d[3] * q[2] + d[0] * q[1] - d[1] * q[0] + d[2] * q[3],\n d[3] * q[3] - d[0] * q[0] - d[1] * q[1] - d[2] * q[2],\n ];\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine-free ports over sphere targets: the reference `HitTester` and
|
|
3
|
+
* `TransformPort` a trace replay, a synthetic recording, a harness run
|
|
4
|
+
* without a host, or a test can drive with no engine at all. They apply the
|
|
5
|
+
* contract exactly as every binding must (`hitTesterContractCases()` and
|
|
6
|
+
* `transformPortContractCases()` pass over them), so a difference between a
|
|
7
|
+
* binding and these is a difference in the binding.
|
|
8
|
+
*/
|
|
9
|
+
import type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from "@realitycollective/webxr-input";
|
|
10
|
+
import type { HitTester, HoldRelease, InteractableHit, TransformPort } from "./ports.js";
|
|
11
|
+
/** IWSDK's radius for a target that declares none. */
|
|
12
|
+
export declare const MEMORY_DEFAULT_RADIUS = 0.1;
|
|
13
|
+
/** A sphere-target hit tester: IWSDK's `EntityHitTester` rules over plain spheres. */
|
|
14
|
+
export declare class MemoryHitTester implements HitTester {
|
|
15
|
+
private readonly targets;
|
|
16
|
+
/** Place a shown sphere target; a port makes its centre follow the port's live pose. */
|
|
17
|
+
place(id: string, centre: Vec3Tuple, radius?: number, port?: MemoryTransformPort): void;
|
|
18
|
+
remove(id: string): void;
|
|
19
|
+
setVisible(id: string, visible: boolean): void;
|
|
20
|
+
clear(): void;
|
|
21
|
+
private centreOf;
|
|
22
|
+
hitRay(ray: RayTuple): InteractableHit | null;
|
|
23
|
+
hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null;
|
|
24
|
+
hitCone(ray: RayTuple, halfAngle: number, maxLength: number): InteractableHit | null;
|
|
25
|
+
}
|
|
26
|
+
/** A transform port over a pose it keeps itself, with the held-pose members recorded. */
|
|
27
|
+
export declare class MemoryTransformPort implements TransformPort {
|
|
28
|
+
private rest;
|
|
29
|
+
private live;
|
|
30
|
+
private offset;
|
|
31
|
+
private rotation;
|
|
32
|
+
private held;
|
|
33
|
+
/** Every release `endHold` received, in order. */
|
|
34
|
+
readonly releases: HoldRelease[];
|
|
35
|
+
/** The last effect written. */
|
|
36
|
+
effect: {
|
|
37
|
+
scale?: number;
|
|
38
|
+
emissive?: number;
|
|
39
|
+
};
|
|
40
|
+
constructor(rest: PoseTuple);
|
|
41
|
+
getWorldPose(): PoseTuple;
|
|
42
|
+
getRestWorldPose(): PoseTuple;
|
|
43
|
+
getLocalOffset(): Vec3Tuple;
|
|
44
|
+
setLocalOffset(offset: Vec3Tuple): void;
|
|
45
|
+
setLocalRotation(quaternion: QuatTuple): void;
|
|
46
|
+
setWorldPose(pose: PoseTuple): void;
|
|
47
|
+
setEffect(effect: {
|
|
48
|
+
scale?: number;
|
|
49
|
+
emissive?: number;
|
|
50
|
+
}): void;
|
|
51
|
+
recaptureRest(): void;
|
|
52
|
+
beginHold(): void;
|
|
53
|
+
endHold(release: HoldRelease): void;
|
|
54
|
+
get isHeld(): boolean;
|
|
55
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { coneHitForSpheres } from "./gaze.js";
|
|
2
|
+
import { quatMultiply, rayPointDistance, surfacePointOnSphere, vAdd, vApplyQuat } from "./math.js";
|
|
3
|
+
/** IWSDK's radius for a target that declares none. */
|
|
4
|
+
export const MEMORY_DEFAULT_RADIUS = 0.1;
|
|
5
|
+
/** A sphere-target hit tester: IWSDK's `EntityHitTester` rules over plain spheres. */
|
|
6
|
+
export class MemoryHitTester {
|
|
7
|
+
targets = new Map();
|
|
8
|
+
/** Place a shown sphere target; a port makes its centre follow the port's live pose. */
|
|
9
|
+
place(id, centre, radius = MEMORY_DEFAULT_RADIUS, port) {
|
|
10
|
+
this.targets.set(id, { id, centre: [centre[0], centre[1], centre[2]], radius, visible: true, ...(port ? { port } : {}) });
|
|
11
|
+
}
|
|
12
|
+
remove(id) {
|
|
13
|
+
this.targets.delete(id);
|
|
14
|
+
}
|
|
15
|
+
setVisible(id, visible) {
|
|
16
|
+
const target = this.targets.get(id);
|
|
17
|
+
if (target)
|
|
18
|
+
target.visible = visible;
|
|
19
|
+
}
|
|
20
|
+
clear() {
|
|
21
|
+
this.targets.clear();
|
|
22
|
+
}
|
|
23
|
+
centreOf(target) {
|
|
24
|
+
return target.port ? target.port.getWorldPose().position : target.centre;
|
|
25
|
+
}
|
|
26
|
+
hitRay(ray) {
|
|
27
|
+
let best = null;
|
|
28
|
+
for (const target of this.targets.values()) {
|
|
29
|
+
if (!target.visible)
|
|
30
|
+
continue;
|
|
31
|
+
const centre = this.centreOf(target);
|
|
32
|
+
const { distance, t } = rayPointDistance(ray, centre);
|
|
33
|
+
if (t <= 0 || distance > target.radius)
|
|
34
|
+
continue;
|
|
35
|
+
if (best === null || t < best.distance) {
|
|
36
|
+
// Where the ray enters the sphere.
|
|
37
|
+
const inside = Math.sqrt(Math.max(0, target.radius * target.radius - distance * distance));
|
|
38
|
+
const enter = t - inside;
|
|
39
|
+
best = {
|
|
40
|
+
interactableId: target.id,
|
|
41
|
+
distance: enter,
|
|
42
|
+
point: [ray.origin[0] + ray.direction[0] * enter, ray.origin[1] + ray.direction[1] * enter, ray.origin[2] + ray.direction[2] * enter],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return best;
|
|
47
|
+
}
|
|
48
|
+
hitProximity(point, radius) {
|
|
49
|
+
let best = null;
|
|
50
|
+
for (const target of this.targets.values()) {
|
|
51
|
+
if (!target.visible)
|
|
52
|
+
continue;
|
|
53
|
+
const centre = this.centreOf(target);
|
|
54
|
+
const surface = Math.max(0, Math.hypot(centre[0] - point[0], centre[1] - point[1], centre[2] - point[2]) - target.radius);
|
|
55
|
+
if (surface <= radius && (best === null || surface < best.distance)) {
|
|
56
|
+
best = { interactableId: target.id, distance: surface, point: surfacePointOnSphere(centre, target.radius, point) };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return best;
|
|
60
|
+
}
|
|
61
|
+
hitCone(ray, halfAngle, maxLength) {
|
|
62
|
+
const spheres = [];
|
|
63
|
+
for (const target of this.targets.values()) {
|
|
64
|
+
if (target.visible)
|
|
65
|
+
spheres.push({ id: target.id, center: this.centreOf(target), radius: target.radius });
|
|
66
|
+
}
|
|
67
|
+
const hit = coneHitForSpheres(ray, spheres, halfAngle, maxLength);
|
|
68
|
+
return hit ? { interactableId: hit.interactableId, distance: hit.distance, point: hit.point } : null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** A transform port over a pose it keeps itself, with the held-pose members recorded. */
|
|
72
|
+
export class MemoryTransformPort {
|
|
73
|
+
rest;
|
|
74
|
+
live;
|
|
75
|
+
offset = [0, 0, 0];
|
|
76
|
+
rotation = [0, 0, 0, 1];
|
|
77
|
+
held = false;
|
|
78
|
+
/** Every release `endHold` received, in order. */
|
|
79
|
+
releases = [];
|
|
80
|
+
/** The last effect written. */
|
|
81
|
+
effect = {};
|
|
82
|
+
constructor(rest) {
|
|
83
|
+
this.rest = copyPose(rest);
|
|
84
|
+
this.live = copyPose(rest);
|
|
85
|
+
}
|
|
86
|
+
getWorldPose() {
|
|
87
|
+
return copyPose(this.live);
|
|
88
|
+
}
|
|
89
|
+
getRestWorldPose() {
|
|
90
|
+
return copyPose(this.rest);
|
|
91
|
+
}
|
|
92
|
+
getLocalOffset() {
|
|
93
|
+
return [this.offset[0], this.offset[1], this.offset[2]];
|
|
94
|
+
}
|
|
95
|
+
setLocalOffset(offset) {
|
|
96
|
+
this.offset = [offset[0], offset[1], offset[2]];
|
|
97
|
+
this.live = { position: vAdd(this.rest.position, vApplyQuat(this.offset, this.rest.quaternion)), quaternion: quatMultiply(this.rest.quaternion, this.rotation) };
|
|
98
|
+
}
|
|
99
|
+
setLocalRotation(quaternion) {
|
|
100
|
+
this.rotation = [quaternion[0], quaternion[1], quaternion[2], quaternion[3]];
|
|
101
|
+
this.live = { position: vAdd(this.rest.position, vApplyQuat(this.offset, this.rest.quaternion)), quaternion: quatMultiply(this.rest.quaternion, this.rotation) };
|
|
102
|
+
}
|
|
103
|
+
setWorldPose(pose) {
|
|
104
|
+
this.live = copyPose(pose);
|
|
105
|
+
}
|
|
106
|
+
setEffect(effect) {
|
|
107
|
+
this.effect = { ...this.effect, ...effect };
|
|
108
|
+
}
|
|
109
|
+
recaptureRest() {
|
|
110
|
+
this.rest = copyPose(this.live);
|
|
111
|
+
this.offset = [0, 0, 0];
|
|
112
|
+
this.rotation = [0, 0, 0, 1];
|
|
113
|
+
}
|
|
114
|
+
beginHold() {
|
|
115
|
+
this.held = true;
|
|
116
|
+
}
|
|
117
|
+
endHold(release) {
|
|
118
|
+
this.held = false;
|
|
119
|
+
this.releases.push({ linearVelocity: [...release.linearVelocity], angularVelocity: [...release.angularVelocity] });
|
|
120
|
+
}
|
|
121
|
+
get isHeld() {
|
|
122
|
+
return this.held;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function copyPose(pose) {
|
|
126
|
+
return { position: [pose.position[0], pose.position[1], pose.position[2]], quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]] };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=memory-ports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-ports.js","sourceRoot":"","sources":["../src/memory-ports.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAqB,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGnG,sDAAsD;AACtD,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAWzC,sFAAsF;AACtF,MAAM,OAAO,eAAe;IACT,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAE3D,wFAAwF;IACxF,KAAK,CAAC,EAAU,EAAE,MAAiB,EAAE,SAAiB,qBAAqB,EAAE,IAA0B;QACrG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5H,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,UAAU,CAAC,EAAU,EAAE,OAAgB;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM;YAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IACvC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAEO,QAAQ,CAAC,MAAoB;QACnC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3E,CAAC;IAED,MAAM,CAAC,GAAa;QAClB,IAAI,IAAI,GAA2B,IAAI,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAS;YAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACtD,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,GAAG,MAAM,CAAC,MAAM;gBAAE,SAAS;YACjD,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACvC,mCAAmC;gBACnC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;gBAC3F,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC;gBACzB,IAAI,GAAG;oBACL,cAAc,EAAE,MAAM,CAAC,EAAE;oBACzB,QAAQ,EAAE,KAAK;oBACf,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;iBACtI,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,YAAY,CAAC,KAAgB,EAAE,MAAc;QAC3C,IAAI,IAAI,GAA2B,IAAI,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAS;YAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC1H,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpE,IAAI,GAAG,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;YACrH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,GAAa,EAAE,SAAiB,EAAE,SAAiB;QACzD,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAClE,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvG,CAAC;CACF;AAED,yFAAyF;AACzF,MAAM,OAAO,mBAAmB;IACtB,IAAI,CAAY;IAChB,IAAI,CAAY;IAChB,MAAM,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,QAAQ,GAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,CAAC;IACrB,kDAAkD;IACzC,QAAQ,GAAkB,EAAE,CAAC;IACtC,+BAA+B;IAC/B,MAAM,GAA0C,EAAE,CAAC;IAEnD,YAAY,IAAe;QACzB,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,YAAY;QACV,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,gBAAgB;QACd,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,cAAc;QACZ,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,cAAc,CAAC,MAAiB;QAC9B,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnK,CAAC;IAED,gBAAgB,CAAC,UAAqB;QACpC,IAAI,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnK,CAAC;IAED,YAAY,CAAC,IAAe;QAC1B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,SAAS,CAAC,MAA6C;QACrD,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9C,CAAC;IAED,aAAa;QACX,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,SAAS;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,OAAoB;QAC1B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,GAAG,OAAO,CAAC,cAAc,CAAc,EAAE,eAAe,EAAE,CAAC,GAAG,OAAO,CAAC,eAAe,CAAc,EAAE,CAAC,CAAC;IAC/I,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,IAAe;IAC/B,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5K,CAAC","sourcesContent":["/**\n * Engine-free ports over sphere targets: the reference `HitTester` and\n * `TransformPort` a trace replay, a synthetic recording, a harness run\n * without a host, or a test can drive with no engine at all. They apply the\n * contract exactly as every binding must (`hitTesterContractCases()` and\n * `transformPortContractCases()` pass over them), so a difference between a\n * binding and these is a difference in the binding.\n */\nimport type { PoseTuple, QuatTuple, RayTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport { coneHitForSpheres, type SphereTarget } from \"./gaze.js\";\nimport { quatMultiply, rayPointDistance, surfacePointOnSphere, vAdd, vApplyQuat } from \"./math.js\";\nimport type { HitTester, HoldRelease, InteractableHit, TransformPort } from \"./ports.js\";\n\n/** IWSDK's radius for a target that declares none. */\nexport const MEMORY_DEFAULT_RADIUS = 0.1;\n\ninterface MemoryTarget {\n id: string;\n /** The port the target's live pose is read from, when one was placed with a port. */\n port?: MemoryTransformPort;\n centre: Vec3Tuple;\n radius: number;\n visible: boolean;\n}\n\n/** A sphere-target hit tester: IWSDK's `EntityHitTester` rules over plain spheres. */\nexport class MemoryHitTester implements HitTester {\n private readonly targets = new Map<string, MemoryTarget>();\n\n /** Place a shown sphere target; a port makes its centre follow the port's live pose. */\n place(id: string, centre: Vec3Tuple, radius: number = MEMORY_DEFAULT_RADIUS, port?: MemoryTransformPort): void {\n this.targets.set(id, { id, centre: [centre[0], centre[1], centre[2]], radius, visible: true, ...(port ? { port } : {}) });\n }\n\n remove(id: string): void {\n this.targets.delete(id);\n }\n\n setVisible(id: string, visible: boolean): void {\n const target = this.targets.get(id);\n if (target) target.visible = visible;\n }\n\n clear(): void {\n this.targets.clear();\n }\n\n private centreOf(target: MemoryTarget): Vec3Tuple {\n return target.port ? target.port.getWorldPose().position : target.centre;\n }\n\n hitRay(ray: RayTuple): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const target of this.targets.values()) {\n if (!target.visible) continue;\n const centre = this.centreOf(target);\n const { distance, t } = rayPointDistance(ray, centre);\n if (t <= 0 || distance > target.radius) continue;\n if (best === null || t < best.distance) {\n // Where the ray enters the sphere.\n const inside = Math.sqrt(Math.max(0, target.radius * target.radius - distance * distance));\n const enter = t - inside;\n best = {\n interactableId: target.id,\n distance: enter,\n point: [ray.origin[0] + ray.direction[0] * enter, ray.origin[1] + ray.direction[1] * enter, ray.origin[2] + ray.direction[2] * enter],\n };\n }\n }\n return best;\n }\n\n hitProximity(point: Vec3Tuple, radius: number): InteractableHit | null {\n let best: InteractableHit | null = null;\n for (const target of this.targets.values()) {\n if (!target.visible) continue;\n const centre = this.centreOf(target);\n const surface = Math.max(0, Math.hypot(centre[0] - point[0], centre[1] - point[1], centre[2] - point[2]) - target.radius);\n if (surface <= radius && (best === null || surface < best.distance)) {\n best = { interactableId: target.id, distance: surface, point: surfacePointOnSphere(centre, target.radius, point) };\n }\n }\n return best;\n }\n\n hitCone(ray: RayTuple, halfAngle: number, maxLength: number): InteractableHit | null {\n const spheres: SphereTarget[] = [];\n for (const target of this.targets.values()) {\n if (target.visible) spheres.push({ id: target.id, center: this.centreOf(target), radius: target.radius });\n }\n const hit = coneHitForSpheres(ray, spheres, halfAngle, maxLength);\n return hit ? { interactableId: hit.interactableId, distance: hit.distance, point: hit.point } : null;\n }\n}\n\n/** A transform port over a pose it keeps itself, with the held-pose members recorded. */\nexport class MemoryTransformPort implements TransformPort {\n private rest: PoseTuple;\n private live: PoseTuple;\n private offset: Vec3Tuple = [0, 0, 0];\n private rotation: QuatTuple = [0, 0, 0, 1];\n private held = false;\n /** Every release `endHold` received, in order. */\n readonly releases: HoldRelease[] = [];\n /** The last effect written. */\n effect: { scale?: number; emissive?: number } = {};\n\n constructor(rest: PoseTuple) {\n this.rest = copyPose(rest);\n this.live = copyPose(rest);\n }\n\n getWorldPose(): PoseTuple {\n return copyPose(this.live);\n }\n\n getRestWorldPose(): PoseTuple {\n return copyPose(this.rest);\n }\n\n getLocalOffset(): Vec3Tuple {\n return [this.offset[0], this.offset[1], this.offset[2]];\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.offset = [offset[0], offset[1], offset[2]];\n this.live = { position: vAdd(this.rest.position, vApplyQuat(this.offset, this.rest.quaternion)), quaternion: quatMultiply(this.rest.quaternion, this.rotation) };\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.rotation = [quaternion[0], quaternion[1], quaternion[2], quaternion[3]];\n this.live = { position: vAdd(this.rest.position, vApplyQuat(this.offset, this.rest.quaternion)), quaternion: quatMultiply(this.rest.quaternion, this.rotation) };\n }\n\n setWorldPose(pose: PoseTuple): void {\n this.live = copyPose(pose);\n }\n\n setEffect(effect: { scale?: number; emissive?: number }): void {\n this.effect = { ...this.effect, ...effect };\n }\n\n recaptureRest(): void {\n this.rest = copyPose(this.live);\n this.offset = [0, 0, 0];\n this.rotation = [0, 0, 0, 1];\n }\n\n beginHold(): void {\n this.held = true;\n }\n\n endHold(release: HoldRelease): void {\n this.held = false;\n this.releases.push({ linearVelocity: [...release.linearVelocity] as Vec3Tuple, angularVelocity: [...release.angularVelocity] as Vec3Tuple });\n }\n\n get isHeld(): boolean {\n return this.held;\n }\n}\n\nfunction copyPose(pose: PoseTuple): PoseTuple {\n return { position: [pose.position[0], pose.position[1], pose.position[2]], quaternion: [pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]] };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HitTesterContractSubject } from "./contract-cases.js";
|
|
2
|
+
/** One check a binding's hit tester must pass under the near-pointer rule. */
|
|
3
|
+
export interface NearPointerContractCase {
|
|
4
|
+
name: string;
|
|
5
|
+
run(subject: HitTesterContractSubject): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The shared near-pointer conformance suite. An adapter's test file:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* for (const contractCase of nearPointerContractCases()) {
|
|
12
|
+
* it(contractCase.name, () => contractCase.run(makeSubject()));
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function nearPointerContractCases(): readonly NearPointerContractCase[];
|