qualia_core_db/render/physics/
joint.rs1use crate::render::pga::{motor_mul, motor_translate, rotor_from_axis_angle, Motor};
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum JointKind {
13 Revolute { axis: [f32; 3] },
15 Prismatic { axis: [f32; 3] },
17}
18
19#[derive(Clone, Copy, Debug)]
21pub struct Joint {
22 pub kind: JointKind,
23 pub rate: f32,
24}
25
26impl Joint {
27 #[inline]
28 pub fn revolute(axis: [f32; 3], rate: f32) -> Self {
29 Joint {
30 kind: JointKind::Revolute { axis },
31 rate,
32 }
33 }
34
35 #[inline]
36 pub fn prismatic(axis: [f32; 3], rate: f32) -> Self {
37 Joint {
38 kind: JointKind::Prismatic { axis },
39 rate,
40 }
41 }
42
43 #[inline]
45 pub fn motor_at(&self, t: f32) -> Motor {
46 let q = self.rate * t;
47 match self.kind {
48 JointKind::Revolute { axis } => Motor::from_rotor(rotor_from_axis_angle(axis, q)),
49 JointKind::Prismatic { axis } => {
50 motor_translate([axis[0] * q, axis[1] * q, axis[2] * q])
51 }
52 }
53 }
54}
55
56pub fn chain_motor_at(joints: &[Joint], t: f32) -> Motor {
58 let mut m = Motor::identity();
59 for j in joints {
60 m = motor_mul(m, j.motor_at(t));
61 }
62 m
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::render::pga::sandwich_point;
69
70 fn approx(a: [f32; 3], b: [f32; 3]) -> bool {
71 (0..3).all(|k| (a[k] - b[k]).abs() < 1e-5)
72 }
73
74 #[test]
75 fn identity_at_t_zero() {
76 let rev = Joint::revolute([0.0, 1.0, 0.0], 1.5);
77 let p = [0.7, -0.2, 0.4];
78 assert!(approx(sandwich_point(rev.motor_at(0.0), p), p));
79 }
80
81 #[test]
82 fn revolute_rotates_over_t() {
83 let rev = Joint::revolute([0.0, 1.0, 0.0], std::f32::consts::FRAC_PI_2);
85 let out = sandwich_point(rev.motor_at(1.0), [1.0, 0.0, 0.0]);
86 assert!(out[0].abs() < 1e-5, "x≈0, got {out:?}");
87 assert!(out[1].abs() < 1e-5, "y≈0");
88 assert!(out[2].abs() > 0.9, "|z|≈1");
89 }
90
91 #[test]
92 fn prismatic_translates_over_t() {
93 let pri = Joint::prismatic([1.0, 0.0, 0.0], 2.0);
94 let out = sandwich_point(pri.motor_at(0.5), [0.0, 0.0, 0.0]);
95 assert!(approx(out, [1.0, 0.0, 0.0])); }
97
98 #[test]
99 fn motor_at_is_deterministic() {
100 let j = Joint::revolute([0.0, 0.0, 1.0], 0.9);
101 let a = sandwich_point(j.motor_at(0.33), [0.5, 0.1, 0.0]);
102 let b = sandwich_point(j.motor_at(0.33), [0.5, 0.1, 0.0]);
103 assert_eq!(a, b);
104 }
105
106 #[test]
107 fn chain_composes_two_joints() {
108 let chain = [
110 Joint::prismatic([1.0, 0.0, 0.0], 1.0),
111 Joint::revolute([0.0, 1.0, 0.0], 0.5),
112 ];
113 let out = sandwich_point(chain_motor_at(&chain, 1.0), [0.0, 0.0, 0.0]);
114 assert!(out[0] > 0.5);
115 }
116}