owb_core/utils/math/
kinematics.rs1use core::f32::consts::PI;
15use libm;
16
17pub struct EmbodiedKinematics {
19 wheel_radius: f32,
21 robot_radius: f32,
23 wheel_angles: [f32; 3],
25}
26
27impl EmbodiedKinematics {
28 pub fn new(
30 wheel_radius: f32,
31 robot_radius: f32,
32 ) -> Self {
33 let wheel_angles = [PI / 3.0, PI, 5.0 * PI / 3.0];
34 Self {
35 wheel_radius,
36 robot_radius,
37 wheel_angles,
38 }
39 }
40
41 pub fn convert_to_body_frame(
46 speed: f32,
47 angle: f32,
48 orientation: f32,
49 ) -> (f32, f32) {
50 let a = angle * (PI / 180.0);
51 let o = orientation * (PI / 180.0);
52 let vx = speed * libm::cosf(a - o);
53 let vy = speed * libm::sinf(a - o);
54 (-vy, vx)
55 }
56
57 pub fn construct_jacobian(&self) -> [[f32; 3]; 3] {
59 let r = self.wheel_radius;
60 let l = self.robot_radius;
61 let mut j = [[0.0; 3]; 3];
62 for (i, &t) in self.wheel_angles.iter().enumerate() {
63 j[i][0] = libm::cosf(t) / r;
64 j[i][1] = libm::sinf(t) / r;
65 j[i][2] = l / r;
66 }
67 j
68 }
69
70 pub fn compute_body_velocity(
76 &self,
77 wheel_velocity: [f32; 3],
78 ) -> (f32, f32, f32) {
79 let j = self.construct_jacobian();
80 let inv = invert_3x3(j);
81 let vx = inv[0][0] * wheel_velocity[0]
82 + inv[0][1] * wheel_velocity[1]
83 + inv[0][2] * wheel_velocity[2];
84 let vy = inv[1][0] * wheel_velocity[0]
85 + inv[1][1] * wheel_velocity[1]
86 + inv[1][2] * wheel_velocity[2];
87 let w = inv[2][0] * wheel_velocity[0]
88 + inv[2][1] * wheel_velocity[1]
89 + inv[2][2] * wheel_velocity[2];
90 (vx, vy, w)
91 }
92
93 pub fn compute_wheel_velocities(
98 &self,
99 speed: f32,
100 angle: f32,
101 orientation: f32,
102 omega: f32,
103 ) -> [f32; 3] {
104 let (vx, vy) = Self::convert_to_body_frame(speed, angle, orientation);
105 let v = [vx, vy, omega];
106 let j = self.construct_jacobian();
107 let mut out = [0.0; 3];
108 fn clamp_small(
109 v: f32,
110 eps: f32,
111 ) -> f32 {
112 if v.abs() < eps {
113 0.0
114 } else {
115 v
116 }
117 }
118 for i in 0..3 {
119 out[i] = clamp_small(j[i][0] * v[0] + j[i][1] * v[1] + j[i][2] * v[2], 1e-6);
120 }
121 out
122 }
123}
124
125fn invert_3x3(m: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
131 let det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
132 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
133 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
134 let inv_det = 1.0 / det;
135 [
136 [
137 (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * inv_det,
138 -(m[0][1] * m[2][2] - m[0][2] * m[2][1]) * inv_det,
139 (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * inv_det,
140 ],
141 [
142 -(m[1][0] * m[2][2] - m[1][2] * m[2][0]) * inv_det,
143 (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * inv_det,
144 -(m[0][0] * m[1][2] - m[0][2] * m[1][0]) * inv_det,
145 ],
146 [
147 (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * inv_det,
148 -(m[0][0] * m[2][1] - m[0][1] * m[2][0]) * inv_det,
149 (m[0][0] * m[1][1] - m[0][1] * m[1][0]) * inv_det,
150 ],
151 ]
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_convert_to_body_frame() {
160 let (vx, vy) = EmbodiedKinematics::convert_to_body_frame(1.0, 0.0, 0.0);
162 assert!((vx - 0.0).abs() < 1e-6);
163 assert!((vy - 1.0).abs() < 1e-6);
164 }
165
166 #[test]
167 fn test_invert_3x3_identity() {
168 let id = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
169 let inv = invert_3x3(id);
170 for i in 0..3 {
171 for j in 0..3 {
172 assert!(
173 (inv[i][j] - id[i][j]).abs() < 1e-6,
174 "inv != id at {}:{}",
175 i,
176 j
177 );
178 }
179 }
180 }
181
182 #[test]
183 fn test_compute_wheel_velocities_zero() {
184 let kin = EmbodiedKinematics::new(0.1, 0.2);
185 let wheels = kin.compute_wheel_velocities(0.0, 0.0, 0.0, 0.0);
186 assert_eq!(wheels, [0.0, 0.0, 0.0]);
187 }
188
189 #[test]
190 fn test_round_trip_body_velocity() {
191 let kin = EmbodiedKinematics::new(0.1, 0.2);
192 let speed = 1.23;
194 let angle = 45.0;
195 let orientation = 10.0;
196 let omega = 0.5;
197 let wheel_speeds = kin.compute_wheel_velocities(speed, angle, orientation, omega);
199 let (vx, vy, w) = kin.compute_body_velocity(wheel_speeds);
200 let (exp_vx, exp_vy) = EmbodiedKinematics::convert_to_body_frame(speed, angle, orientation);
202 assert!((vx - exp_vx).abs() < 1e-3);
203 assert!((vy - exp_vy).abs() < 1e-3);
204 assert!((w - omega).abs() < 1e-3);
205 }
206}