1use crate::CartesianTreeError;
2use crate::Pose;
3use crate::lazy_access::{LazyRotation, LazyTranslation};
4use crate::rotation::{MIN_QUATERNION_NORM, Rotation};
5use crate::tree::{HasChildren, HasParent, NodeEquality};
6
7use nalgebra::{Isometry3, Quaternion, Translation3, UnitQuaternion, Vector3};
8use serde::{Deserialize, Serialize};
9use slotmap::SlotMap;
10use std::ops::{Add, Mul, Sub};
11use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
12use uuid::Uuid;
13
14slotmap::new_key_type! {
15 pub(crate) struct NodeKey;
18}
19
20#[derive(Debug)]
22struct Node {
23 name: String,
25 parent: Option<NodeKey>,
27 children: Vec<NodeKey>,
29 transform_to_parent: Isometry3<f64>,
31}
32
33#[derive(Debug)]
35pub(crate) struct TreeInner {
36 nodes: SlotMap<NodeKey, Node>,
37}
38
39pub(crate) type SharedTree = Arc<RwLock<TreeInner>>;
40
41pub(crate) fn read_tree(tree: &SharedTree) -> RwLockReadGuard<'_, TreeInner> {
43 tree.read().unwrap_or_else(PoisonError::into_inner)
44}
45
46pub(crate) fn write_tree(tree: &SharedTree) -> RwLockWriteGuard<'_, TreeInner> {
48 tree.write().unwrap_or_else(PoisonError::into_inner)
49}
50
51impl TreeInner {
52 fn node(&self, key: NodeKey) -> Result<&Node, CartesianTreeError> {
53 self.nodes.get(key).ok_or(CartesianTreeError::FrameRemoved)
54 }
55
56 fn node_mut(&mut self, key: NodeKey) -> Result<&mut Node, CartesianTreeError> {
57 self.nodes
58 .get_mut(key)
59 .ok_or(CartesianTreeError::FrameRemoved)
60 }
61
62 pub(crate) fn contains(&self, key: NodeKey) -> bool {
63 self.nodes.contains_key(key)
64 }
65
66 pub(crate) fn name_of(&self, key: NodeKey) -> String {
67 self.nodes
68 .get(key)
69 .map_or_else(|| "<removed>".to_owned(), |node| node.name.clone())
70 }
71
72 fn add_child_node(
73 &mut self,
74 parent: NodeKey,
75 name: String,
76 transform: Isometry3<f64>,
77 ) -> Result<NodeKey, CartesianTreeError> {
78 let children = self.node(parent)?.children.clone();
79 if children
80 .iter()
81 .any(|&child| self.nodes.get(child).is_some_and(|node| node.name == name))
82 {
83 return Err(CartesianTreeError::ChildNameConflict(
84 name,
85 self.name_of(parent),
86 ));
87 }
88 let key = self.nodes.insert(Node {
89 name,
90 parent: Some(parent),
91 children: Vec::new(),
92 transform_to_parent: transform,
93 });
94 self.nodes[parent].children.push(key);
95 Ok(key)
96 }
97
98 fn remove_subtree(&mut self, key: NodeKey) {
99 if let Some(node) = self.nodes.remove(key) {
100 for child in node.children {
101 self.remove_subtree(child);
102 }
103 }
104 }
105
106 fn depth_of(&self, key: NodeKey) -> Result<usize, CartesianTreeError> {
107 let mut depth = 0;
108 let mut current = key;
109 while let Some(parent) = self.node(current)?.parent {
110 depth += 1;
111 current = parent;
112 }
113 Ok(depth)
114 }
115
116 pub(crate) fn lca(
118 &self,
119 a: NodeKey,
120 b: NodeKey,
121 ) -> Result<Option<NodeKey>, CartesianTreeError> {
122 let mut own = a;
123 let mut other = b;
124 let mut own_depth = self.depth_of(own)?;
125 let mut other_depth = self.depth_of(other)?;
126
127 while own_depth > other_depth {
128 own = self.node(own)?.parent.expect("depth guarantees a parent");
129 own_depth -= 1;
130 }
131 while other_depth > own_depth {
132 other = self.node(other)?.parent.expect("depth guarantees a parent");
133 other_depth -= 1;
134 }
135 while own != other {
136 match (self.node(own)?.parent, self.node(other)?.parent) {
137 (Some(own_parent), Some(other_parent)) => {
138 own = own_parent;
139 other = other_parent;
140 }
141 _ => return Ok(None),
142 }
143 }
144 Ok(Some(own))
145 }
146
147 pub(crate) fn transform_up(
150 &self,
151 start: NodeKey,
152 start_offset: Isometry3<f64>,
153 target: NodeKey,
154 ) -> Result<Isometry3<f64>, CartesianTreeError> {
155 let mut transform = start_offset;
156 let mut current = start;
157 while current != target {
158 let node = self.node(current)?;
159 let Some(parent) = node.parent else {
160 return Err(CartesianTreeError::IsNoAncestor(
161 self.name_of(target),
162 self.name_of(start),
163 ));
164 };
165 transform = node.transform_to_parent * transform;
166 current = parent;
167 }
168 Ok(transform)
169 }
170}
171
172#[derive(Clone, Debug)]
174pub(crate) enum FrameKind {
175 Node(NodeKey),
177 Derived {
180 anchor: NodeKey,
181 offset: Isometry3<f64>,
182 name: String,
183 },
184}
185
186impl FrameKind {
187 pub(crate) const fn anchor(&self) -> NodeKey {
189 match self {
190 Self::Node(key) => *key,
191 Self::Derived { anchor, .. } => *anchor,
192 }
193 }
194
195 pub(crate) fn offset(&self) -> Isometry3<f64> {
197 match self {
198 Self::Node(_) => Isometry3::identity(),
199 Self::Derived { offset, .. } => *offset,
200 }
201 }
202}
203
204#[derive(Clone)]
223pub struct Frame {
224 pub(crate) tree: SharedTree,
225 pub(crate) kind: FrameKind,
226}
227
228impl std::fmt::Debug for Frame {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 f.debug_struct("Frame")
232 .field("kind", &self.kind)
233 .finish_non_exhaustive()
234 }
235}
236
237impl Frame {
238 pub fn new_origin(name: impl Into<String>) -> Self {
252 let mut nodes = SlotMap::with_key();
253 let root = nodes.insert(Node {
254 name: name.into(),
255 parent: None,
256 children: Vec::new(),
257 transform_to_parent: Isometry3::identity(),
258 });
259 Self {
260 tree: Arc::new(RwLock::new(TreeInner { nodes })),
261 kind: FrameKind::Node(root),
262 }
263 }
264
265 fn read(&self) -> RwLockReadGuard<'_, TreeInner> {
266 read_tree(&self.tree)
267 }
268
269 fn write(&self) -> RwLockWriteGuard<'_, TreeInner> {
270 write_tree(&self.tree)
271 }
272
273 fn node_key(&self) -> Result<NodeKey, CartesianTreeError> {
276 match &self.kind {
277 FrameKind::Node(key) => Ok(*key),
278 FrameKind::Derived { name, .. } => {
279 Err(CartesianTreeError::DerivedFrameUnsupported(name.clone()))
280 }
281 }
282 }
283
284 #[must_use]
288 pub fn name(&self) -> String {
289 match &self.kind {
290 FrameKind::Node(key) => self.read().name_of(*key),
291 FrameKind::Derived { name, .. } => name.clone(),
292 }
293 }
294
295 pub fn transformation(&self) -> Result<Isometry3<f64>, CartesianTreeError> {
305 match &self.kind {
306 FrameKind::Node(key) => {
307 let guard = self.read();
308 let node = guard.node(*key)?;
309 if node.parent.is_none() {
310 return Err(CartesianTreeError::RootHasNoParent(node.name.clone()));
311 }
312 Ok(node.transform_to_parent)
313 }
314 FrameKind::Derived { offset, .. } => Ok(*offset),
315 }
316 }
317
318 fn local_transform(&self) -> Result<Isometry3<f64>, CartesianTreeError> {
321 match &self.kind {
322 FrameKind::Node(key) => Ok(self.read().node(*key)?.transform_to_parent),
323 FrameKind::Derived { offset, .. } => Ok(*offset),
324 }
325 }
326
327 pub fn position(&self) -> Result<Vector3<f64>, CartesianTreeError> {
336 Ok(self.local_transform()?.translation.vector)
337 }
338
339 pub fn orientation(&self) -> Result<Rotation, CartesianTreeError> {
348 Ok(self.local_transform()?.rotation.into())
349 }
350
351 pub fn set(
381 &self,
382 position: Vector3<f64>,
383 orientation: impl Into<Rotation>,
384 ) -> Result<(), CartesianTreeError> {
385 let transform = Isometry3::from_parts(
386 Translation3::from(position),
387 orientation.into().as_quaternion(),
388 );
389 self.update_transform(|_| transform)
390 }
391
392 pub fn apply_in_parent_frame(
422 &self,
423 isometry: &Isometry3<f64>,
424 ) -> Result<(), CartesianTreeError> {
425 self.update_transform(|current| isometry * current)
426 }
427
428 pub fn apply_in_local_frame(
458 &self,
459 isometry: &Isometry3<f64>,
460 ) -> Result<(), CartesianTreeError> {
461 self.update_transform(|current| current * isometry)
462 }
463
464 fn update_transform(
466 &self,
467 update: impl FnOnce(Isometry3<f64>) -> Isometry3<f64>,
468 ) -> Result<(), CartesianTreeError> {
469 let key = self.node_key()?;
470 let mut guard = self.write();
471 let node = guard.node_mut(key)?;
472 if node.parent.is_none() {
473 return Err(CartesianTreeError::CannotUpdateRootTransform(
474 node.name.clone(),
475 ));
476 }
477 node.transform_to_parent = update(node.transform_to_parent);
478 Ok(())
479 }
480
481 pub fn add_child(
512 &self,
513 name: impl Into<String>,
514 position: Vector3<f64>,
515 orientation: impl Into<Rotation>,
516 ) -> Result<Self, CartesianTreeError> {
517 let key = self.node_key()?;
518 let transform = Isometry3::from_parts(
519 Translation3::from(position),
520 orientation.into().as_quaternion(),
521 );
522 let child_key = self.write().add_child_node(key, name.into(), transform)?;
523 Ok(Self {
524 tree: Arc::clone(&self.tree),
525 kind: FrameKind::Node(child_key),
526 })
527 }
528
529 pub fn remove_child(&self, name: &str) -> Result<(), CartesianTreeError> {
557 let key = self.node_key()?;
558 let mut guard = self.write();
559 let children = guard.node(key)?.children.clone();
560 let child_key = children
561 .iter()
562 .copied()
563 .find(|&child| guard.nodes.get(child).is_some_and(|node| node.name == name))
564 .ok_or_else(|| {
565 CartesianTreeError::ChildNotFound(name.to_owned(), guard.name_of(key))
566 })?;
567 guard.node_mut(key)?.children.retain(|&c| c != child_key);
568 guard.remove_subtree(child_key);
569 Ok(())
570 }
571
572 pub fn calibrate_child(
606 &self,
607 name: impl Into<String>,
608 desired_position: Vector3<f64>,
609 desired_orientation: impl Into<Rotation>,
610 reference_pose: &Pose,
611 ) -> Result<Self, CartesianTreeError> {
612 let key = self.node_key()?;
613 if !Arc::ptr_eq(&self.tree, &reference_pose.tree) {
614 return Err(CartesianTreeError::DifferentTrees(
615 self.name(),
616 reference_pose
617 .frame()
618 .map_or_else(|| "<removed>".to_owned(), |frame| frame.name()),
619 ));
620 }
621
622 let desired_pose = Isometry3::from_parts(
623 Translation3::from(desired_position),
624 desired_orientation.into().as_quaternion(),
625 );
626
627 let mut guard = self.write();
628 let reference_anchor = reference_pose.anchor.anchor();
629 let ancestor = guard.lca(key, reference_anchor)?.ok_or_else(|| {
630 CartesianTreeError::NoCommonAncestor(
631 guard.name_of(key),
632 guard.name_of(reference_anchor),
633 )
634 })?;
635
636 let t_pose_to_reference_anchor =
637 reference_pose.anchor.offset() * reference_pose.transformation();
638 let t_pose_to_ancestor =
639 guard.transform_up(reference_anchor, t_pose_to_reference_anchor, ancestor)?;
640 let t_parent_to_ancestor = guard.transform_up(key, Isometry3::identity(), ancestor)?;
641
642 let t_calibrated_to_parent =
643 t_parent_to_ancestor.inverse() * t_pose_to_ancestor * desired_pose.inverse();
644
645 let child_key = guard.add_child_node(key, name.into(), t_calibrated_to_parent)?;
646 Ok(Self {
647 tree: Arc::clone(&self.tree),
648 kind: FrameKind::Node(child_key),
649 })
650 }
651
652 pub fn add_pose(&self, position: Vector3<f64>, orientation: impl Into<Rotation>) -> Pose {
670 Pose::new(
671 Arc::clone(&self.tree),
672 self.kind.clone(),
673 position,
674 orientation,
675 )
676 }
677
678 pub fn to_json(&self) -> Result<String, CartesianTreeError> {
691 let key = self.node_key()?;
692 let guard = self.read();
693 let serial = to_serial(&guard, key)?;
694 Ok(serde_json::to_string_pretty(&serial)?)
695 }
696
697 pub fn apply_config(&self, json: &str) -> Result<(), CartesianTreeError> {
717 let key = self.node_key()?;
718 let serial: SerialFrame = serde_json::from_str(json)?;
719 let mut guard = self.write();
720 apply_serial(&mut guard, key, &serial)
721 }
722
723 fn derive_in_parent_frame(&self, isometry: &Isometry3<f64>) -> Self {
729 let (anchor, offset) = match &self.kind {
730 FrameKind::Node(key) => {
731 let transform = self
734 .read()
735 .nodes
736 .get(*key)
737 .map_or_else(Isometry3::identity, |node| node.transform_to_parent);
738 (*key, transform.inverse() * isometry * transform)
739 }
740 FrameKind::Derived { anchor, offset, .. } => (*anchor, isometry * offset),
741 };
742 Self {
743 tree: Arc::clone(&self.tree),
744 kind: FrameKind::Derived {
745 anchor,
746 offset,
747 name: Uuid::new_v4().to_string(),
748 },
749 }
750 }
751
752 fn derive_in_local_frame(&self, isometry: &Isometry3<f64>) -> Self {
756 let (anchor, offset) = match &self.kind {
757 FrameKind::Node(key) => (*key, *isometry),
758 FrameKind::Derived { anchor, offset, .. } => (*anchor, offset * isometry),
759 };
760 Self {
761 tree: Arc::clone(&self.tree),
762 kind: FrameKind::Derived {
763 anchor,
764 offset,
765 name: Uuid::new_v4().to_string(),
766 },
767 }
768 }
769}
770
771#[derive(Serialize, Deserialize, Debug, Clone)]
772struct SerialFrame {
773 name: String,
774 position: Vector3<f64>,
775 orientation: Quaternion<f64>,
778 children: Vec<Self>,
779}
780
781fn to_serial(inner: &TreeInner, key: NodeKey) -> Result<SerialFrame, CartesianTreeError> {
782 let node = inner.node(key)?;
783 let (position, orientation) = if node.parent.is_some() {
784 (
785 node.transform_to_parent.translation.vector,
786 node.transform_to_parent.rotation.into_inner(),
787 )
788 } else {
789 (Vector3::zeros(), Quaternion::identity())
790 };
791 Ok(SerialFrame {
792 name: node.name.clone(),
793 position,
794 orientation,
795 children: node
796 .children
797 .iter()
798 .map(|&child| to_serial(inner, child))
799 .collect::<Result<_, _>>()?,
800 })
801}
802
803fn apply_serial(
804 inner: &mut TreeInner,
805 key: NodeKey,
806 serial: &SerialFrame,
807) -> Result<(), CartesianTreeError> {
808 let node = inner.node(key)?;
809 if node.name != serial.name {
810 return Err(CartesianTreeError::Mismatch(format!(
811 "Frame names do not match: {} vs {}",
812 node.name, serial.name
813 )));
814 }
815
816 if node.parent.is_some() {
818 let orientation = UnitQuaternion::try_new(serial.orientation, MIN_QUATERNION_NORM)
819 .ok_or_else(|| {
820 let q = &serial.orientation;
821 CartesianTreeError::InvalidQuaternion(q.i, q.j, q.k, q.w)
822 })?;
823 inner.node_mut(key)?.transform_to_parent =
824 Isometry3::from_parts(Translation3::from(serial.position), orientation);
825 }
826
827 for potential_child in &serial.children {
828 let children = inner.node(key)?.children.clone();
829 let matching = children.iter().copied().find(|&child| {
830 inner
831 .nodes
832 .get(child)
833 .is_some_and(|node| node.name == potential_child.name)
834 });
835 if let Some(child_key) = matching {
836 apply_serial(inner, child_key, potential_child)?;
837 }
838 }
839
840 Ok(())
841}
842
843impl Add<LazyTranslation> for &Frame {
850 type Output = Frame;
851
852 fn add(self, rhs: LazyTranslation) -> Self::Output {
853 self.derive_in_parent_frame(&rhs.inner)
854 }
855}
856
857impl Sub<LazyTranslation> for &Frame {
864 type Output = Frame;
865
866 fn sub(self, rhs: LazyTranslation) -> Self::Output {
867 self.derive_in_parent_frame(&rhs.inner.inverse())
868 }
869}
870
871impl Mul<LazyRotation> for &Frame {
878 type Output = Frame;
879
880 fn mul(self, rhs: LazyRotation) -> Self::Output {
881 self.derive_in_local_frame(&rhs.inner)
882 }
883}
884
885impl HasParent for Frame {
886 type Node = Self;
887
888 fn parent(&self) -> Option<Self::Node> {
889 let guard = self.read();
890 let parent_key = match &self.kind {
891 FrameKind::Node(key) => guard.nodes.get(*key)?.parent?,
892 FrameKind::Derived { anchor, .. } => {
893 if !guard.contains(*anchor) {
894 return None;
895 }
896 *anchor
897 }
898 };
899 drop(guard);
900 Some(Self {
901 tree: Arc::clone(&self.tree),
902 kind: FrameKind::Node(parent_key),
903 })
904 }
905}
906
907impl NodeEquality for Frame {
908 fn is_same(&self, other: &Self) -> bool {
909 if !Arc::ptr_eq(&self.tree, &other.tree) {
910 return false;
911 }
912 match (&self.kind, &other.kind) {
913 (FrameKind::Node(own), FrameKind::Node(other)) => own == other,
914 (FrameKind::Derived { name: own, .. }, FrameKind::Derived { name: other, .. }) => {
915 own == other
916 }
917 _ => false,
918 }
919 }
920}
921
922impl HasChildren for Frame {
923 type Node = Self;
924 fn children(&self) -> Vec<Self> {
925 let FrameKind::Node(key) = &self.kind else {
926 return Vec::new();
927 };
928 let guard = self.read();
929 let Some(node) = guard.nodes.get(*key) else {
930 return Vec::new();
931 };
932 node.children
933 .iter()
934 .map(|&child| Self {
935 tree: Arc::clone(&self.tree),
936 kind: FrameKind::Node(child),
937 })
938 .collect()
939 }
940}
941
942#[cfg(test)]
943mod tests {
944 use crate::lazy_access::{rx, ry, rz, x, y, z};
945
946 use super::*;
947 use crate::tree::Walking;
948 use approx::assert_relative_eq;
949 use nalgebra::{UnitQuaternion, Vector3};
950
951 #[test]
952 fn frame_and_pose_are_send_and_sync() {
953 const fn assert_send_sync<T: Send + Sync>() {}
954 assert_send_sync::<Frame>();
955 assert_send_sync::<Pose>();
956 }
957
958 #[test]
959 fn create_origin_frame() {
960 let root = Frame::new_origin("world");
961 assert_eq!(root.name(), "world");
962 assert!(root.parent().is_none());
963 assert!(root.children().is_empty());
964 }
965
966 #[test]
967 fn add_child_frame_with_quaternion() {
968 let root = Frame::new_origin("world");
969 let child = root
970 .add_child(
971 "dummy",
972 Vector3::new(1.0, 0.0, 0.0),
973 UnitQuaternion::identity(),
974 )
975 .unwrap();
976
977 assert_eq!(root.children().len(), 1);
978 assert_eq!(child.name(), "dummy");
979 assert_eq!(child.parent().unwrap().name(), "world");
980 }
981
982 #[test]
983 fn add_child_frame_with_rpy() {
984 let root = Frame::new_origin("world");
985 let child = root
986 .add_child(
987 "dummy",
988 Vector3::new(0.0, 1.0, 0.0),
989 Rotation::from_rpy(0.0, 0.0, std::f64::consts::FRAC_PI_2),
990 )
991 .unwrap();
992
993 assert_eq!(child.name(), "dummy");
994
995 let rotation = child.transformation().unwrap().rotation;
996 let expected = UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2);
997 assert!((rotation.angle() - expected.angle()).abs() < 1e-10);
998 }
999
1000 #[test]
1001 fn test_child_frame_transform_to_parent() {
1002 let root = Frame::new_origin("world");
1003 let child = root
1004 .add_child(
1005 "dummy",
1006 Vector3::new(0.0, 0.0, 1.0),
1007 UnitQuaternion::identity(),
1008 )
1009 .unwrap();
1010
1011 let transform = child.transformation().unwrap();
1012 assert_eq!(transform.translation.vector, Vector3::new(0.0, 0.0, 1.0));
1013 assert_eq!(transform.rotation, UnitQuaternion::identity());
1014
1015 assert_eq!(child.position().unwrap(), Vector3::new(0.0, 0.0, 1.0));
1016 assert_eq!(
1017 child.orientation().unwrap().as_quaternion(),
1018 UnitQuaternion::identity()
1019 );
1020 }
1021
1022 #[test]
1023 fn multiple_child_frames() {
1024 let root = Frame::new_origin("world");
1025
1026 let a = root
1027 .add_child("a", Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
1028 .unwrap();
1029 let b = root
1030 .add_child("b", Vector3::new(0.0, 1.0, 0.0), UnitQuaternion::identity())
1031 .unwrap();
1032
1033 assert_eq!(root.children().len(), 2);
1034 assert_eq!(a.parent().unwrap().name(), "world");
1035 assert_eq!(b.parent().unwrap().name(), "world");
1036 }
1037
1038 #[test]
1039 fn test_remove_child() {
1040 let root = Frame::new_origin("root");
1041 let child = root
1042 .add_child(
1043 "child",
1044 Vector3::new(1.0, 0.0, 0.0),
1045 UnitQuaternion::identity(),
1046 )
1047 .unwrap();
1048 let grandchild = child
1049 .add_child("grandchild", Vector3::zeros(), UnitQuaternion::identity())
1050 .unwrap();
1051
1052 root.remove_child("child").unwrap();
1053 assert!(root.children().is_empty());
1054
1055 assert!(matches!(
1057 child.transformation(),
1058 Err(CartesianTreeError::FrameRemoved)
1059 ));
1060 assert!(matches!(
1061 grandchild.transformation(),
1062 Err(CartesianTreeError::FrameRemoved)
1063 ));
1064 assert_eq!(child.name(), "<removed>");
1065 assert!(child.parent().is_none());
1066
1067 assert!(
1069 root.add_child("child", Vector3::zeros(), UnitQuaternion::identity())
1070 .is_ok()
1071 );
1072
1073 assert!(matches!(
1075 root.remove_child("unknown"),
1076 Err(CartesianTreeError::ChildNotFound(..))
1077 ));
1078 }
1079
1080 #[test]
1081 fn test_tree_stays_alive_through_any_handle() {
1082 let leaf = {
1083 let root = Frame::new_origin("root");
1084 let mid = root
1085 .add_child(
1086 "mid",
1087 Vector3::new(1.0, 0.0, 0.0),
1088 UnitQuaternion::identity(),
1089 )
1090 .unwrap();
1091 mid.add_child(
1092 "leaf",
1093 Vector3::new(0.0, 2.0, 0.0),
1094 UnitQuaternion::identity(),
1095 )
1096 .unwrap()
1097 }; assert_eq!(leaf.root().name(), "root");
1100 assert_eq!(leaf.depth(), 2);
1101 assert!(leaf.transformation().is_ok());
1102
1103 let leaf_in_root = leaf
1104 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1105 .in_frame(&leaf.root())
1106 .unwrap()
1107 .transformation();
1108 assert_relative_eq!(
1109 leaf_in_root.translation.vector,
1110 Vector3::new(1.0, 2.0, 0.0),
1111 epsilon = 1e-10
1112 );
1113 }
1114
1115 #[test]
1116 fn test_threaded_access() {
1117 let root = Frame::new_origin("root");
1118 let child = root
1119 .add_child("child", Vector3::zeros(), UnitQuaternion::identity())
1120 .unwrap();
1121
1122 let handles: Vec<_> = (0..4)
1123 .map(|i| {
1124 let child = child.clone();
1125 let root = root.clone();
1126 std::thread::spawn(move || {
1127 for j in 0..100 {
1128 child
1129 .set(
1130 Vector3::new(f64::from(j), 0.0, f64::from(i)),
1131 UnitQuaternion::identity(),
1132 )
1133 .unwrap();
1134 let pose = root.add_pose(Vector3::zeros(), UnitQuaternion::identity());
1135 pose.in_frame(&child).unwrap();
1136 }
1137 })
1138 })
1139 .collect();
1140
1141 for handle in handles {
1142 handle.join().unwrap();
1143 }
1144 }
1145
1146 #[test]
1147 fn reject_duplicate_child_name() {
1148 let root = Frame::new_origin("world");
1149
1150 let _ = root
1151 .add_child(
1152 "duplicate",
1153 Vector3::new(1.0, 0.0, 0.0),
1154 UnitQuaternion::identity(),
1155 )
1156 .unwrap();
1157
1158 let result = root.add_child(
1159 "duplicate",
1160 Vector3::new(2.0, 0.0, 0.0),
1161 UnitQuaternion::identity(),
1162 );
1163 assert!(result.is_err());
1164 }
1165
1166 #[test]
1167 fn test_chained_lazy_frames_survive_intermediate_drop() {
1168 let root = Frame::new_origin("root");
1169 let derived = {
1170 let intermediate = &root + z(5.0);
1171 &intermediate - y(3.0)
1172 }; let derived_in_root = derived
1175 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1176 .in_frame(&root)
1177 .unwrap()
1178 .transformation();
1179 assert_relative_eq!(
1180 derived_in_root.translation.vector,
1181 Vector3::new(0.0, -3.0, 5.0),
1182 epsilon = 1e-10
1183 );
1184 }
1185
1186 #[test]
1187 fn test_derived_frames_are_read_only() {
1188 let root = Frame::new_origin("root");
1189 let derived = &root + z(5.0);
1190
1191 assert!(matches!(
1192 derived.set(Vector3::zeros(), UnitQuaternion::identity()),
1193 Err(CartesianTreeError::DerivedFrameUnsupported(_))
1194 ));
1195 assert!(matches!(
1196 derived.add_child("child", Vector3::zeros(), UnitQuaternion::identity()),
1197 Err(CartesianTreeError::DerivedFrameUnsupported(_))
1198 ));
1199 assert!(matches!(
1200 derived.to_json(),
1201 Err(CartesianTreeError::DerivedFrameUnsupported(_))
1202 ));
1203 }
1204
1205 #[test]
1206 fn test_add_pose_to_frame() {
1207 let frame = Frame::new_origin("dummy");
1208 let pose = frame.add_pose(Vector3::new(1.0, 2.0, 3.0), UnitQuaternion::identity());
1209
1210 assert_eq!(pose.frame().unwrap().name(), "dummy");
1211 }
1212
1213 #[test]
1214 fn test_set_transform() {
1215 let root = Frame::new_origin("root");
1216 let child = root
1217 .add_child(
1218 "dummy",
1219 Vector3::new(0.0, 0.0, 1.0),
1220 UnitQuaternion::identity(),
1221 )
1222 .unwrap();
1223 child
1224 .set(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
1225 .unwrap();
1226 assert_eq!(
1227 child.transformation().unwrap().translation.vector,
1228 Vector3::new(1.0, 0.0, 0.0)
1229 );
1230
1231 assert!(
1233 root.set(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
1234 .is_err()
1235 );
1236 }
1237
1238 #[test]
1239 fn test_apply_in_parent_frame() {
1240 let root = Frame::new_origin("root");
1241 let child = root
1242 .add_child(
1243 "dummy",
1244 Vector3::new(1.0, 0.0, 1.0),
1245 UnitQuaternion::identity(),
1246 )
1247 .unwrap();
1248 child
1249 .apply_in_parent_frame(&Isometry3::from_parts(
1250 Translation3::identity(),
1251 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1252 ))
1253 .unwrap();
1254
1255 assert_relative_eq!(
1256 child.transformation().unwrap().translation.vector,
1257 Vector3::new(0.0, 1.0, 1.0),
1258 epsilon = 1e-10
1259 );
1260
1261 child
1262 .apply_in_parent_frame(&Isometry3::from_parts(
1263 Translation3::new(1.0, 0.0, 1.0),
1264 UnitQuaternion::identity(),
1265 ))
1266 .unwrap();
1267 assert_relative_eq!(
1268 child.transformation().unwrap().translation.vector,
1269 Vector3::new(1.0, 1.0, 2.0),
1270 epsilon = 1e-10
1271 );
1272 }
1273
1274 #[test]
1275 fn test_apply_in_local_frame() {
1276 let root = Frame::new_origin("root");
1277 let child = root
1278 .add_child(
1279 "dummy",
1280 Vector3::zeros(),
1281 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1282 )
1283 .unwrap();
1284
1285 child
1286 .apply_in_local_frame(&Isometry3::from_parts(
1287 Translation3::new(1.0, 0.0, 0.0),
1288 UnitQuaternion::identity(),
1289 ))
1290 .unwrap();
1291
1292 assert_relative_eq!(
1293 child.transformation().unwrap().translation.vector,
1294 Vector3::new(0.0, 1.0, 0.0),
1295 epsilon = 1e-10
1296 );
1297
1298 child
1299 .apply_in_local_frame(&Isometry3::from_parts(
1300 Translation3::identity(),
1301 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1302 ))
1303 .unwrap();
1304 assert_relative_eq!(
1305 child.transformation().unwrap().translation.vector,
1306 Vector3::new(0.0, 1.0, 0.0),
1307 epsilon = 1e-10
1308 );
1309
1310 let (roll, pitch, yaw) = child.transformation().unwrap().rotation.euler_angles();
1311 assert_relative_eq!(roll, 0.0, epsilon = 1e-10);
1312 assert_relative_eq!(pitch, 0.0, epsilon = 1e-10);
1313 assert_relative_eq!(yaw, std::f64::consts::PI, epsilon = 1e-10);
1314 }
1315
1316 #[test]
1317 fn test_pose_apply_in_parent_frame() {
1318 let root = Frame::new_origin("root");
1319 let mut pose = root.add_pose(Vector3::new(1.0, 0.0, 1.0), UnitQuaternion::identity());
1320
1321 pose.apply_in_parent_frame(&Isometry3::from_parts(
1322 Translation3::identity(),
1323 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1324 ));
1325
1326 assert_relative_eq!(
1327 pose.transformation().translation.vector,
1328 Vector3::new(0.0, 1.0, 1.0),
1329 epsilon = 1e-10
1330 );
1331
1332 pose.apply_in_parent_frame(&Isometry3::from_parts(
1333 Translation3::new(1.0, 0.0, 1.0),
1334 UnitQuaternion::identity(),
1335 ));
1336 assert_relative_eq!(
1337 pose.transformation().translation.vector,
1338 Vector3::new(1.0, 1.0, 2.0),
1339 epsilon = 1e-10
1340 );
1341 }
1342
1343 #[test]
1344 fn test_pose_apply_in_local_frame() {
1345 let root = Frame::new_origin("root");
1346 let mut pose = root.add_pose(
1347 Vector3::zeros(),
1348 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1349 );
1350
1351 pose.apply_in_local_frame(&Isometry3::from_parts(
1352 Translation3::new(1.0, 0.0, 0.0),
1353 UnitQuaternion::identity(),
1354 ));
1355
1356 assert_relative_eq!(
1357 pose.transformation().translation.vector,
1358 Vector3::new(0.0, 1.0, 0.0),
1359 epsilon = 1e-10
1360 );
1361
1362 pose.apply_in_local_frame(&Isometry3::from_parts(
1363 Translation3::identity(),
1364 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1365 ));
1366 assert_relative_eq!(
1367 pose.transformation().translation.vector,
1368 Vector3::new(0.0, 1.0, 0.0),
1369 epsilon = 1e-10
1370 );
1371
1372 let (roll, pitch, yaw) = pose.transformation().rotation.euler_angles();
1373 assert_relative_eq!(roll, 0.0, epsilon = 1e-10);
1374 assert_relative_eq!(pitch, 0.0, epsilon = 1e-10);
1375 assert_relative_eq!(yaw, std::f64::consts::PI, epsilon = 1e-10);
1376 }
1377
1378 #[test]
1379 fn test_pose_transform_to_parent() {
1380 let root = Frame::new_origin("root");
1381 let pose = root.add_pose(Vector3::new(1.0, 2.0, 3.0), UnitQuaternion::identity());
1382
1383 let transformation = pose.transformation();
1384 assert_eq!(
1385 transformation.translation.vector,
1386 Vector3::new(1.0, 2.0, 3.0)
1387 );
1388 assert_eq!(transformation.rotation, UnitQuaternion::identity());
1389
1390 assert_eq!(pose.position(), Vector3::new(1.0, 2.0, 3.0));
1391 assert_eq!(
1392 pose.orientation().as_quaternion(),
1393 UnitQuaternion::identity()
1394 );
1395 }
1396
1397 #[test]
1398 fn test_pose_transformation_between_frames() {
1399 let root = Frame::new_origin("root");
1400
1401 let f1 = root
1402 .add_child(
1403 "f1",
1404 Vector3::new(1.0, 0.0, 0.0),
1405 UnitQuaternion::identity(),
1406 )
1407 .unwrap();
1408
1409 let f2 = f1
1410 .add_child(
1411 "f2",
1412 Vector3::new(0.0, 2.0, 0.0),
1413 UnitQuaternion::identity(),
1414 )
1415 .unwrap();
1416
1417 let pose_in_f2 = f2.add_pose(Vector3::new(1.0, 1.0, 0.0), UnitQuaternion::identity());
1418
1419 let pose_in_root = pose_in_f2.in_frame(&root).unwrap();
1420 let pos = pose_in_root.transformation().translation.vector;
1421
1422 assert!((pos - Vector3::new(2.0, 3.0, 0.0)).norm() < 1e-6);
1424 }
1425
1426 #[test]
1427 fn test_pose_round_trip_through_deep_tree() {
1428 let root = Frame::new_origin("root");
1431 let a = root
1432 .add_child(
1433 "a",
1434 Vector3::new(0.3, -1.2, 2.5),
1435 UnitQuaternion::from_euler_angles(0.4, -0.3, 1.2),
1436 )
1437 .unwrap();
1438 let b = a
1439 .add_child(
1440 "b",
1441 Vector3::new(-2.0, 0.7, 0.1),
1442 UnitQuaternion::from_euler_angles(-1.0, 0.2, 0.5),
1443 )
1444 .unwrap();
1445 let c = root
1446 .add_child(
1447 "c",
1448 Vector3::new(1.5, 2.0, -0.4),
1449 UnitQuaternion::from_euler_angles(0.1, 1.1, -0.7),
1450 )
1451 .unwrap();
1452 let d = c
1453 .add_child(
1454 "d",
1455 Vector3::new(0.0, -0.5, 1.0),
1456 UnitQuaternion::from_euler_angles(0.9, -0.8, 0.3),
1457 )
1458 .unwrap();
1459
1460 let pose = b.add_pose(
1461 Vector3::new(0.2, 0.4, -0.6),
1462 UnitQuaternion::from_euler_angles(0.5, 0.5, -0.5),
1463 );
1464
1465 let round_tripped = pose.in_frame(&d).unwrap().in_frame(&b).unwrap();
1466 let original = pose.transformation();
1467 let result = round_tripped.transformation();
1468 assert_relative_eq!(
1469 result.translation.vector,
1470 original.translation.vector,
1471 epsilon = 1e-9
1472 );
1473 assert_relative_eq!(
1474 result.rotation.angle_to(&original.rotation),
1475 0.0,
1476 epsilon = 1e-9
1477 );
1478 }
1479
1480 #[test]
1481 fn test_in_frame_across_disjoint_trees_fails() {
1482 let tree_1 = Frame::new_origin("tree_1");
1483 let tree_2 = Frame::new_origin("tree_2");
1484 let pose = tree_1.add_pose(Vector3::zeros(), UnitQuaternion::identity());
1485
1486 assert!(matches!(
1487 pose.in_frame(&tree_2),
1488 Err(CartesianTreeError::DifferentTrees(..))
1489 ));
1490 }
1491
1492 #[test]
1493 fn test_apply_config_malformed_json_fails() {
1494 let root = Frame::new_origin("root");
1495 assert!(matches!(
1496 root.apply_config("not json"),
1497 Err(CartesianTreeError::SerdeError(_))
1498 ));
1499 }
1500
1501 #[test]
1502 fn test_lazy_helpers_all_axes() {
1503 use nalgebra::UnitQuaternion;
1504
1505 let root = Frame::new_origin("root");
1506 let pose = root.add_pose(Vector3::zeros(), UnitQuaternion::identity());
1507
1508 let moved = &(&(&pose + x(1.0)) + y(2.0)) + z(3.0);
1509 assert_relative_eq!(
1510 moved.transformation().translation.vector,
1511 Vector3::new(1.0, 2.0, 3.0),
1512 epsilon = 1e-10
1513 );
1514
1515 let rotated = &pose * rx(0.3);
1516 assert_relative_eq!(
1517 rotated
1518 .transformation()
1519 .rotation
1520 .angle_to(&UnitQuaternion::from_euler_angles(0.3, 0.0, 0.0)),
1521 0.0,
1522 epsilon = 1e-10
1523 );
1524
1525 let rotated = &pose * ry(0.4);
1526 assert_relative_eq!(
1527 rotated
1528 .transformation()
1529 .rotation
1530 .angle_to(&UnitQuaternion::from_euler_angles(0.0, 0.4, 0.0)),
1531 0.0,
1532 epsilon = 1e-10
1533 );
1534 }
1535
1536 #[test]
1537 fn test_calibrate_child() {
1538 let root = Frame::new_origin("root");
1539
1540 let reference_pose = root.add_pose(
1541 Vector3::new(1.0, 2.0, 3.0),
1542 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1543 );
1544
1545 let calibrated_frame = root
1547 .calibrate_child(
1548 "calibrated",
1549 Vector3::zeros(),
1550 UnitQuaternion::identity(),
1551 &reference_pose,
1552 )
1553 .unwrap();
1554
1555 let pose_in_calibrated = reference_pose.in_frame(&calibrated_frame).unwrap();
1556 let transformation = pose_in_calibrated.transformation();
1557
1558 assert!((transformation.translation.vector - Vector3::zeros()).norm() < 1e-6);
1559 assert!((transformation.rotation.angle() - 0.0).abs() < 1e-6);
1560
1561 let calibrated_transformation = calibrated_frame.transformation().unwrap();
1563 assert!(
1564 (calibrated_transformation.translation.vector - Vector3::new(1.0, 2.0, 3.0)).norm()
1565 < 1e-6
1566 );
1567 assert!(
1568 (calibrated_transformation.rotation.angle() - std::f64::consts::FRAC_PI_2).abs() < 1e-6
1569 );
1570 }
1571
1572 #[test]
1573 fn test_calibrate_child_under_non_identity_parent() {
1574 let root = Frame::new_origin("root");
1575
1576 let mount = root
1579 .add_child(
1580 "mount",
1581 Vector3::new(0.0, 0.0, 1.0),
1582 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2),
1583 )
1584 .unwrap();
1585
1586 let reference_pose = root.add_pose(
1587 Vector3::new(1.0, 2.0, 3.0),
1588 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_4),
1589 );
1590
1591 let desired_position = Vector3::new(0.5, 0.0, 0.0);
1592 let desired_orientation =
1593 UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2);
1594
1595 let calibrated = mount
1596 .calibrate_child(
1597 "calibrated",
1598 desired_position,
1599 desired_orientation,
1600 &reference_pose,
1601 )
1602 .unwrap();
1603
1604 let pose_in_calibrated = reference_pose.in_frame(&calibrated).unwrap();
1606 let transformation = pose_in_calibrated.transformation();
1607 assert_relative_eq!(
1608 transformation.translation.vector,
1609 desired_position,
1610 epsilon = 1e-10
1611 );
1612 assert_relative_eq!(
1613 transformation.rotation.angle_to(&desired_orientation),
1614 0.0,
1615 epsilon = 1e-10
1616 );
1617 }
1618
1619 #[test]
1620 fn test_to_json_and_apply_config() {
1621 let root = Frame::new_origin("root");
1622 let _ = root
1623 .add_child(
1624 "child",
1625 Vector3::new(1.0, 2.0, 3.0),
1626 UnitQuaternion::from_euler_angles(0.1, 0.2, 0.3),
1627 )
1628 .unwrap();
1629
1630 let json = root.to_json().unwrap();
1631 assert!(json.contains(r#""name": "root""#));
1633 assert!(json.contains(r#""name": "child""#));
1634
1635 let default_root = Frame::new_origin("root");
1637 default_root
1638 .add_child(
1639 "child",
1640 Vector3::new(0.0, 0.0, 0.0),
1641 UnitQuaternion::identity(),
1642 )
1643 .unwrap();
1644
1645 default_root.apply_config(&json).unwrap();
1647
1648 let updated_child = default_root
1650 .children()
1651 .into_iter()
1652 .find(|c| c.name() == "child")
1653 .unwrap();
1654 let iso = updated_child.transformation().unwrap();
1655 assert_eq!(iso.translation.vector, Vector3::new(1.0, 2.0, 3.0));
1656 let (r, p, y) = iso.rotation.euler_angles();
1657 assert!((r - 0.1).abs() < 1e-6);
1658 assert!((p - 0.2).abs() < 1e-6);
1659 assert!((y - 0.3).abs() < 1e-6);
1660
1661 let partial_json = r#"
1663 {
1664 "name": "root",
1665 "position": [0.0, 0.0, 0.0],
1666 "orientation": [0.0, 0.0, 0.0, 1.0],
1667 "children": [
1668 {
1669 "name": "child",
1670 "position": [4.0, 5.0, 6.0],
1671 "orientation": [0.0, 0.0, 0.0, 1.0],
1672 "children": []
1673 },
1674 {
1675 "name": "extra",
1676 "position": [0.0, 0.0, 0.0],
1677 "orientation": [0.0, 0.0, 0.0, 1.0],
1678 "children": []
1679 }
1680 ]
1681 }
1682 "#;
1683 default_root.apply_config(partial_json).unwrap();
1684 let updated_child = default_root
1685 .children()
1686 .into_iter()
1687 .find(|c| c.name() == "child")
1688 .unwrap();
1689 assert_eq!(
1690 updated_child.transformation().unwrap().translation.vector,
1691 Vector3::new(4.0, 5.0, 6.0)
1692 );
1693
1694 let mismatch_json = r#"
1696 {
1697 "name": "wrong_root",
1698 "position": [0.0, 0.0, 0.0],
1699 "orientation": [0.0, 0.0, 0.0, 1.0],
1700 "children": []
1701 }
1702 "#;
1703 assert!(default_root.apply_config(mismatch_json).is_err());
1704 }
1705
1706 #[test]
1707 fn test_apply_config_validates_quaternions() {
1708 let root = Frame::new_origin("root");
1709 let child = root
1710 .add_child("child", Vector3::zeros(), UnitQuaternion::identity())
1711 .unwrap();
1712
1713 let scaled_json = r#"
1715 {
1716 "name": "root",
1717 "position": [0.0, 0.0, 0.0],
1718 "orientation": [0.0, 0.0, 0.0, 1.0],
1719 "children": [
1720 {
1721 "name": "child",
1722 "position": [0.0, 0.0, 0.0],
1723 "orientation": [0.0, 0.0, 2.0, 0.0],
1724 "children": []
1725 }
1726 ]
1727 }
1728 "#;
1729 root.apply_config(scaled_json).unwrap();
1730 let q = child.orientation().unwrap().as_quaternion();
1731 assert_relative_eq!(q.k, 1.0, epsilon = 1e-12);
1732 assert_relative_eq!(q.w, 0.0, epsilon = 1e-12);
1733
1734 let zero_json = scaled_json.replace("2.0", "0.0");
1736 assert!(matches!(
1737 root.apply_config(&zero_json),
1738 Err(CartesianTreeError::InvalidQuaternion(..))
1739 ));
1740 }
1741
1742 #[test]
1743 fn test_lazy_translation_frame() {
1744 use nalgebra::UnitQuaternion;
1745
1746 let root = Frame::new_origin("root");
1747 let child = root
1748 .add_child(
1749 "child",
1750 Vector3::new(0.0, 0.0, 0.0),
1751 UnitQuaternion::identity(),
1752 )
1753 .unwrap();
1754
1755 let result = &child + z(5.0);
1756 assert_relative_eq!(
1757 result.transformation().unwrap().translation.vector,
1758 Vector3::new(0.0, 0.0, 5.0),
1759 epsilon = 1e-10
1760 );
1761 assert_relative_eq!(
1762 child.transformation().unwrap().translation.vector,
1763 Vector3::new(0.0, 0.0, 0.0),
1764 epsilon = 1e-10
1765 );
1766
1767 let result = &result - y(3.0);
1769 let result_in_root = result
1770 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1771 .in_frame(&root)
1772 .unwrap()
1773 .transformation();
1774 assert_relative_eq!(
1775 result_in_root.translation.vector,
1776 Vector3::new(0.0, -3.0, 5.0),
1777 epsilon = 1e-10
1778 );
1779
1780 let (roll, pitch, yaw) = result_in_root.rotation.euler_angles();
1781 assert_relative_eq!(
1782 Vector3::new(roll, pitch, yaw),
1783 Vector3::new(0.0, 0.0, 0.0),
1784 epsilon = 1e-10
1785 );
1786 }
1787
1788 #[test]
1789 fn test_lazy_rotation_frame() {
1790 use nalgebra::UnitQuaternion;
1791 let root = Frame::new_origin("root");
1792 let child = root
1793 .add_child(
1794 "child",
1795 Vector3::new(0.0, 0.0, 0.0),
1796 UnitQuaternion::identity(),
1797 )
1798 .unwrap();
1799 let result = &child * rz(std::f64::consts::FRAC_PI_4);
1800
1801 let (roll, pitch, yaw) = result.transformation().unwrap().rotation.euler_angles();
1802 assert_relative_eq!(
1803 Vector3::new(roll, pitch, yaw),
1804 Vector3::new(0.0, 0.0, std::f64::consts::FRAC_PI_4),
1805 epsilon = 1e-10
1806 );
1807 assert_relative_eq!(
1808 result.transformation().unwrap().translation.vector,
1809 Vector3::new(0.0, 0.0, 0.0),
1810 epsilon = 1e-10
1811 );
1812 let (roll, pitch, yaw) = child.transformation().unwrap().rotation.euler_angles();
1813 assert_relative_eq!(
1814 Vector3::new(roll, pitch, yaw),
1815 Vector3::new(0.0, 0.0, 0.0),
1816 epsilon = 1e-10
1817 );
1818 }
1819
1820 #[test]
1821 fn test_lazy_ops_on_non_identity_frame() {
1822 use nalgebra::UnitQuaternion;
1823
1824 let root = Frame::new_origin("root");
1825 let yaw_90 = UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2);
1826 let child = root
1827 .add_child("child", Vector3::new(1.0, 0.0, 0.0), yaw_90)
1828 .unwrap();
1829
1830 let shifted = &child + y(3.0);
1833 let shifted_in_root = shifted
1834 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1835 .in_frame(&root)
1836 .unwrap()
1837 .transformation();
1838 assert_relative_eq!(
1839 shifted_in_root.translation.vector,
1840 Vector3::new(1.0, 3.0, 0.0),
1841 epsilon = 1e-10
1842 );
1843 assert_relative_eq!(
1844 shifted_in_root.rotation.angle_to(&yaw_90),
1845 0.0,
1846 epsilon = 1e-10
1847 );
1848
1849 let rotated = &child * rz(std::f64::consts::FRAC_PI_2);
1851 let rotated_in_root = rotated
1852 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1853 .in_frame(&root)
1854 .unwrap()
1855 .transformation();
1856 assert_relative_eq!(
1857 rotated_in_root.translation.vector,
1858 Vector3::new(1.0, 0.0, 0.0),
1859 epsilon = 1e-10
1860 );
1861 let yaw_180 = UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::PI);
1862 assert_relative_eq!(
1863 rotated_in_root.rotation.angle_to(&yaw_180),
1864 0.0,
1865 epsilon = 1e-10
1866 );
1867 }
1868
1869 #[test]
1870 fn test_lazy_ops_do_not_register_children() {
1871 let root = Frame::new_origin("root");
1872 let child = root
1873 .add_child("child", Vector3::zeros(), UnitQuaternion::identity())
1874 .unwrap();
1875
1876 let derived = &child + z(5.0);
1877 let rotated = &child * rz(0.5);
1878
1879 assert!(child.children().is_empty());
1881 assert_eq!(root.children().len(), 1);
1882
1883 let derived_in_root = derived
1885 .add_pose(Vector3::zeros(), UnitQuaternion::identity())
1886 .in_frame(&root)
1887 .unwrap()
1888 .transformation();
1889 assert_relative_eq!(
1890 derived_in_root.translation.vector,
1891 Vector3::new(0.0, 0.0, 5.0),
1892 epsilon = 1e-10
1893 );
1894 drop(rotated);
1895 }
1896
1897 #[test]
1898 fn test_lazy_translation_pose() {
1899 use nalgebra::UnitQuaternion;
1900
1901 let root = Frame::new_origin("root");
1902 let pose = root.add_pose(Vector3::new(0.0, 0.0, 0.0), UnitQuaternion::identity());
1903
1904 let result = &pose + z(5.0);
1905 assert_relative_eq!(
1906 result.transformation().translation.vector,
1907 Vector3::new(0.0, 0.0, 5.0),
1908 epsilon = 1e-10
1909 );
1910 assert_relative_eq!(
1911 pose.transformation().translation.vector,
1912 Vector3::new(0.0, 0.0, 0.0),
1913 epsilon = 1e-10
1914 );
1915
1916 let result = &result - y(3.0);
1917 assert_relative_eq!(
1918 result.transformation().translation.vector,
1919 Vector3::new(0.0, -3.0, 5.0),
1920 epsilon = 1e-10
1921 );
1922
1923 let (roll, pitch, yaw) = result.transformation().rotation.euler_angles();
1924 assert_relative_eq!(
1925 Vector3::new(roll, pitch, yaw),
1926 Vector3::new(0.0, 0.0, 0.0),
1927 epsilon = 1e-10
1928 );
1929 }
1930
1931 #[test]
1932 fn test_lazy_rotation_pose() {
1933 use nalgebra::UnitQuaternion;
1934 let root = Frame::new_origin("root");
1935 let pose = root.add_pose(Vector3::new(0.0, 0.0, 0.0), UnitQuaternion::identity());
1936 let result = &pose * rz(std::f64::consts::FRAC_PI_4);
1937
1938 let (roll, pitch, yaw) = result.transformation().rotation.euler_angles();
1939 assert_relative_eq!(
1940 Vector3::new(roll, pitch, yaw),
1941 Vector3::new(0.0, 0.0, std::f64::consts::FRAC_PI_4),
1942 epsilon = 1e-10
1943 );
1944 assert_relative_eq!(
1945 result.transformation().translation.vector,
1946 Vector3::new(0.0, 0.0, 0.0),
1947 epsilon = 1e-10
1948 );
1949 let (roll, pitch, yaw) = pose.transformation().rotation.euler_angles();
1950 assert_relative_eq!(
1951 Vector3::new(roll, pitch, yaw),
1952 Vector3::new(0.0, 0.0, 0.0),
1953 epsilon = 1e-10
1954 );
1955 }
1956}