Skip to main content

cartesian_tree/
frame.rs

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    /// Generational key identifying a frame node in a tree arena. Stale keys (of removed
16    /// nodes) are detected by the generation counter and never alias a new node.
17    pub(crate) struct NodeKey;
18}
19
20/// A single frame node stored in a tree arena.
21#[derive(Debug)]
22struct Node {
23    /// The name of the frame (must be unique among siblings).
24    name: String,
25    /// The parent node, or `None` for the root.
26    parent: Option<NodeKey>,
27    /// Child nodes directly connected to this node.
28    children: Vec<NodeKey>,
29    /// Transformation from this frame to its parent frame.
30    transform_to_parent: Isometry3<f64>,
31}
32
33/// The arena storage shared by all frames and poses of one tree.
34#[derive(Debug)]
35pub(crate) struct TreeInner {
36    nodes: SlotMap<NodeKey, Node>,
37}
38
39pub(crate) type SharedTree = Arc<RwLock<TreeInner>>;
40
41/// Acquires the tree read lock, recovering from poisoning (a panicked writer).
42pub(crate) fn read_tree(tree: &SharedTree) -> RwLockReadGuard<'_, TreeInner> {
43    tree.read().unwrap_or_else(PoisonError::into_inner)
44}
45
46/// Acquires the tree write lock, recovering from poisoning (a panicked writer).
47pub(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    /// Finds the lowest common ancestor of two nodes, or `None` if they are unconnected.
117    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    /// Accumulates the transformation from `start` (pre-composed with `start_offset`)
148    /// up to the ancestor `target`.
149    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/// Identifies which kind of frame a handle refers to.
173#[derive(Clone, Debug)]
174pub(crate) enum FrameKind {
175    /// A regular frame stored in the tree arena.
176    Node(NodeKey),
177    /// A frame derived by the lazy operators: anchored to an arena node with a fixed
178    /// offset, but not stored in the arena itself. Derived frames are read-only.
179    Derived {
180        anchor: NodeKey,
181        offset: Isometry3<f64>,
182        name: String,
183    },
184}
185
186impl FrameKind {
187    /// The arena node this frame resolves transforms through.
188    pub(crate) const fn anchor(&self) -> NodeKey {
189        match self {
190            Self::Node(key) => *key,
191            Self::Derived { anchor, .. } => *anchor,
192        }
193    }
194
195    /// The fixed offset of this frame relative to its anchor node (identity for nodes).
196    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/// Represents a coordinate frame in a Cartesian tree structure.
205///
206/// Each frame can have one parent and multiple children. The frame stores its
207/// transformation (position and orientation) relative to its parent.
208///
209/// Root frames (created via `Frame::new_origin`) have no parent and use the identity transform.
210///
211/// # Ownership, lifetimes, and thread safety
212///
213/// All frames of a tree share ownership of a single tree arena: keeping any `Frame`
214/// (or [`Pose`]) handle alive keeps the whole tree alive, so a leaf handle is always
215/// enough to reach the root. Frames removed via [`Frame::remove_child`] become stale;
216/// operations on stale handles return [`CartesianTreeError::FrameRemoved`].
217///
218/// `Frame` is `Send + Sync` and can be shared freely across threads. All operations
219/// are synchronized through a tree-wide read/write lock, and every operation
220/// (including multi-frame computations like [`Pose::in_frame`]) sees a consistent
221/// snapshot of the tree.
222#[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        // Deliberately does not lock the tree, so Debug is safe in any context.
231        f.debug_struct("Frame")
232            .field("kind", &self.kind)
233            .finish_non_exhaustive()
234    }
235}
236
237impl Frame {
238    /// Creates a new root frame (origin) with the given name.
239    ///
240    /// This allocates a new tree; all frames added below the root share it.
241    /// The origin has no parent and uses the identity transform.
242    /// # Arguments
243    /// - `name`: The name of the root frame.
244    ///
245    /// # Example
246    /// ```
247    /// use cartesian_tree::Frame;
248    ///
249    /// let origin = Frame::new_origin("world");
250    /// ```
251    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    /// Returns the key of this frame if it is a regular (non-derived) frame,
274    /// or a [`CartesianTreeError::DerivedFrameUnsupported`] error otherwise.
275    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    /// Returns the name of the frame.
285    ///
286    /// Returns `"<removed>"` for stale handles whose frame has been removed from the tree.
287    #[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    /// Returns the transformation from this frame to its parent frame.
296    ///
297    /// # Returns
298    /// - The isometry from this frame to its parent frame.
299    ///
300    /// # Errors
301    /// Returns a [`CartesianTreeError`] if:
302    /// - The frame has no parent.
303    /// - The frame has been removed from its tree.
304    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    /// Returns the transformation of this frame relative to its parent, where root
319    /// frames report identity (unlike [`Frame::transformation`], which errors).
320    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    /// Returns the position of this frame relative to its parent frame.
328    ///
329    /// # Returns
330    /// The position of the frame in its parent frame (zero for root frames).
331    ///
332    /// # Errors
333    /// Returns a [`CartesianTreeError`] if:
334    /// - The frame has been removed from its tree.
335    pub fn position(&self) -> Result<Vector3<f64>, CartesianTreeError> {
336        Ok(self.local_transform()?.translation.vector)
337    }
338
339    /// Returns the orientation of this frame relative to its parent frame.
340    ///
341    /// # Returns
342    /// The orientation of the frame in its parent frame (identity for root frames).
343    ///
344    /// # Errors
345    /// Returns a [`CartesianTreeError`] if:
346    /// - The frame has been removed from its tree.
347    pub fn orientation(&self) -> Result<Rotation, CartesianTreeError> {
348        Ok(self.local_transform()?.rotation.into())
349    }
350
351    /// Sets the frame's transformation relative to its parent.
352    ///
353    /// This method modifies the frame's position and orientation relative to its parent frame.
354    ///
355    /// # Arguments
356    /// - `position`: A 3D vector representing the new translational offset from the parent.
357    /// - `orientation`: An orientation convertible into a unit quaternion for new orientational offset from the parent.
358    ///
359    /// # Returns
360    /// - `Ok(())` if the transformation was updated successfully.
361    ///
362    /// # Errors
363    /// Returns a [`CartesianTreeError`] if:
364    /// - The frame has no parent (i.e., the root frame).
365    /// - The frame has been removed from its tree.
366    /// - The frame is a derived frame (derived frames are read-only).
367    ///
368    /// # Example
369    /// ```
370    /// use cartesian_tree::Frame;
371    /// use nalgebra::{Vector3, UnitQuaternion};
372    ///
373    /// let root = Frame::new_origin("root");
374    /// let child = root
375    ///     .add_child("camera", Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity())
376    ///     .unwrap();
377    /// child.set(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
378    ///     .unwrap();
379    /// ```
380    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    /// Applies the provided isometry interpreted in the parent frame to this frame.
393    ///
394    /// This method modifies the frame's position and orientation relative to its current position and orientation.
395    ///
396    /// # Arguments
397    /// - `isometry`: The isometry (describing a motion in the parent frame coordinates) to apply to the current transformation.
398    ///
399    /// # Returns
400    /// - `Ok(())` if the transformation was updated successfully.
401    ///
402    /// # Errors
403    /// Returns a [`CartesianTreeError`] if:
404    /// - The frame has no parent (i.e., the root frame).
405    /// - The frame has been removed from its tree.
406    /// - The frame is a derived frame (derived frames are read-only).
407    ///
408    /// # Example
409    /// ```
410    /// use cartesian_tree::Frame;
411    /// use nalgebra::{Isometry3, Translation3, Vector3, UnitQuaternion};
412    ///
413    /// let root = Frame::new_origin("root");
414    /// let child = root
415    ///     .add_child("camera", Vector3::new(1.0, 0.0, 1.0), UnitQuaternion::identity())
416    ///     .unwrap();
417    /// child.apply_in_parent_frame(&Isometry3::from_parts(Translation3::new(1.0, 0.0, 0.0), UnitQuaternion::identity()))
418    ///     .unwrap();
419    ///
420    /// ```
421    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    /// Applies the provided isometry interpreted in this frame to this frame.
429    ///
430    /// This method modifies the frame's position and orientation relative to its current position and orientation.
431    ///
432    /// # Arguments
433    /// - `isometry`: The isometry (describing a motion in this frame) to apply to the current transformation.
434    ///
435    /// # Returns
436    /// - `Ok(())` if the transformation was updated successfully.
437    ///
438    /// # Errors
439    /// Returns a [`CartesianTreeError`] if:
440    /// - The frame has no parent (i.e., the root frame).
441    /// - The frame has been removed from its tree.
442    /// - The frame is a derived frame (derived frames are read-only).
443    ///
444    /// # Example
445    /// ```
446    /// use cartesian_tree::Frame;
447    /// use nalgebra::{Isometry3, Translation3, Vector3, UnitQuaternion};
448    ///
449    /// let root = Frame::new_origin("root");
450    /// let child = root
451    ///     .add_child("camera", Vector3::zeros(), UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2))
452    ///     .unwrap();
453    /// child.apply_in_local_frame(&Isometry3::from_parts(Translation3::new(1.0, 0.0, 0.0), UnitQuaternion::identity()))
454    ///     .unwrap();
455    ///
456    /// ```
457    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    /// Applies `update` to this frame's transform-to-parent under the write lock.
465    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    /// Adds a new child frame to the current frame.
482    ///
483    /// The child is positioned and oriented relative to this frame.
484    ///
485    /// Returns an error if a child with the same name already exists.
486    ///
487    /// # Arguments
488    /// - `name`: The name of the new child frame.
489    /// - `position`: A 3D vector representing the translational offset from the parent.
490    /// - `orientation`: An orientation convertible into a unit quaternion.
491    ///
492    /// # Returns
493    /// The newly added child frame.
494    ///
495    /// # Errors
496    /// Returns a [`CartesianTreeError`] if:
497    /// - A child with the same name already exists.
498    /// - The frame has been removed from its tree.
499    /// - The frame is a derived frame.
500    ///
501    /// # Example
502    /// ```
503    /// use cartesian_tree::Frame;
504    /// use nalgebra::{Vector3, UnitQuaternion};
505    ///
506    /// let root = Frame::new_origin("base");
507    /// let child = root
508    ///     .add_child("camera", Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity())
509    ///     .unwrap();
510    /// ```
511    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    /// Removes the child with the given name and its entire subtree from the tree.
530    ///
531    /// Existing handles to removed frames become stale and return
532    /// [`CartesianTreeError::FrameRemoved`] when used.
533    ///
534    /// # Arguments
535    /// - `name`: The name of the child frame to remove.
536    ///
537    /// # Errors
538    /// Returns a [`CartesianTreeError`] if:
539    /// - No child with the given name exists.
540    /// - The frame has been removed from its tree.
541    /// - The frame is a derived frame.
542    ///
543    /// # Example
544    /// ```
545    /// use cartesian_tree::Frame;
546    /// use cartesian_tree::tree::HasChildren;
547    /// use nalgebra::{Vector3, UnitQuaternion};
548    ///
549    /// let root = Frame::new_origin("root");
550    /// let _ = root
551    ///     .add_child("camera", Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity())
552    ///     .unwrap();
553    /// root.remove_child("camera").unwrap();
554    /// assert!(root.children().is_empty());
555    /// ```
556    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    /// Adds a new child frame calibrated such that a reference pose, when expressed in the new frame,
573    /// matches the desired position and orientation.
574    ///
575    /// # Arguments
576    /// - `name`: The name of the new child frame.
577    /// - `desired_position`: The desired position of the reference pose in the new frame.
578    /// - `desired_orientation`: The desired orientation of the reference pose in the new frame.
579    /// - `reference_pose`: The existing pose (in some frame A) used as the calibration reference.
580    ///
581    /// # Returns
582    /// - The new child frame if successful.
583    ///
584    /// # Errors
585    /// Returns a [`CartesianTreeError`] if:
586    /// - The reference pose belongs to a different tree or its frame has been removed.
587    /// - No common ancestor exists.
588    /// - A child with the same name already exists.
589    /// - The frame is a derived frame.
590    ///
591    /// # Example
592    /// ```
593    /// use cartesian_tree::Frame;
594    /// use nalgebra::{Vector3, UnitQuaternion};
595    ///
596    /// let root = Frame::new_origin("root");
597    /// let reference_pose = root.add_pose(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity());
598    /// let calibrated_child = root.calibrate_child(
599    ///     "calibrated",
600    ///     Vector3::zeros(),
601    ///     UnitQuaternion::identity(),
602    ///     &reference_pose,
603    /// ).unwrap();
604    /// ```
605    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    /// Adds a pose to the current frame.
653    ///
654    /// # Arguments
655    /// - `position`: The translational part of the pose.
656    /// - `orientation`: The orientational part of the pose.
657    ///
658    /// # Returns
659    /// - The newly added pose.
660    ///
661    /// # Example
662    /// ```
663    /// use cartesian_tree::Frame;
664    /// use nalgebra::{Vector3, UnitQuaternion};
665    ///
666    /// let frame = Frame::new_origin("base");
667    /// let pose = frame.add_pose(Vector3::new(0.5, 0.0, 0.0), UnitQuaternion::identity());
668    /// ```
669    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    /// Serializes the frame tree to a JSON string.
679    ///
680    /// This recursively serializes the hierarchy starting from this frame (ideally the root).
681    /// Transforms for root frames are set to identity.
682    ///
683    /// # Returns
684    /// The serialized tree as a JSON string.
685    ///
686    /// # Errors
687    /// Returns a [`CartesianTreeError`] if:
688    /// - On serialization failure.
689    /// - The frame has been removed from its tree, or is a derived frame.
690    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    /// Applies a JSON config to this frame tree by updating matching transforms.
698    ///
699    /// Deserializes the JSON to a temporary structure, then recursively updates transforms
700    /// where names match (partial apply; ignores unmatched frames in config).
701    /// Skips updating root frames (identity assumed) - assumes this frame is the root.
702    ///
703    /// # Arguments
704    /// - `json`: The JSON string to apply.
705    ///
706    /// # Returns
707    /// `Ok(())` if applied successfully (even if partial).
708    ///
709    /// # Errors
710    /// Returns a [`CartesianTreeError`] if:
711    /// - On deserialization failure.
712    /// - The frame names do not match at the root.
713    /// - An orientation in the config has a norm too close to zero to normalize.
714    /// - The frame has been removed from its tree, or is a derived frame.
715    ///
716    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    /// Creates a derived frame that coincides with this frame moved by `isometry`,
724    /// where `isometry` is interpreted in this frame's parent coordinates
725    /// (like [`Frame::apply_in_parent_frame`]).
726    ///
727    /// For root frames, the parent coordinates are the root's own coordinates.
728    fn derive_in_parent_frame(&self, isometry: &Isometry3<f64>) -> Self {
729        let (anchor, offset) = match &self.kind {
730            FrameKind::Node(key) => {
731                // The derived frame is anchored to this node, so conjugate the
732                // parent-frame motion by this node's own transform (identity for roots).
733                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    /// Creates a derived frame that coincides with this frame moved by `isometry`,
753    /// where `isometry` is interpreted in this frame's own coordinates
754    /// (like [`Frame::apply_in_local_frame`]).
755    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    // Deserialized as a plain quaternion because nalgebra does not re-normalize
776    // `UnitQuaternion`s on deserialization; validated in `apply_serial`.
777    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    // only update if frame has parent
817    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
843/// Creates a new derived frame translated by `rhs`, interpreted in the
844/// parent frame of `self` (matching the `Pose` operator semantics).
845///
846/// The derived frame resolves transforms through `self` but is not stored in the tree:
847/// it does not appear in `children()` or serialization, is read-only, and is freed when
848/// dropped.
849impl 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
857/// Creates a new derived frame translated by the inverse of `rhs`, interpreted
858/// in the parent frame of `self` (matching the `Pose` operator semantics).
859///
860/// The derived frame resolves transforms through `self` but is not stored in the tree:
861/// it does not appear in `children()` or serialization, is read-only, and is freed when
862/// dropped.
863impl 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
871/// Creates a new derived frame rotated by `rhs` about the axes of `self`
872/// (local frame, matching the `Pose` operator semantics).
873///
874/// The derived frame resolves transforms through `self` but is not stored in the tree:
875/// it does not appear in `children()` or serialization, is read-only, and is freed when
876/// dropped.
877impl 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        // Handles to removed frames (including the subtree) become stale.
1056        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        // The name becomes available again.
1068        assert!(
1069            root.add_child("child", Vector3::zeros(), UnitQuaternion::identity())
1070                .is_ok()
1071        );
1072
1073        // Unknown names are rejected.
1074        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        }; // All other handles are dropped here; the leaf keeps the tree alive.
1098
1099        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        }; // The intermediate frame handle is dropped here.
1173
1174        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        // Test root frame error
1232        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        // Total offset should be: f2 (0,2,0) + pose (1,1,0) + f1 (1,0,0)
1423        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        // Two branches, each two levels deep, all with non-identity transforms:
1429        // expressing a pose in the other branch and back must be lossless.
1430        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        // Calibrate a child where the reference pose should appear at (0,0,0) with identity orientation.
1546        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        // Verify the child's transform matches the reference pose's original transform.
1562        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        // The parent of the calibrated frame is NOT the common ancestor and has a
1577        // non-identity transform, so the composition order actually matters here.
1578        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        // The reference pose expressed in the calibrated frame must match the desired transform.
1605        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        // roughly verify JSON structure
1632        assert!(json.contains(r#""name": "root""#));
1633        assert!(json.contains(r#""name": "child""#));
1634
1635        // Create a default tree with different transforms
1636        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        // Apply config
1646        default_root.apply_config(&json).unwrap();
1647
1648        // Verify child transform updated
1649        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        // Test partial: If config has extra, ignore it
1662        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        // Test mismatch
1695        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        // Non-unit quaternions are normalized on load.
1714        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        // Zero-norm quaternions are rejected.
1735        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        // Chained operations accumulate in world coordinates.
1768        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        // Translation is interpreted in the parent frame: the derived frame must sit at
1831        // child + (0, 3, 0) in root coordinates, with unchanged orientation.
1832        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        // Rotation is interpreted in the local frame: position unchanged, yaw doubled.
1850        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        // Derived frames must not accumulate in the tree.
1880        assert!(child.children().is_empty());
1881        assert_eq!(root.children().len(), 1);
1882
1883        // They still resolve transforms through the parent chain.
1884        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}