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 all_fk(
263 &self,
264 q: &SRobotQ<N, F>,
265 ) -> Result<(AAffine3<F>, [AAffine3<F>; N], AAffine3<F>), Self::Error> {
266 let frames = self.fk(q)?;
267 let end = if N > 0 {
271 frames[N - 1]
272 } else {
273 AAffine3::<F>::IDENTITY
274 };
275 Ok((self.base_tf(), frames, end))
276 }
277
278 fn joint_axes_positions(
279 &self,
280 q: &SRobotQ<N, F>,
281 ) -> Result<([AVec3<F>; N], [AVec3<F>; N], AVec3<F>), Self::Error> {
282 let frames = self.fk(q)?;
283 let mut axes = [AVec3::<F>::Z; N];
284 let mut positions = [AVec3::<F>::ZERO; N];
285
286 for i in 1..N {
287 axes[i] = frames[i - 1].matrix3().z_axis();
288 positions[i] = frames[i - 1].translation();
289 }
290
291 Ok((axes, positions, frames[N - 1].translation()))
292 }
293}
294
295impl From<DHJoint<f32>> for DHJoint<f64> {
296 #[inline]
297 fn from(j: DHJoint<f32>) -> Self {
298 DHJoint {
299 a: j.a as f64,
300 alpha: j.alpha as f64,
301 d: j.d as f64,
302 theta_offset: j.theta_offset as f64,
303 }
304 }
305}
306
307impl From<DHJoint<f64>> for DHJoint<f32> {
308 #[inline]
309 fn from(j: DHJoint<f64>) -> Self {
310 DHJoint {
311 a: j.a as f32,
312 alpha: j.alpha as f32,
313 d: j.d as f32,
314 theta_offset: j.theta_offset as f32,
315 }
316 }
317}
318
319#[inline]
320fn cast_arr<const N: usize, A: Copy, B: Copy>(src: [A; N], cast: impl Fn(A) -> B) -> [B; N] {
321 std::array::from_fn(|i| cast(src[i]))
322}
323
324impl<const N: usize> From<DHChain<N, f32>> for DHChain<N, f64> {
325 #[inline]
326 fn from(c: DHChain<N, f32>) -> Self {
327 DHChain::<N, f64> {
328 a: cast_arr(c.a, |x| x as f64),
329 d: cast_arr(c.d, |x| x as f64),
330 sin_alpha: cast_arr(c.sin_alpha, |x| x as f64),
331 cos_alpha: cast_arr(c.cos_alpha, |x| x as f64),
332 theta_offset: cast_arr(c.theta_offset, |x| x as f64),
333 }
334 }
335}
336
337impl<const N: usize> From<DHChain<N, f64>> for DHChain<N, f32> {
338 #[inline]
339 fn from(c: DHChain<N, f64>) -> Self {
340 DHChain::<N, f32> {
341 a: cast_arr(c.a, |x| x as f32),
342 d: cast_arr(c.d, |x| x as f32),
343 sin_alpha: cast_arr(c.sin_alpha, |x| x as f32),
344 cos_alpha: cast_arr(c.cos_alpha, |x| x as f32),
345 theta_offset: cast_arr(c.theta_offset, |x| x as f32),
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use glam_traits_ext::TAffine3;
354
355 fn planar_2dof<F: FKScalar>() -> DHChain<2, F> {
356 let zero = F::zero();
357 let one = F::one();
358 DHChain::<2, F>::from_joints([
359 DHJoint { a: one, alpha: zero, d: zero, theta_offset: zero },
360 DHJoint { a: one, alpha: zero, d: zero, theta_offset: zero },
361 ])
362 }
363
364 #[test]
365 fn f32_and_f64_agree_at_zero() {
366 let f32_chain = planar_2dof::<f32>();
367 let f64_chain = planar_2dof::<f64>();
368
369 let q32 = SRobotQ::<2, f32>::zeros();
370 let q64 = SRobotQ::<2, f64>::zeros();
371
372 let end32 = f32_chain.fk_end(&q32).unwrap();
373 let end64 = f64_chain.fk_end(&q64).unwrap();
374
375 let t32 = end32.translation();
376 let t64 = end64.translation();
377 assert!((t32.x() as f64 - t64.x()).abs() < 1e-5);
378 assert!((t32.y() as f64 - t64.y()).abs() < 1e-5);
379 assert!((t32.z() as f64 - t64.z()).abs() < 1e-5);
380 }
381
382 #[test]
383 fn const_f64_constructor_matches_runtime_f64() {
384 const CHAIN_CONST: DHChain<2, f64> = DHChain::<2, f64>::new_f64([
385 DHJoint { a: 1.0, alpha: 0.0, d: 0.0, theta_offset: 0.0 },
386 DHJoint { a: 1.0, alpha: 0.0, d: 0.0, theta_offset: 0.0 },
387 ]);
388 let chain_runtime = planar_2dof::<f64>();
389 let q = SRobotQ::<2, f64>::from_array([0.5, -0.3]);
390 let end_const = CHAIN_CONST.fk_end(&q).unwrap().translation();
391 let end_runtime = chain_runtime.fk_end(&q).unwrap().translation();
392 assert!((end_const.x() - end_runtime.x()).abs() < 1e-12);
393 assert!((end_const.y() - end_runtime.y()).abs() < 1e-12);
394 }
395
396 #[test]
397 fn cast_f32_to_f64_and_back_preserves_fk() {
398 let f32_chain = planar_2dof::<f32>();
399 let f64_chain: DHChain<2, f64> = f32_chain.clone().into();
400 let f32_again: DHChain<2, f32> = f64_chain.clone().into();
401
402 let q32 = SRobotQ::<2, f32>::from_array([0.5, -0.3]);
403 let q64 = SRobotQ::<2, f64>::from_array([0.5, -0.3]);
404
405 let end32 = f32_chain.fk_end(&q32).unwrap().translation();
406 let end64 = f64_chain.fk_end(&q64).unwrap().translation();
407 let end32b = f32_again.fk_end(&q32).unwrap().translation();
408
409 assert!((end32.x() as f64 - end64.x()).abs() < 1e-4);
410 assert!((end32.y() as f64 - end64.y()).abs() < 1e-4);
411 assert_eq!(end32, end32b);
412 }
413}