1use itertools::Itertools;
5use nalgebra::{vector, Matrix3, Rotation3, Vector3};
6
7#[cfg(feature = "urdf")]
8use crate::to_rdf::to_urdf::ToURDF;
9#[cfg(feature = "xml")]
10use quick_xml::{events::attributes::Attribute, name::QName};
11
12#[derive(Debug, PartialEq, Clone, Copy, Default)]
13pub struct Transform {
24 pub translation: Option<(f32, f32, f32)>,
26 pub rotation: Option<(f32, f32, f32)>,
28}
29
30impl Transform {
31 pub fn new(xyz: (f32, f32, f32), rpy: (f32, f32, f32)) -> Self {
51 Self {
52 translation: Some(xyz),
53 rotation: Some(rpy),
54 }
55 }
56
57 pub fn new_translation(x: f32, y: f32, z: f32) -> Self {
76 Self {
77 translation: Some((x, y, z)),
78 ..Default::default()
79 }
80 }
81
82 pub fn new_rotation(r: f32, p: f32, y: f32) -> Self {
102 Self {
103 rotation: Some((r, p, y)),
104 ..Default::default()
105 }
106 }
107
108 pub fn contains_some(&self) -> bool {
136 self.translation.is_some() || self.rotation.is_some()
137 }
138}
139
140impl Mirror for Transform {
141 fn mirrored(&self, mirror_matrix: &Matrix3<f32>) -> Self {
142 Transform {
143 translation: self.translation.as_ref().map(|(x, y, z)| {
144 let old_translation = vector![*x, *y, *z];
145 (mirror_matrix * old_translation)
146 .component_mul(&Vector3::from_iterator(old_translation.iter().map(|val| {
147 if val.is_normal() {
148 1.
149 } else {
150 0.
151 }
152 }))) .iter()
154 .copied()
155 .collect_tuple()
156 .unwrap() }),
158 rotation: self.rotation,
159 }
160 }
161}
162
163impl MirrorUpdater for Transform {
164 fn update_mirror_matrix(&self, mirror_matrix: &Matrix3<f32>) -> Matrix3<f32> {
165 match self.rotation.as_ref() {
166 Some(rpy) => {
167 Rotation3::from_euler_angles(rpy.0, rpy.1, rpy.2)
168 * mirror_matrix * Rotation3::from_euler_angles(rpy.0, rpy.1, rpy.2).inverse()
169 }
170 None => *mirror_matrix,
171 }
172 }
173}
174
175#[cfg(feature = "urdf")]
176impl ToURDF for Transform {
177 fn to_urdf(
178 &self,
179 writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
180 _urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
181 ) -> Result<(), quick_xml::Error> {
182 let mut element = writer.create_element("origin");
183 if let Some(translation) = self.translation {
184 element = element.with_attribute(Attribute {
185 key: QName(b"xyz"),
186 value: format!("{} {} {}", translation.0, translation.1, translation.2)
187 .as_bytes()
188 .into(),
189 })
190 }
191
192 if let Some(rotation) = self.rotation {
193 element = element.with_attribute(Attribute {
194 key: QName(b"rpy"),
195 value: format!("{} {} {}", rotation.0, rotation.1, rotation.2)
196 .as_bytes()
197 .into(),
198 });
199 }
200
201 element.write_empty()?;
202 Ok(())
203 }
204}
205
206impl From<Transform> for crate::joint::JointTransformMode {
207 fn from(value: Transform) -> Self {
208 Self::Direct(value)
209 }
210}
211
212#[derive(Debug, PartialEq, Eq, Clone, Copy)]
214pub enum MirrorAxis {
215 X,
217 Y,
219 Z,
221}
222
223impl From<MirrorAxis> for Matrix3<f32> {
224 fn from(value: MirrorAxis) -> Self {
225 let diag = match value {
226 MirrorAxis::X => (-1., 1., 1.),
227 MirrorAxis::Y => (1., -1., 1.),
228 MirrorAxis::Z => (1., 1., -1.),
229 };
230
231 Matrix3::from_diagonal(&Vector3::new(diag.0, diag.1, diag.2))
232 }
233}
234
235pub(crate) trait Mirror {
239 fn mirrored(&self, mirror_matrix: &Matrix3<f32>) -> Self;
243}
244
245pub(crate) trait MirrorUpdater: Sized + Mirror {
251 fn update_mirror_matrix(&self, mirror_matrix: &Matrix3<f32>) -> Matrix3<f32>;
253
254 fn mirrored_update_matrix(&self, mirror_matrix: &Matrix3<f32>) -> (Self, Matrix3<f32>) {
256 (
257 self.mirrored(mirror_matrix),
258 self.update_mirror_matrix(mirror_matrix),
259 )
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::Transform;
266 use std::f32::consts::{FRAC_PI_2, FRAC_PI_4};
267 use test_log::test;
268
269 mod mirror {
270 use super::{test, *};
271 use crate::transform::{MirrorAxis, MirrorUpdater};
272 use nalgebra::{matrix, vector, Matrix3};
273
274 fn test_mirror(
275 transform: Transform,
276 mirror_axis: MirrorAxis,
277 result: (Transform, Matrix3<f32>),
278 ) {
279 assert_eq!(
280 transform.mirrored_update_matrix(&mirror_axis.into()),
281 result
282 )
283 }
284
285 fn test_all_mirrors(transform: Transform, results: [(Transform, Matrix3<f32>); 3]) {
286 results
287 .into_iter()
288 .enumerate()
289 .map(|(index, result)| {
290 (
291 match index {
292 0 => MirrorAxis::X,
293 1 => MirrorAxis::Y,
294 2 => MirrorAxis::Z,
295 _ => unreachable!(),
296 },
297 result,
298 )
299 })
300 .for_each(|(mirror_axis, result)| test_mirror(transform, mirror_axis, result))
301 }
302
303 fn test_all_mirrors_angle_var(
304 transform: Transform,
305 angle: f32,
306 results: [(Transform, [Matrix3<f32>; 3]); 3],
307 ) {
308 for i in 0..2 {
309 let rotation = match i {
310 0 => (angle, 0., 0.),
311 1 => (0., angle, 0.),
312 2 => (0., 0., angle),
313 _ => unreachable!(),
314 };
315
316 test_all_mirrors(
317 Transform {
318 rotation: Some(rotation),
319 ..transform.clone()
320 },
321 [
322 (
323 Transform {
324 rotation: Some(rotation),
325 ..results[0].0
326 },
327 results[0].1[i],
328 ),
329 (
330 Transform {
331 rotation: Some(rotation),
332 ..results[1].0
333 },
334 results[1].1[i],
335 ),
336 (
337 Transform {
338 rotation: Some(rotation),
339 ..results[2].0
340 },
341 results[2].1[i],
342 ),
343 ],
344 )
345 }
346 }
347
348 #[test]
349 fn uniaxial_no_rotation() {
350 test_all_mirrors(
352 Transform::new_translation(2., 0., 0.),
353 [
354 (
355 Transform {
356 translation: Some((-2., 0., 0.)),
357 rotation: None,
358 },
359 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
360 ),
361 (
362 Transform {
363 translation: Some((2., 0., 0.)),
364 rotation: None,
365 },
366 Matrix3::from_diagonal(&vector![1., -1., 1.]),
367 ),
368 (
369 Transform {
370 translation: Some((2., 0., 0.)),
371 rotation: None,
372 },
373 Matrix3::from_diagonal(&vector![1., 1., -1.]),
374 ),
375 ],
376 );
377
378 test_all_mirrors(
380 Transform::new_translation(0., 0.5, 0.),
381 [
382 (
383 Transform {
384 translation: Some((0., 0.5, 0.)),
385 rotation: None,
386 },
387 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
388 ),
389 (
390 Transform {
391 translation: Some((0., -0.5, 0.)),
392 rotation: None,
393 },
394 Matrix3::from_diagonal(&vector![1., -1., 1.]),
395 ),
396 (
397 Transform {
398 translation: Some((0., 0.5, 0.)),
399 rotation: None,
400 },
401 Matrix3::from_diagonal(&vector![1., 1., -1.]),
402 ),
403 ],
404 );
405
406 test_all_mirrors(
408 Transform::new_translation(0., 0., -900.),
409 [
410 (
411 Transform {
412 translation: Some((0., 0., -900.)),
413 rotation: None,
414 },
415 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
416 ),
417 (
418 Transform {
419 translation: Some((0., 0., -900.)),
420 rotation: None,
421 },
422 Matrix3::from_diagonal(&vector![1., -1., 1.]),
423 ),
424 (
425 Transform {
426 translation: Some((0., 0., 900.)),
427 rotation: None,
428 },
429 Matrix3::from_diagonal(&vector![1., 1., -1.]),
430 ),
431 ],
432 );
433 }
434
435 #[test]
436 #[ignore = "Is this necessary?"]
437 fn uniaxial_unirotation() {
438 test_all_mirrors_angle_var(
439 Transform::new_translation(2., 0., 0.),
440 FRAC_PI_2,
441 [
442 (
443 Transform::new_translation(-2., 0., 0.),
444 [
445 matrix![
446 -1., 0., 0.;
447 0., 1., 0.;
448 0., 0., 1.;
449 ],
450 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
451 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
452 ],
453 ),
454 (
455 Transform::new_translation(2., 0., 0.),
456 [
457 matrix![
458 1., 0. ,0.;
459 0., 1., 0.;
460 0. ,0. ,-1.;
461 ],
462 Matrix3::from_diagonal(&vector![1., -1., 1.]),
463 Matrix3::from_diagonal(&vector![1., -1., 1.]),
464 ],
465 ),
466 (
467 Transform::new_translation(2., 0., 0.),
468 [
469 Matrix3::from_diagonal(&vector![1., 1., -1.]),
470 Matrix3::from_diagonal(&vector![1., 1., -1.]),
471 Matrix3::from_diagonal(&vector![1., 1., -1.]),
472 ],
473 ),
474 ],
475 );
476
477 test_all_mirrors(
478 Transform::new_translation(2., 0., 0.),
479 [
480 (
481 Transform {
482 translation: Some((-2., 0., 0.)),
483 rotation: None,
484 },
485 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
486 ),
487 (
488 Transform {
489 translation: Some((2., 0., 0.)),
490 rotation: None,
491 },
492 Matrix3::from_diagonal(&vector![1., -1., 1.]),
493 ),
494 (
495 Transform {
496 translation: Some((2., 0., 0.)),
497 rotation: None,
498 },
499 Matrix3::from_diagonal(&vector![1., 1., -1.]),
500 ),
501 ],
502 );
503
504 test_all_mirrors(
506 Transform::new((0., 0.5, 0.), (FRAC_PI_2, 0., FRAC_PI_4)),
507 [
508 (
509 Transform {
510 translation: Some((0., 0.5, 0.)),
511 rotation: None,
512 },
513 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
514 ),
515 (
516 Transform {
517 translation: Some((0., -0.5, 0.)),
518 rotation: None,
519 },
520 Matrix3::from_diagonal(&vector![1., -1., 1.]),
521 ),
522 (
523 Transform {
524 translation: Some((0., 0.5, 0.)),
525 rotation: None,
526 },
527 Matrix3::from_diagonal(&vector![1., 1., -1.]),
528 ),
529 ],
530 );
531
532 test_all_mirrors(
534 Transform::new_translation(0., 0., -900.),
535 [
536 (
537 Transform {
538 translation: Some((0., 0., -900.)),
539 rotation: None,
540 },
541 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
542 ),
543 (
544 Transform {
545 translation: Some((0., 0., -900.)),
546 rotation: None,
547 },
548 Matrix3::from_diagonal(&vector![1., -1., 1.]),
549 ),
550 (
551 Transform {
552 translation: Some((0., 0., 900.)),
553 rotation: None,
554 },
555 Matrix3::from_diagonal(&vector![1., 1., -1.]),
556 ),
557 ],
558 );
559 }
560
561 #[test]
562 fn multiaxial_no_rotation() {
563 test_all_mirrors(
564 Transform::new_translation(2., 3., 0.),
565 [
566 (
567 Transform {
568 translation: Some((-2., 3., 0.)),
569 rotation: None,
570 },
571 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
572 ),
573 (
574 Transform {
575 translation: Some((2., -3., 0.)),
576 rotation: None,
577 },
578 Matrix3::from_diagonal(&vector![1., -1., 1.]),
579 ),
580 (
581 Transform {
582 translation: Some((2., 3., 0.)),
583 rotation: None,
584 },
585 Matrix3::from_diagonal(&vector![1., 1., -1.]),
586 ),
587 ],
588 );
589
590 test_all_mirrors(
591 Transform::new_translation(0., 0.5, 7.),
592 [
593 (
594 Transform {
595 translation: Some((0., 0.5, 7.)),
596 rotation: None,
597 },
598 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
599 ),
600 (
601 Transform {
602 translation: Some((0., -0.5, 7.)),
603 rotation: None,
604 },
605 Matrix3::from_diagonal(&vector![1., -1., 1.]),
606 ),
607 (
608 Transform {
609 translation: Some((0., 0.5, -7.)),
610 rotation: None,
611 },
612 Matrix3::from_diagonal(&vector![1., 1., -1.]),
613 ),
614 ],
615 );
616
617 test_all_mirrors(
618 Transform::new_translation(120., 0., -900.),
619 [
620 (
621 Transform {
622 translation: Some((-120., 0., -900.)),
623 rotation: None,
624 },
625 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
626 ),
627 (
628 Transform {
629 translation: Some((120., 0., -900.)),
630 rotation: None,
631 },
632 Matrix3::from_diagonal(&vector![1., -1., 1.]),
633 ),
634 (
635 Transform {
636 translation: Some((120., 0., 900.)),
637 rotation: None,
638 },
639 Matrix3::from_diagonal(&vector![1., 1., -1.]),
640 ),
641 ],
642 );
643
644 test_all_mirrors(
645 Transform::new_translation(3., 4., 5.0005),
646 [
647 (
648 Transform {
649 translation: Some((-3., 4., 5.0005)),
650 rotation: None,
651 },
652 Matrix3::from_diagonal(&vector![-1., 1., 1.]),
653 ),
654 (
655 Transform {
656 translation: Some((3., -4., 5.0005)),
657 rotation: None,
658 },
659 Matrix3::from_diagonal(&vector![1., -1., 1.]),
660 ),
661 (
662 Transform {
663 translation: Some((3., 4., -5.0005)),
664 rotation: None,
665 },
666 Matrix3::from_diagonal(&vector![1., 1., -1.]),
667 ),
668 ],
669 )
670 }
671
672 #[test]
673 #[ignore = "Is this necessary?"]
674 fn multiaxial_rotation() {
675 todo!()
676 }
677 }
678
679 #[cfg(feature = "urdf")]
680 mod to_urdf {
681 use super::{test, Transform};
682 use crate::to_rdf::to_urdf::{ToURDF, URDFConfig};
683 use std::io::Seek;
684
685 fn test_to_urdf_transform(transform: Transform, result: String, urdf_config: &URDFConfig) {
686 let mut writer = quick_xml::Writer::new(std::io::Cursor::new(Vec::new()));
687 assert!(transform.to_urdf(&mut writer, urdf_config).is_ok());
688
689 writer.get_mut().rewind().unwrap();
690 assert_eq!(
691 std::io::read_to_string(writer.into_inner()).unwrap(),
692 result
693 )
694 }
695
696 #[test]
697 fn translation_only() {
698 test_to_urdf_transform(
699 Transform {
700 translation: Some((1.2, 2.3, 3.4)),
701 ..Default::default()
702 },
703 String::from(r#"<origin xyz="1.2 2.3 3.4"/>"#),
704 &URDFConfig::default(),
705 );
706 }
707
708 #[test]
709 fn rotation_only() {
710 test_to_urdf_transform(
711 Transform {
712 rotation: Some((1.2, 2.3, 3.4)),
713 ..Default::default()
714 },
715 String::from(r#"<origin rpy="1.2 2.3 3.4"/>"#),
716 &URDFConfig::default(),
717 );
718 }
719
720 #[test]
721 fn translation_rotatation() {
722 test_to_urdf_transform(
723 Transform {
724 translation: Some((1.23, 2.34, 3.45)),
725 rotation: Some((4.56, 5.67, 6.78)),
726 },
727 String::from(r#"<origin xyz="1.23 2.34 3.45" rpy="4.56 5.67 6.78"/>"#),
728 &URDFConfig::default(),
729 );
730 }
731 }
732}