use super::*;
pub(super) fn quat_normalize(q: [f64; 4]) -> Result<[f64; 4], String> {
let n = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
if !(n.is_finite() && n > ASM_DIR_EPS) {
return Err("rotation quaternion must be finite and nonzero".into());
}
Ok([q[0] / n, q[1] / n, q[2] / n, q[3] / n])
}
pub(super) fn quat_mul(a: [f64; 4], b: [f64; 4]) -> [f64; 4] {
[
a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3],
a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2],
a[0] * b[2] - a[1] * b[3] + a[2] * b[0] + a[3] * b[1],
a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0],
]
}
pub(super) fn quat_rotate(q: [f64; 4], v: Vec3) -> Vec3 {
let u = Vec3::new(q[1], q[2], q[3]);
let uv = u.cross(v);
let uuv = u.cross(uv);
v.add(uv.scale(2.0 * q[0])).add(uuv.scale(2.0))
}
pub(super) fn quat_from_rotvec(w: Vec3) -> [f64; 4] {
let a = w.length();
let half = 0.5 * a;
let (c, k) = if a > 1e-8 {
(half.cos(), half.sin() / a)
} else {
(1.0 - half * half / 2.0, 0.5 - a * a / 48.0)
};
[c, k * w.x, k * w.y, k * w.z]
}
#[derive(Clone, Copy, Debug)]
pub(super) struct Pose {
pub(super) q: [f64; 4],
pub(super) t: Vec3,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct LocalPoint {
pub(super) body: usize,
pub(super) p: Vec3,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct LocalDir {
pub(super) body: usize,
pub(super) d: Vec3, }
fn world_point(poses: &[Pose], lp: LocalPoint) -> Vec3 {
quat_rotate(poses[lp.body].q, lp.p).add(poses[lp.body].t)
}
fn world_dir(poses: &[Pose], ld: LocalDir) -> Vec3 {
quat_rotate(poses[ld.body].q, ld.d)
}
#[derive(Clone, Copy, Debug)]
pub(super) enum Atom {
PointsCoincide(LocalPoint, LocalPoint),
PointOnPlane(LocalPoint, LocalPoint, LocalDir, f64),
DirMatch(LocalDir, LocalDir, f64),
DirCross(LocalDir, LocalDir),
DirDot(LocalDir, LocalDir, f64),
PointDistance(LocalPoint, LocalPoint, f64),
AxisLateral(LocalPoint, LocalPoint, LocalDir),
PointLineDistance(LocalPoint, LocalPoint, LocalDir, f64),
LineLineDistance(LocalPoint, LocalDir, LocalPoint, LocalDir, f64),
LineLineTouch(LocalPoint, LocalDir, LocalPoint, LocalDir),
}
fn line_line_skew_weight(sin_angle: f64) -> f64 {
let x = (sin_angle - ASM_LL_SIN_PARALLEL) / (ASM_LL_SIN_SKEW - ASM_LL_SIN_PARALLEL);
let x = x.clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
struct LineLineFrame {
u: Vec3,
c: Vec3,
s2: f64,
v: Vec3,
beta: f64,
}
fn line_line_frame(
poses: &[Pose],
oa: LocalPoint,
da: LocalDir,
ob: LocalPoint,
db: LocalDir,
) -> LineLineFrame {
let woa = world_point(poses, oa);
let wda = world_dir(poses, da); let wob = world_point(poses, ob);
let wdb = world_dir(poses, db);
let u = woa.sub(wob);
let c = wda.cross(wdb);
let s2 = c.dot(c);
let v = u.sub(wdb.scale(wdb.dot(u)));
let beta = line_line_skew_weight(s2.sqrt());
LineLineFrame { u, c, s2, v, beta }
}
impl Atom {
pub(super) fn rows(&self) -> usize {
match self {
Atom::PointsCoincide(..)
| Atom::DirMatch(..)
| Atom::DirCross(..)
| Atom::AxisLateral(..)
| Atom::LineLineTouch(..) => 3,
Atom::PointOnPlane(..)
| Atom::DirDot(..)
| Atom::PointDistance(..)
| Atom::PointLineDistance(..)
| Atom::LineLineDistance(..) => 1,
}
}
pub(super) fn eff_rows(&self) -> usize {
match self {
Atom::PointsCoincide(..) => 3,
Atom::DirMatch(..) | Atom::DirCross(..) | Atom::AxisLateral(..) => 2,
Atom::PointOnPlane(..)
| Atom::DirDot(..)
| Atom::PointDistance(..)
| Atom::PointLineDistance(..)
| Atom::LineLineDistance(..)
| Atom::LineLineTouch(..) => 1,
}
}
pub(super) fn eval(&self, poses: &[Pose], out: &mut Vec<f64>) {
match *self {
Atom::PointsCoincide(a, b) => {
let wa = world_point(poses, a);
let wb = world_point(poses, b);
out.extend([wa.x - wb.x, wa.y - wb.y, wa.z - wb.z]);
}
Atom::PointOnPlane(p, o, n, target) => {
let wp = world_point(poses, p);
let wo = world_point(poses, o);
let wn = world_dir(poses, n);
out.push(wn.dot(wp.sub(wo)) - target);
}
Atom::DirMatch(a, b, sign) => {
let da = world_dir(poses, a);
let db = world_dir(poses, b);
out.extend([da.x - sign * db.x, da.y - sign * db.y, da.z - sign * db.z]);
}
Atom::DirCross(a, b) => {
let da = world_dir(poses, a);
let db = world_dir(poses, b);
let c = da.cross(db);
out.extend([c.x, c.y, c.z]);
}
Atom::DirDot(a, b, target) => {
let da = world_dir(poses, a);
let db = world_dir(poses, b);
out.push(da.dot(db) - target);
}
Atom::PointDistance(a, b, target) => {
let wa = world_point(poses, a);
let wb = world_point(poses, b);
out.push(wa.sub(wb).length() - target);
}
Atom::AxisLateral(p, o, d) => {
let wp = world_point(poses, p);
let wo = world_point(poses, o);
let wd = world_dir(poses, d); let v = wp.sub(wo);
let lateral = v.sub(wd.scale(wd.dot(v)));
out.extend([lateral.x, lateral.y, lateral.z]);
}
Atom::PointLineDistance(p, o, d, target) => {
let wp = world_point(poses, p);
let wo = world_point(poses, o);
let wd = world_dir(poses, d); let v = wp.sub(wo);
let lateral = v.sub(wd.scale(wd.dot(v)));
out.push(lateral.length() - target);
}
Atom::LineLineDistance(oa, da, ob, db, target) => {
let f = line_line_frame(poses, oa, da, ob, db);
let d_par2 = f.v.dot(f.v);
let d2 = if f.beta > 0.0 {
let triple = f.u.dot(f.c);
f.beta * (triple * triple / f.s2) + (1.0 - f.beta) * d_par2
} else {
d_par2
};
out.push(d2.sqrt() - target);
}
Atom::LineLineTouch(oa, da, ob, db) => {
let f = line_line_frame(poses, oa, da, ob, db);
let r = if f.beta > 0.0 {
let c_hat = f.c.scale(1.0 / f.s2.sqrt());
c_hat
.scale(f.beta * f.u.dot(c_hat))
.add(f.v.scale(1.0 - f.beta))
} else {
f.v
};
out.extend([r.x, r.y, r.z]);
}
}
}
}