use std::f32::consts::FRAC_PI_2;
use oxijolt::*;
use super::math::{add, cross, f3, scale, sub, v3, vec3, V3};
use super::quat_about;
use super::ragdoll::PARTS;
#[derive(Clone, Debug)]
pub struct RigJoint {
pub name: String,
pub parent: Option<String>,
pub position: V3,
pub rotation: Quat,
}
#[derive(Clone, Debug)]
pub struct Rig {
pub joints: Vec<RigJoint>,
}
const Y: Vec3 = Vec3::new(0.0, 1.0, 0.0);
fn bind_centre(name: &str) -> V3 {
let spec = PARTS.iter().find(|spec| spec.name == name).unwrap();
spec.centre.map(f64::from)
}
impl Rig {
pub fn humanoid() -> Self {
let turned = quat_about(Y, FRAC_PI_2);
let mapped =
|name: &'static str, parent: &'static str| (name, Some(parent), bind_centre(name));
let mut rig = Self { joints: Vec::new() };
let joints: [(&str, Option<&str>, V3); 22] = [
("root", None, [0.0, -0.9, 0.0]),
("pelvis", Some("root"), bind_centre("pelvis")),
("spine", Some("pelvis"), [0.0, 0.13, 0.0]),
mapped("abdomen", "spine"),
mapped("chest", "abdomen"),
("neck", Some("chest"), [0.0, 0.72, 0.0]),
mapped("head", "neck"),
("head_end", Some("head"), [0.0, 1.0, 0.0]),
("clavicle_l", Some("chest"), [0.12, 0.62, 0.0]),
mapped("upper_arm_l", "clavicle_l"),
mapped("forearm_l", "upper_arm_l"),
("hand_l", Some("forearm_l"), [0.70, 0.60, 0.0]),
("clavicle_r", Some("chest"), [-0.12, 0.62, 0.0]),
mapped("upper_arm_r", "clavicle_r"),
mapped("forearm_r", "upper_arm_r"),
("hand_r", Some("forearm_r"), [-0.70, 0.60, 0.0]),
mapped("thigh_l", "pelvis"),
mapped("shin_l", "thigh_l"),
("foot_l", Some("shin_l"), [0.09, -0.88, 0.05]),
mapped("thigh_r", "pelvis"),
mapped("shin_r", "thigh_r"),
("foot_r", Some("shin_r"), [-0.09, -0.88, 0.05]),
];
for (name, parent, position) in joints {
let rotation = if name == "pelvis" || name == "chest" {
turned
} else {
Quat::IDENTITY
};
rig.joints.push(RigJoint {
name: name.to_owned(),
parent: parent.map(str::to_owned),
position,
rotation,
});
}
rig
}
pub fn with_long_left_arm() -> Self {
let mut rig = Self::humanoid();
rig.insert_above("upper_arm_l", "shoulder_l", [0.19, 0.61, 0.0]);
rig
}
pub fn with_long_spine(count: usize) -> Self {
let mut rig = Self::humanoid();
rig.joints.retain(|joint| joint.name != "spine");
let abdomen = rig.index("abdomen");
rig.joints[abdomen].parent = Some("pelvis".to_owned());
let (from, to) = (bind_centre("pelvis"), bind_centre("abdomen"));
for i in 0..count {
let fraction = (i + 1) as f64 / (count + 1) as f64;
let position = add(from, scale(sub(to, from), fraction));
rig.insert_above("abdomen", &format!("spine_{i}"), position);
}
rig
}
pub fn insert_above(&mut self, child: &str, name: &str, position: V3) {
let index = self.index(child);
let parent = self.joints[index].parent.replace(name.to_owned());
self.joints.insert(
index,
RigJoint {
name: name.to_owned(),
parent,
position,
rotation: Quat::IDENTITY,
},
);
}
pub fn index(&self, name: &str) -> usize {
self.joints
.iter()
.position(|joint| joint.name == name)
.unwrap_or_else(|| panic!("no joint {name}"))
}
pub fn len(&self) -> usize {
self.joints.len()
}
pub fn parents(&self) -> Vec<Option<u32>> {
self.joints
.iter()
.map(|joint| joint.parent.as_deref().map(|name| self.index(name) as u32))
.collect()
}
pub fn skeleton(&self) -> Skeleton {
let parents = self.parents();
let joints: Vec<SkeletonJoint<'_>> = self
.joints
.iter()
.zip(&parents)
.map(|(joint, &parent)| SkeletonJoint {
name: &joint.name,
parent,
})
.collect();
Skeleton::new(&joints).unwrap()
}
pub fn neutral_pose(&self) -> SkeletonPose {
SkeletonPose {
root_offset: RVec3::ZERO,
joints: self
.joints
.iter()
.map(|joint| JointTransform {
translation: vec3(joint.position),
rotation: joint.rotation,
})
.collect(),
}
}
pub fn neutral_local(&self) -> Vec<JointTransform> {
let model = self.neutral_pose();
let world: Vec<Rigid> = (0..self.len())
.map(|j| Rigid::of(model.joints[j], RVec3::ZERO))
.collect();
self.parents()
.iter()
.enumerate()
.map(|(j, parent)| match parent {
None => world[j].joint(),
Some(parent) => world[*parent as usize].inverse().then(world[j]).joint(),
})
.collect()
}
pub fn animated_local(&self, t: f32) -> Vec<JointTransform> {
let mut local = self.neutral_local();
let bends: [(&str, Vec3, f32); 7] = [
("spine", Vec3::new(0.0, 0.0, 1.0), 0.3 * t.sin()),
("neck", Vec3::new(1.0, 0.0, 0.0), 0.4 * (1.3 * t).sin()),
("clavicle_l", Vec3::new(0.0, 0.0, 1.0), 0.2 * t.cos()),
("hand_l", Y, 0.5 * t.sin()),
("foot_r", Vec3::new(1.0, 0.0, 0.0), 0.3 * t.cos()),
("head", Vec3::new(1.0, 0.0, 0.0), 0.2 * t.sin()),
("upper_arm_r", Vec3::new(0.0, 0.0, 1.0), 0.4 * t.cos()),
];
for (name, axis, angle) in bends {
if let Some(index) = self.joints.iter().position(|joint| joint.name == name) {
let joint = &mut local[index];
joint.rotation = super::ragdoll::mul(joint.rotation, quat_about(axis, angle));
}
}
local
}
pub fn local_to_model(&self, local: &[JointTransform], root_offset: RVec3) -> SkeletonPose {
let parents = self.parents();
let mut world: Vec<Rigid> = Vec::with_capacity(local.len());
for (j, transform) in local.iter().enumerate() {
let own = Rigid::of(*transform, RVec3::ZERO);
world.push(match parents[j] {
None => Rigid::of(*transform, root_offset),
Some(parent) => world[parent as usize].then(own),
});
}
SkeletonPose {
root_offset,
joints: world.iter().map(|w| w.relative_to(root_offset)).collect(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Rigid {
pub rotation: [f64; 4],
pub translation: V3,
}
fn qmul(a: [f64; 4], b: [f64; 4]) -> [f64; 4] {
let ([ax, ay, az, aw], [bx, by, bz, bw]) = (a, b);
[
aw * bx + ax * bw + ay * bz - az * by,
aw * by - ax * bz + ay * bw + az * bx,
aw * bz + ax * by - ay * bx + az * bw,
aw * bw - ax * bx - ay * by - az * bz,
]
}
fn quat(q: [f64; 4]) -> Quat {
Quat::from_xyzw(q[0] as f32, q[1] as f32, q[2] as f32, q[3] as f32)
}
impl Rigid {
pub fn of(joint: JointTransform, root_offset: RVec3) -> Self {
let r = joint.rotation;
Self {
rotation: [r.x, r.y, r.z, r.w].map(f64::from),
translation: add(v3(root_offset), f3(joint.translation)),
}
}
pub fn at(pose: &SkeletonPose, index: usize) -> Self {
Self::of(pose.joints[index], pose.root_offset)
}
pub fn then(self, other: Self) -> Self {
Self {
rotation: qmul(self.rotation, other.rotation),
translation: add(self.translation, self.apply_vector(other.translation)),
}
}
pub fn inverse(self) -> Self {
let [x, y, z, w] = self.rotation;
let conjugate = [-x, -y, -z, w];
let rotated = Self {
rotation: conjugate,
translation: [0.0; 3],
}
.apply_vector(self.translation);
Self {
rotation: conjugate,
translation: [-rotated[0], -rotated[1], -rotated[2]],
}
}
pub fn apply_vector(self, v: V3) -> V3 {
let [x, y, z, w] = self.rotation;
let u = [x, y, z];
let t = scale(cross(u, v), 2.0);
add(add(v, scale(t, w)), cross(u, t))
}
pub fn relative_to(self, root_offset: RVec3) -> JointTransform {
JointTransform {
translation: vec3(sub(self.translation, v3(root_offset))),
rotation: quat(self.rotation),
}
}
pub fn joint(self) -> JointTransform {
self.relative_to(RVec3::ZERO)
}
pub fn difference(self, other: Self) -> (f64, f64) {
let d = sub(self.translation, other.translation);
let distance = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
let [x, y, z, w] = self.rotation;
let relative = qmul([-x, -y, -z, w], other.rotation);
let sin = (relative[0].powi(2) + relative[1].powi(2) + relative[2].powi(2)).sqrt();
(distance, 2.0 * sin.atan2(relative[3].abs()))
}
}