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, Debug)]
pub(super) enum Atom {
PointsCoincide(LocalPoint, LocalPoint),
PointOnPlane(LocalPoint, LocalPoint, LocalDir, f64),
DirMatch(LocalDir, LocalDir, f64),
DirCross(LocalDir, LocalDir),
DirDot(LocalDir, LocalDir, f64),
DirAngle(LocalDir, 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),
Limit(Measure, f64, f64),
SymPoints(LocalPoint, LocalPoint, LocalPoint, LocalDir),
SymDirMatch(LocalDir, LocalDir, LocalDir),
SymDirCross(LocalDir, LocalDir, LocalDir),
SymPointOnPlane(LocalPoint, LocalPoint, LocalDir, LocalPoint, LocalDir),
SymAxisLateral(LocalPoint, LocalPoint, LocalDir, LocalPoint, LocalDir),
GearPhase(Spin, Spin, f64, f64),
RackTravel(Spin, LocalPoint, LocalDir, Vec3, f64),
ScrewAdvance(LocalPoint, LocalDir, LocalDir, LocalDir, LocalPoint, f64, f64, f64),
PointOnCurve(LocalPoint, CurveRef),
AxisOnCurve(LocalPoint, LocalDir, CurveRef),
CurveAlong(LocalPoint, Option<LocalDir>, CurveRef, f64, bool),
CamContact(LocalPoint, Option<LocalDir>, CurveRef, LocalDir, f64, f64),
}
#[derive(Clone, Copy, Debug)]
pub(super) struct Spin {
pub(super) reference: LocalDir,
pub(super) axis: LocalDir,
pub(super) world: Vec3,
}
impl Spin {
fn angle(&self, poses: &[Pose]) -> f64 {
signed_angle(self.world, world_dir(poses, self.reference), world_dir(poses, self.axis))
}
}
pub(super) fn signed_angle(from: Vec3, to: Vec3, n: Vec3) -> f64 {
let from = from.sub(n.scale(from.dot(n)));
let to = to.sub(n.scale(to.dot(n)));
n.dot(from.cross(to)).atan2(from.dot(to))
}
fn wrap_angle(angle: f64) -> f64 {
angle.sin().atan2(angle.cos())
}
fn mirror_point(p: Vec3, o: Vec3, n: Vec3) -> Vec3 {
p.sub(n.scale(2.0 * p.sub(o).dot(n)))
}
fn mirror_dir(d: Vec3, n: Vec3) -> Vec3 {
d.sub(n.scale(2.0 * d.dot(n)))
}
#[derive(Clone, Copy, Debug)]
pub(super) enum Measure {
PointOnPlane(LocalPoint, LocalPoint, LocalDir),
DirDot(LocalDir, LocalDir),
DirAngle(LocalDir, LocalDir, LocalDir),
PointDistance(LocalPoint, LocalPoint),
PointLineDistance(LocalPoint, LocalPoint, LocalDir),
LineLineDistance(LocalPoint, LocalDir, LocalPoint, LocalDir),
}
impl Measure {
pub(super) fn of(atom: &Atom) -> Option<Self> {
Some(match *atom {
Atom::PointOnPlane(p, o, n, _) => Measure::PointOnPlane(p, o, n),
Atom::DirDot(a, b, _) => Measure::DirDot(a, b),
Atom::DirAngle(a, b, axis, _) => Measure::DirAngle(a, b, axis),
Atom::PointDistance(a, b, _) | Atom::PointsCoincide(a, b) => Measure::PointDistance(a, b),
Atom::PointLineDistance(p, o, d, _) | Atom::AxisLateral(p, o, d) => {
Measure::PointLineDistance(p, o, d)
}
Atom::LineLineDistance(oa, da, ob, db, _) | Atom::LineLineTouch(oa, da, ob, db) => {
Measure::LineLineDistance(oa, da, ob, db)
}
_ => return None,
})
}
fn circular(&self) -> bool {
matches!(self, Measure::DirAngle(..))
}
fn value(&self, poses: &[Pose]) -> f64 {
let atom = match *self {
Measure::PointOnPlane(p, o, n) => Atom::PointOnPlane(p, o, n, 0.0),
Measure::DirDot(a, b) => Atom::DirDot(a, b, 0.0),
Measure::DirAngle(a, b, axis) => Atom::DirAngle(a, b, axis, 0.0),
Measure::PointDistance(a, b) => Atom::PointDistance(a, b, 0.0),
Measure::PointLineDistance(p, o, d) => Atom::PointLineDistance(p, o, d, 0.0),
Measure::LineLineDistance(oa, da, ob, db) => Atom::LineLineDistance(oa, da, ob, db, 0.0),
};
let mut out = Vec::with_capacity(1);
atom.eval(poses, &mut out);
out[0]
}
}
pub(super) fn limit_residual(m: f64, lo: f64, hi: f64, circular: bool) -> f64 {
if circular {
let turn = std::f64::consts::TAU;
let rel = (m - lo).rem_euclid(turn);
let span = (hi - lo).rem_euclid(turn);
if rel <= span {
return 0.0;
}
let past_hi = rel - span;
let short_of_lo = turn - rel;
if past_hi <= short_of_lo { past_hi } else { -short_of_lo }
} else if m < lo {
m - lo
} else if m > hi {
m - hi
} else {
0.0
}
}
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(..)
| Atom::SymPoints(..)
| Atom::SymDirMatch(..)
| Atom::SymDirCross(..)
| Atom::SymAxisLateral(..)
| Atom::PointOnCurve(..)
| Atom::AxisOnCurve(..) => 3,
Atom::PointOnPlane(..)
| Atom::DirDot(..)
| Atom::DirAngle(..)
| Atom::PointDistance(..)
| Atom::PointLineDistance(..)
| Atom::LineLineDistance(..)
| Atom::Limit(..)
| Atom::SymPointOnPlane(..)
| Atom::GearPhase(..)
| Atom::RackTravel(..)
| Atom::ScrewAdvance(..)
| Atom::CurveAlong(..)
| Atom::CamContact(..) => 1,
}
}
pub(super) fn eff_rows(&self) -> usize {
match self {
Atom::PointsCoincide(..) | Atom::SymPoints(..) => 3,
Atom::DirMatch(..)
| Atom::DirCross(..)
| Atom::AxisLateral(..)
| Atom::SymDirMatch(..)
| Atom::SymDirCross(..)
| Atom::SymAxisLateral(..)
| Atom::PointOnCurve(..)
| Atom::AxisOnCurve(..) => 2,
Atom::CurveAlong(..) | Atom::CamContact(..) => 1,
Atom::SymPointOnPlane(..)
| Atom::GearPhase(..)
| Atom::RackTravel(..)
| Atom::ScrewAdvance(..) => 1,
Atom::PointOnPlane(..)
| Atom::DirDot(..)
| Atom::DirAngle(..)
| Atom::PointDistance(..)
| Atom::PointLineDistance(..)
| Atom::LineLineDistance(..)
| Atom::LineLineTouch(..) => 1,
Atom::Limit(..) => 0,
}
}
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::DirAngle(a, b, axis, target) => {
let da = world_dir(poses, a);
let db = world_dir(poses, b);
let n = world_dir(poses, axis);
let delta = n.dot(da.cross(db)).atan2(da.dot(db)) - target;
out.push(delta.sin().atan2(delta.cos()));
}
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]);
}
Atom::Limit(measure, lo, hi) => {
out.push(limit_residual(measure.value(poses), lo, hi, measure.circular()));
}
Atom::SymPoints(a, b, o, n) => {
let r = mirror_point(world_point(poses, a), world_point(poses, o), world_dir(poses, n))
.sub(world_point(poses, b));
out.extend([r.x, r.y, r.z]);
}
Atom::SymDirMatch(a, b, n) => {
let r = mirror_dir(world_dir(poses, a), world_dir(poses, n)).sub(world_dir(poses, b));
out.extend([r.x, r.y, r.z]);
}
Atom::SymDirCross(a, b, n) => {
let r = mirror_dir(world_dir(poses, a), world_dir(poses, n)).cross(world_dir(poses, b));
out.extend([r.x, r.y, r.z]);
}
Atom::SymPointOnPlane(pb, oa, na, o, n) => {
let (wo, wn) = (world_point(poses, o), world_dir(poses, n));
let origin = mirror_point(world_point(poses, oa), wo, wn);
let normal = mirror_dir(world_dir(poses, na), wn);
out.push(world_point(poses, pb).sub(origin).dot(normal));
}
Atom::GearPhase(a, b, ka, kb) => {
out.push(ka * a.angle(poses) + kb * b.angle(poses));
}
Atom::RackTravel(pinion, p, d, origin, k) => {
let travel = world_point(poses, p).sub(origin).dot(world_dir(poses, d));
out.push(travel - k * pinion.angle(poses));
}
Atom::ScrewAdvance(oa, da, ref_a, ref_b, pb, angle0, offset0, k) => {
let n = world_dir(poses, da);
let height = world_point(poses, pb).sub(world_point(poses, oa)).dot(n);
let turn = signed_angle(world_dir(poses, ref_a), world_dir(poses, ref_b), n);
out.push((height - offset0) - k * wrap_angle(turn - angle0));
}
Atom::PointOnCurve(p, ref curve) => eval_point_on_curve(poses, p, curve, out),
Atom::AxisOnCurve(p, d, ref curve) => eval_axis_on_curve(poses, p, d, curve, out),
Atom::CamContact(p, axis, ref curve, normal, radius, side) => {
eval_cam_contact(poses, p, axis, curve, normal, radius, side, out)
}
Atom::CurveAlong(p, d, ref curve, target, reverse) => {
eval_curve_along(poses, p, d, curve, target, reverse, out)
}
Atom::SymAxisLateral(oa, ob, db, o, n) => {
let v = mirror_point(world_point(poses, oa), world_point(poses, o), world_dir(poses, n))
.sub(world_point(poses, ob));
let d = world_dir(poses, db);
let lateral = v.sub(d.scale(d.dot(v)));
out.extend([lateral.x, lateral.y, lateral.z]);
}
}
}
}