1use glam_traits_ext::{FloatAffine, FloatVec, TAffine3, TMat3, TVec3};
2
3#[cfg(debug_assertions)]
4use crate::DekeError;
5use crate::SRobotQ;
6
7use super::{
8 AAffine3, AMat3, AVec3, FKChain, FKScalar, check_finite, const_sin_cos, const_sin_cos_f64,
9};
10
11#[derive(Debug, Clone, Copy)]
12pub struct DHJoint<F: FKScalar = f32> {
13 pub a: F,
14 pub alpha: F,
15 pub d: F,
16 pub theta_offset: F,
17}
18
19#[derive(Debug, Clone)]
23pub struct DHChain<const N: usize, F: FKScalar = f32> {
24 a: [F; N],
25 d: [F; N],
26 sin_alpha: [F; N],
27 cos_alpha: [F; N],
28 theta_offset: [F; N],
29}
30
31impl<const N: usize> DHChain<N, f32> {
32 pub const fn new(joints: [DHJoint<f32>; N]) -> Self {
33 let mut a = [0.0; N];
34 let mut d = [0.0; N];
35 let mut sin_alpha = [0.0; N];
36 let mut cos_alpha = [0.0; N];
37 let mut theta_offset = [0.0; N];
38
39 let mut i = 0;
40 while i < N {
41 a[i] = joints[i].a;
42 d[i] = joints[i].d;
43 let (sa, ca) = const_sin_cos(joints[i].alpha);
44 sin_alpha[i] = sa;
45 cos_alpha[i] = ca;
46 theta_offset[i] = joints[i].theta_offset;
47 i += 1;
48 }
49
50 Self {
51 a,
52 d,
53 sin_alpha,
54 cos_alpha,
55 theta_offset,
56 }
57 }
58
59 pub const fn from_dh(params: &[[f64; N]; 4]) -> Self {
65 let mut a = [0.0f32; N];
66 let mut d = [0.0f32; N];
67 let mut sin_alpha = [0.0f32; N];
68 let mut cos_alpha = [0.0f32; N];
69 let mut theta_offset = [0.0f32; N];
70
71 let mut i = 0;
72 while i < N {
73 a[i] = params[0][i] as f32;
74 let (sa, ca) = const_sin_cos(params[1][i] as f32);
75 sin_alpha[i] = sa;
76 cos_alpha[i] = ca;
77 d[i] = params[2][i] as f32;
78 theta_offset[i] = params[3][i] as f32;
79 i += 1;
80 }
81
82 Self {
83 a,
84 d,
85 sin_alpha,
86 cos_alpha,
87 theta_offset,
88 }
89 }
90}
91
92impl<const N: usize> DHChain<N, f64> {
93 pub const fn new_f64(joints: [DHJoint<f64>; N]) -> Self {
95 let mut a = [0.0; N];
96 let mut d = [0.0; N];
97 let mut sin_alpha = [0.0; N];
98 let mut cos_alpha = [0.0; N];
99 let mut theta_offset = [0.0; N];
100
101 let mut i = 0;
102 while i < N {
103 a[i] = joints[i].a;
104 d[i] = joints[i].d;
105 let (sa, ca) = const_sin_cos_f64(joints[i].alpha);
106 sin_alpha[i] = sa;
107 cos_alpha[i] = ca;
108 theta_offset[i] = joints[i].theta_offset;
109 i += 1;
110 }
111
112 Self {
113 a,
114 d,
115 sin_alpha,
116 cos_alpha,
117 theta_offset,
118 }
119 }
120
121 pub const fn from_dh_f64(params: &[[f64; N]; 4]) -> Self {
124 let mut a = [0.0f64; N];
125 let mut d = [0.0f64; N];
126 let mut sin_alpha = [0.0f64; N];
127 let mut cos_alpha = [0.0f64; N];
128 let mut theta_offset = [0.0f64; N];
129
130 let mut i = 0;
131 while i < N {
132 a[i] = params[0][i];
133 let (sa, ca) = const_sin_cos_f64(params[1][i]);
134 sin_alpha[i] = sa;
135 cos_alpha[i] = ca;
136 d[i] = params[2][i];
137 theta_offset[i] = params[3][i];
138 i += 1;
139 }
140
141 Self {
142 a,
143 d,
144 sin_alpha,
145 cos_alpha,
146 theta_offset,
147 }
148 }
149}
150
151impl<const N: usize, F: FKScalar> DHChain<N, F> {
152 pub fn from_joints(joints: [DHJoint<F>; N]) -> Self {
156 let zero = F::zero();
157 let mut a = [zero; N];
158 let mut d = [zero; N];
159 let mut sin_alpha = [zero; N];
160 let mut cos_alpha = [zero; N];
161 let mut theta_offset = [zero; N];
162
163 for i in 0..N {
164 a[i] = joints[i].a;
165 d[i] = joints[i].d;
166 let (sa, ca) = joints[i].alpha.sin_cos();
167 sin_alpha[i] = sa;
168 cos_alpha[i] = ca;
169 theta_offset[i] = joints[i].theta_offset;
170 }
171
172 Self {
173 a,
174 d,
175 sin_alpha,
176 cos_alpha,
177 theta_offset,
178 }
179 }
180}
181
182impl<const N: usize, F: FKScalar> FKChain<N, F> for DHChain<N, F> {
183 #[cfg(debug_assertions)]
184 type Error = DekeError;
185 #[cfg(not(debug_assertions))]
186 type Error = std::convert::Infallible;
187
188 fn fk(&self, q: &SRobotQ<N, F>) -> Result<[AAffine3<F>; N], Self::Error> {
195 check_finite::<N, F>(q)?;
196 let mut out = [AAffine3::<F>::IDENTITY; N];
197 let mut c0 = AVec3::<F>::X;
198 let mut c1 = AVec3::<F>::Y;
199 let mut c2 = AVec3::<F>::Z;
200 let mut t = AVec3::<F>::ZERO;
201
202 let mut i = 0;
203 while i < N {
204 let (st, ct) = (q.0[i] + self.theta_offset[i]).sin_cos();
205 let sa = self.sin_alpha[i];
206 let ca = self.cos_alpha[i];
207
208 let new_c0 = c0 * ct + c1 * st;
209 let perp = c1 * ct - c0 * st;
210
211 let new_c1 = perp * ca + c2 * sa;
212 let new_c2 = c2 * ca - perp * sa;
213
214 t = new_c0 * self.a[i] + c2 * self.d[i] + t;
215
216 c0 = new_c0;
217 c1 = new_c1;
218 c2 = new_c2;
219
220 out[i] = AAffine3::<F>::from_mat3_translation(
221 AMat3::<F>::from_cols(c0, c1, c2),
222 t,
223 );
224 i += 1;
225 }
226 Ok(out)
227 }
228
229 fn fk_end(&self, q: &SRobotQ<N, F>) -> Result<AAffine3<F>, Self::Error> {
230 check_finite::<N, F>(q)?;
231 let mut c0 = AVec3::<F>::X;
232 let mut c1 = AVec3::<F>::Y;
233 let mut c2 = AVec3::<F>::Z;
234 let mut t = AVec3::<F>::ZERO;
235
236 let mut i = 0;
237 while i < N {
238 let (st, ct) = (q.0[i] + self.theta_offset[i]).sin_cos();
239 let sa = self.sin_alpha[i];
240 let ca = self.cos_alpha[i];
241
242 let new_c0 = c0 * ct + c1 * st;
243 let perp = c1 * ct - c0 * st;
244
245 let new_c1 = perp * ca + c2 * sa;
246 let new_c2 = c2 * ca - perp * sa;
247
248 t = new_c0 * self.a[i] + c2 * self.d[i] + t;
249
250 c0 = new_c0;
251 c1 = new_c1;
252 c2 = new_c2;
253 i += 1;
254 }
255
256 Ok(AAffine3::<F>::from_mat3_translation(
257 AMat3::<F>::from_cols(c0, c1, c2),
258 t,
259 ))
260 }
261
262 fn joint_axes_positions(
263 &self,
264 q: &SRobotQ<N, F>,
265 ) -> Result<([AVec3<F>; N], [AVec3<F>; N], AVec3<F>), Self::Error> {
266 let frames = self.fk(q)?;
267 let mut axes = [AVec3::<F>::Z; N];
268 let mut positions = [AVec3::<F>::ZERO; N];
269
270 for i in 1..N {
271 axes[i] = frames[i - 1].matrix3().z_axis();
272 positions[i] = frames[i - 1].translation();
273 }
274
275 Ok((axes, positions, frames[N - 1].translation()))
276 }
277}
278
279impl From<DHJoint<f32>> for DHJoint<f64> {
280 #[inline]
281 fn from(j: DHJoint<f32>) -> Self {
282 DHJoint {
283 a: j.a as f64,
284 alpha: j.alpha as f64,
285 d: j.d as f64,
286 theta_offset: j.theta_offset as f64,
287 }
288 }
289}
290
291impl From<DHJoint<f64>> for DHJoint<f32> {
292 #[inline]
293 fn from(j: DHJoint<f64>) -> Self {
294 DHJoint {
295 a: j.a as f32,
296 alpha: j.alpha as f32,
297 d: j.d as f32,
298 theta_offset: j.theta_offset as f32,
299 }
300 }
301}
302
303#[inline]
304fn cast_arr<const N: usize, A: Copy, B: Copy>(src: [A; N], cast: impl Fn(A) -> B) -> [B; N] {
305 std::array::from_fn(|i| cast(src[i]))
306}
307
308impl<const N: usize> From<DHChain<N, f32>> for DHChain<N, f64> {
309 #[inline]
310 fn from(c: DHChain<N, f32>) -> Self {
311 DHChain::<N, f64> {
312 a: cast_arr(c.a, |x| x as f64),
313 d: cast_arr(c.d, |x| x as f64),
314 sin_alpha: cast_arr(c.sin_alpha, |x| x as f64),
315 cos_alpha: cast_arr(c.cos_alpha, |x| x as f64),
316 theta_offset: cast_arr(c.theta_offset, |x| x as f64),
317 }
318 }
319}
320
321impl<const N: usize> From<DHChain<N, f64>> for DHChain<N, f32> {
322 #[inline]
323 fn from(c: DHChain<N, f64>) -> Self {
324 DHChain::<N, f32> {
325 a: cast_arr(c.a, |x| x as f32),
326 d: cast_arr(c.d, |x| x as f32),
327 sin_alpha: cast_arr(c.sin_alpha, |x| x as f32),
328 cos_alpha: cast_arr(c.cos_alpha, |x| x as f32),
329 theta_offset: cast_arr(c.theta_offset, |x| x as f32),
330 }
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use glam_traits_ext::TAffine3;
338
339 fn planar_2dof<F: FKScalar>() -> DHChain<2, F> {
340 let zero = F::zero();
341 let one = F::one();
342 DHChain::<2, F>::from_joints([
343 DHJoint { a: one, alpha: zero, d: zero, theta_offset: zero },
344 DHJoint { a: one, alpha: zero, d: zero, theta_offset: zero },
345 ])
346 }
347
348 #[test]
349 fn f32_and_f64_agree_at_zero() {
350 let f32_chain = planar_2dof::<f32>();
351 let f64_chain = planar_2dof::<f64>();
352
353 let q32 = SRobotQ::<2, f32>::zeros();
354 let q64 = SRobotQ::<2, f64>::zeros();
355
356 let end32 = f32_chain.fk_end(&q32).unwrap();
357 let end64 = f64_chain.fk_end(&q64).unwrap();
358
359 let t32 = end32.translation();
360 let t64 = end64.translation();
361 assert!((t32.x() as f64 - t64.x()).abs() < 1e-5);
362 assert!((t32.y() as f64 - t64.y()).abs() < 1e-5);
363 assert!((t32.z() as f64 - t64.z()).abs() < 1e-5);
364 }
365
366 #[test]
367 fn const_f64_constructor_matches_runtime_f64() {
368 const CHAIN_CONST: DHChain<2, f64> = DHChain::<2, f64>::new_f64([
369 DHJoint { a: 1.0, alpha: 0.0, d: 0.0, theta_offset: 0.0 },
370 DHJoint { a: 1.0, alpha: 0.0, d: 0.0, theta_offset: 0.0 },
371 ]);
372 let chain_runtime = planar_2dof::<f64>();
373 let q = SRobotQ::<2, f64>::from_array([0.5, -0.3]);
374 let end_const = CHAIN_CONST.fk_end(&q).unwrap().translation();
375 let end_runtime = chain_runtime.fk_end(&q).unwrap().translation();
376 assert!((end_const.x() - end_runtime.x()).abs() < 1e-12);
377 assert!((end_const.y() - end_runtime.y()).abs() < 1e-12);
378 }
379
380 #[test]
381 fn cast_f32_to_f64_and_back_preserves_fk() {
382 let f32_chain = planar_2dof::<f32>();
383 let f64_chain: DHChain<2, f64> = f32_chain.clone().into();
384 let f32_again: DHChain<2, f32> = f64_chain.clone().into();
385
386 let q32 = SRobotQ::<2, f32>::from_array([0.5, -0.3]);
387 let q64 = SRobotQ::<2, f64>::from_array([0.5, -0.3]);
388
389 let end32 = f32_chain.fk_end(&q32).unwrap().translation();
390 let end64 = f64_chain.fk_end(&q64).unwrap().translation();
391 let end32b = f32_again.fk_end(&q32).unwrap().translation();
392
393 assert!((end32.x() as f64 - end64.x()).abs() < 1e-4);
394 assert!((end32.y() as f64 - end64.y()).abs() < 1e-4);
395 assert_eq!(end32, end32b);
396 }
397}