#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::similar_names,
clippy::too_many_arguments,
clippy::doc_markdown,
clippy::many_single_char_names,
clippy::missing_panics_doc,
clippy::float_cmp,
clippy::useless_vec
)]
use roxlap_formats::kfa::{Hinge, KfaSprite, Point3};
use roxlap_formats::sprite::Sprite;
use roxlap_formats::xform::BoneXform;
use crate::camera_math::CameraState;
use crate::opticast::OpticastSettings;
use crate::sprite::{draw_sprite, mat2, DrawTarget, SpriteLighting};
fn genperp(a: [f32; 3]) -> ([f32; 3], [f32; 3]) {
if a == [0.0, 0.0, 0.0] {
return ([0.0; 3], [0.0; 3]);
}
let ax = a[0].abs();
let ay = a[1].abs();
let az = a[2].abs();
let b = if ax < ay && ax < az {
let t = 1.0 / (a[1] * a[1] + a[2] * a[2]).sqrt();
[0.0, a[2] * t, -a[1] * t]
} else if ay < az {
let t = 1.0 / (a[0] * a[0] + a[2] * a[2]).sqrt();
[-a[2] * t, 0.0, a[0] * t]
} else {
let t = 1.0 / (a[0] * a[0] + a[1] * a[1]).sqrt();
[a[1] * t, -a[0] * t, 0.0]
};
let c = [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
];
(b, c)
}
fn mat0(
b_s: [f32; 3],
b_h: [f32; 3],
b_f: [f32; 3],
b_o: [f32; 3],
c_s: [f32; 3],
c_h: [f32; 3],
c_f: [f32; 3],
c_o: [f32; 3],
) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
let ts = [
b_s[0] * c_s[0] + b_h[0] * c_h[0] + b_f[0] * c_f[0],
b_s[0] * c_s[1] + b_h[0] * c_h[1] + b_f[0] * c_f[1],
b_s[0] * c_s[2] + b_h[0] * c_h[2] + b_f[0] * c_f[2],
];
let th = [
b_s[1] * c_s[0] + b_h[1] * c_h[0] + b_f[1] * c_f[0],
b_s[1] * c_s[1] + b_h[1] * c_h[1] + b_f[1] * c_f[1],
b_s[1] * c_s[2] + b_h[1] * c_h[2] + b_f[1] * c_f[2],
];
let tf = [
b_s[2] * c_s[0] + b_h[2] * c_h[0] + b_f[2] * c_f[0],
b_s[2] * c_s[1] + b_h[2] * c_h[1] + b_f[2] * c_f[1],
b_s[2] * c_s[2] + b_h[2] * c_h[2] + b_f[2] * c_f[2],
];
let to = [
c_o[0] - b_o[0] * ts[0] - b_o[1] * th[0] - b_o[2] * tf[0],
c_o[1] - b_o[0] * ts[1] - b_o[1] * th[1] - b_o[2] * tf[1],
c_o[2] - b_o[0] * ts[2] - b_o[1] * th[2] - b_o[2] * tf[2],
];
(ts, th, tf, to)
}
#[inline]
fn pt(p: Point3) -> [f32; 3] {
[p.x, p.y, p.z]
}
fn setlimb(limbs: &mut [Sprite], hinges: &[Hinge], i: usize, parent: usize, xform: &BoneXform) {
let p = &limbs[parent];
let (cs, ch, cf, co) = limb_xform((p.s, p.h, p.f, p.p), &hinges[i], xform);
let child = &mut limbs[i];
child.s = cs;
child.h = ch;
child.f = cf;
child.p = co;
}
fn limb_xform(
parent: ([f32; 3], [f32; 3], [f32; 3], [f32; 3]),
hinge: &Hinge,
xform: &BoneXform,
) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
let (parent_s, parent_h, parent_f, parent_p) = parent;
let qp0 = pt(hinge.p[0]);
let qp = [
qp0[0] + xform.t[0],
qp0[1] + xform.t[1],
qp0[2] + xform.t[2],
];
let qs0 = pt(hinge.v[0]);
let (qh0, qf0) = genperp(qs0);
let qs = xform.r.rotate(qs0);
let qh = xform.r.rotate(qh0);
let qf = xform.r.rotate(qf0);
let pp = pt(hinge.p[1]);
let ps = pt(hinge.v[1]);
let (ph, pf) = genperp(ps);
let (rs, rh, rf, ro) = mat0(ps, ph, pf, pp, qs, qh, qf, qp);
let (cs, ch, cf, co) = mat2(parent_s, parent_h, parent_f, parent_p, rs, rh, rf, ro);
(
[cs[0] * xform.s[0], cs[1] * xform.s[0], cs[2] * xform.s[0]],
[ch[0] * xform.s[1], ch[1] * xform.s[1], ch[2] * xform.s[1]],
[cf[0] * xform.s[2], cf[1] * xform.s[2], cf[2] * xform.s[2]],
co,
)
}
pub fn draw_kfa_sprite(
target: &mut DrawTarget<'_>,
cam: &CameraState,
settings: &OpticastSettings,
lighting: &SpriteLighting<'_>,
kfa: &mut KfaSprite,
) -> u32 {
solve_kfa_limbs(kfa);
let n = kfa.hinge_sort.len();
let mut total: u32 = 0;
for k in (0..n).rev() {
let j = kfa.hinge_sort[k];
total += draw_sprite(target, cam, settings, lighting, &kfa.limbs[j]);
}
total
}
pub fn solve_kfa_limbs(kfa: &mut KfaSprite) {
let n = kfa.hinge_sort.len();
for k in (0..n).rev() {
let j = kfa.hinge_sort[k];
let parent = kfa.hinges[j].parent;
if parent >= 0 {
let htype = kfa.hinges[j].htype;
let xform = if htype == 0 {
kfa.kfaval[j]
} else {
BoneXform::IDENTITY
};
setlimb(&mut kfa.limbs, &kfa.hinges, j, parent as usize, &xform);
} else {
let s = kfa.s;
let h = kfa.h;
let f = kfa.f;
let p_world = kfa.p;
let tp = pt(kfa.hinges[j].p[0]);
let limb = &mut kfa.limbs[j];
limb.s = s;
limb.h = h;
limb.f = f;
limb.p = [
p_world[0] - tp[0] * s[0] - tp[1] * h[0] - tp[2] * f[0],
p_world[1] - tp[0] * s[1] - tp[1] * h[1] - tp[2] * f[1],
p_world[2] - tp[0] * s[2] - tp[1] * h[2] - tp[2] * f[2],
];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn genperp_orthonormal() {
let a = [1.0_f32, 0.0, 0.0];
let (b, c) = genperp(a);
assert!((a[0] * b[0] + a[1] * b[1] + a[2] * b[2]).abs() < 1e-6);
assert!((a[0] * c[0] + a[1] * c[1] + a[2] * c[2]).abs() < 1e-6);
assert!((b[0] * c[0] + b[1] * c[1] + b[2] * c[2]).abs() < 1e-6);
let lb = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
assert!((lb - 1.0).abs() < 1e-5, "|b|² = {lb}");
let lc = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
assert!((lc - 1.0).abs() < 1e-5, "|c|² = {lc}");
}
#[test]
fn genperp_zero() {
let (b, c) = genperp([0.0, 0.0, 0.0]);
assert_eq!(b, [0.0, 0.0, 0.0]);
assert_eq!(c, [0.0, 0.0, 0.0]);
}
fn legacy_limb_xform(
parent: ([f32; 3], [f32; 3], [f32; 3], [f32; 3]),
hinge: &Hinge,
val: i16,
) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
let (ps0, ph0, pf0, pp0) = parent;
let qp = pt(hinge.p[0]);
let qs = pt(hinge.v[0]);
let (mut qh, mut qf) = genperp(qs);
let ang = (i32::from(val) as f32) * (std::f32::consts::PI * 2.0 / 65536.0);
let (c, s) = (ang.cos(), ang.sin());
let (ih, jf) = (qh, qf);
qh = [
ih[0] * c - jf[0] * s,
ih[1] * c - jf[1] * s,
ih[2] * c - jf[2] * s,
];
qf = [
ih[0] * s + jf[0] * c,
ih[1] * s + jf[1] * c,
ih[2] * s + jf[2] * c,
];
let pp = pt(hinge.p[1]);
let ps = pt(hinge.v[1]);
let (ph, pf) = genperp(ps);
let (rs, rh, rf, ro) = mat0(ps, ph, pf, pp, qs, qh, qf, qp);
mat2(ps0, ph0, pf0, pp0, rs, rh, rf, ro)
}
#[test]
fn trs_solver_matches_the_legacy_hinge_rotation() {
let axis = Point3 {
x: 0.0,
y: 0.0,
z: 1.0,
};
let hinge = Hinge {
parent: 0,
p: [
Point3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
Point3 {
x: 6.0,
y: 0.0,
z: 0.0,
},
],
v: [axis, axis],
vmin: i16::MIN,
vmax: i16::MAX,
htype: 0,
filler: [0; 7],
};
let parent = (
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0],
);
let close = |a: [f32; 3], b: [f32; 3]| (0..3).all(|i| (a[i] - b[i]).abs() < 1e-4);
for val in [0i16, 8000, 16384, -16384, 30000, i16::MIN] {
let want = legacy_limb_xform(parent, &hinge, val);
let got = limb_xform(parent, &hinge, &BoneXform::from_hinge_angle(pt(axis), val));
assert!(
close(got.0, want.0),
"s mismatch at {val}: {:?} vs {:?}",
got.0,
want.0
);
assert!(close(got.1, want.1), "h mismatch at {val}");
assert!(close(got.2, want.2), "f mismatch at {val}");
assert!(close(got.3, want.3), "p mismatch at {val}");
}
}
}