reachy-blocks / src /util /math.ts
Claude
feat: SVG puppet, VirtualRobotAdapter, pose tables, test harness
6b2de60 unverified
Raw
History Blame Contribute Delete
2.68 kB
// Pure math helpers. Kept SDK-free so every layer except robot/real.ts
// (the single SDK-touching file) can use them in any test environment.
// Conventions match the SDK/daemon wire format: rotation is intrinsic
// ZYX (yaw about Z, then pitch about Y, then roll about X), matrices are
// row-major 4×4 homogeneous, angles on the wire are radians. A unit test
// cross-validates rpyToMatrixDeg/matrixToRpyDeg against the SDK's own
// rpyToMatrix/matrixToRpy.
export function degToRad(deg: number): number {
return (deg * Math.PI) / 180;
}
export function radToDeg(rad: number): number {
return (rad * 180) / Math.PI;
}
export function clamp(v: number, lo: number, hi: number): number {
return Math.min(hi, Math.max(lo, v));
}
export type Flat16 = number[];
/** Accepts a nested 4×4 (recorded-move JSON) or an already-flat array. */
export function flat16(m: number[][] | number[]): Flat16 {
if (typeof m[0] === "number") return m as number[];
return (m as number[][]).flat();
}
export function nest16(m: number[][] | number[]): number[][] {
if (typeof m[0] !== "number") return m as number[][];
const f = m as number[];
return [f.slice(0, 4), f.slice(4, 8), f.slice(8, 12), f.slice(12, 16)];
}
/** Degrees → flat row-major 4×4 rotation matrix (ZYX intrinsic). */
export function rpyToMatrixDeg(rollDeg: number, pitchDeg: number, yawDeg: number): Flat16 {
const r = degToRad(rollDeg);
const p = degToRad(pitchDeg);
const y = degToRad(yawDeg);
const cr = Math.cos(r), sr = Math.sin(r);
const cp = Math.cos(p), sp = Math.sin(p);
const cy = Math.cos(y), sy = Math.sin(y);
return [
cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, 0,
sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, 0,
-sp, cp * sr, cp * cr, 0,
0, 0, 0, 1,
];
}
export interface RpyDeg {
roll: number;
pitch: number;
yaw: number;
}
/** Flat or nested 4×4 rotation matrix → degrees (ZYX intrinsic). */
export function matrixToRpyDeg(m: number[][] | number[]): RpyDeg {
const f = flat16(m);
const m00 = f[0]!, m10 = f[4]!, m20 = f[8]!, m21 = f[9]!, m22 = f[10]!;
return {
roll: radToDeg(Math.atan2(m21, m22)),
pitch: radToDeg(-Math.asin(clamp(m20, -1, 1))),
yaw: radToDeg(Math.atan2(m10, m00)),
};
}
/**
* The daemon's head matrix is world-frame: to keep the head pointing
* "straight ahead relative to the body" while the body turns, the head
* matrix must carry body yaw + local head yaw.
*/
export function worldHeadYawDeg(bodyYawDeg: number, headYawLocalDeg: number): number {
return bodyYawDeg + headYawLocalDeg;
}