"""Quaternion math expressed as Expr trees. All functions take Expr nodes (typically Var nodes from a RigidBody's parameter set) and return Expr trees. Quaternion rotation is polynomial in the quaternion components — no transcendentals needed. Convention: quaternion (qw, qx, qy, qz) matches KCSolve's (w, x, y, z). """ from __future__ import annotations from .expr import Const, Expr def quat_rotate( qw: Expr, qx: Expr, qy: Expr, qz: Expr, px: Expr, py: Expr, pz: Expr, ) -> tuple[Expr, Expr, Expr]: """Rotate point (px, py, pz) by unit quaternion (qw, qx, qy, qz). Uses the standard formula: p' = q * p * q^{-1} expanded into component form (polynomial in q): rx = (1 - 2(qy^2 + qz^2)) * px + 2(qx*qy - qw*qz) * py + 2(qx*qz + qw*qy) * pz ry = 2(qx*qy + qw*qz) * px + (1 - 2(qx^2 + qz^2)) * py + 2(qy*qz - qw*qx) * pz rz = 2(qx*qz - qw*qy) * px + 2(qy*qz + qw*qx) * py + (1 - 2(qx^2 + qy^2)) * pz """ two = Const(2.0) rx = ( (Const(1.0) - two * (qy * qy + qz * qz)) * px + two * (qx * qy - qw * qz) * py + two * (qx * qz + qw * qy) * pz ) ry = ( two * (qx * qy + qw * qz) * px + (Const(1.0) - two * (qx * qx + qz * qz)) * py + two * (qy * qz - qw * qx) * pz ) rz = ( two * (qx * qz - qw * qy) * px + two * (qy * qz + qw * qx) * py + (Const(1.0) - two * (qx * qx + qy * qy)) * pz ) return rx, ry, rz def world_point( tx: Expr, ty: Expr, tz: Expr, qw: Expr, qx: Expr, qy: Expr, qz: Expr, lx: float, ly: float, lz: float, ) -> tuple[Expr, Expr, Expr]: """Transform local point (lx, ly, lz) to world coordinates. world = rotation(q, local) + translation """ clx, cly, clz = Const(lx), Const(ly), Const(lz) rx, ry, rz = quat_rotate(qw, qx, qy, qz, clx, cly, clz) return tx + rx, ty + ry, tz + rz