cartesian_tree/
frame.rs

1use crate::CartesianTreeError;
2use crate::Pose;
3use crate::orientation::IntoOrientation;
4use crate::tree::{HasChildren, HasParent, NodeEquality};
5
6use nalgebra::{Isometry3, Translation3, Vector3};
7use std::cell::RefCell;
8use std::rc::{Rc, Weak};
9
10/// Represents a coordinate frame in a Cartesian tree structure.
11///
12/// Each frame can have one parent and multiple children. The frame stores its
13/// transformation (position and orientation) relative to its parent.
14///
15/// Root frames (created via `Frame::new_origin`) have no parent and use the identity transform.
16#[derive(Clone, Debug)]
17pub struct Frame {
18    pub(crate) data: Rc<RefCell<FrameData>>,
19}
20
21#[derive(Debug)]
22pub(crate) struct FrameData {
23    /// The name of the frame (must be unique among siblings).
24    pub(crate) name: String,
25    /// Reference to the parent frame.
26    parent: Option<Weak<RefCell<FrameData>>>,
27    /// Transformation from this frame to its parent frame.
28    transform_to_parent: Isometry3<f64>,
29    /// Child frames directly connected to this frame.
30    children: Vec<Frame>,
31}
32
33impl Frame {
34    /// Creates a new root frame (origin) with the given name.
35    ///
36    /// The origin has no parent and uses the identity transform.
37    /// # Arguments
38    /// - `name`: The name of the root frame.
39    ///
40    /// # Example
41    /// ```
42    /// use cartesian_tree::Frame;
43    ///
44    /// let origin = Frame::new_origin("world");
45    /// ```
46    pub fn new_origin(name: impl Into<String>) -> Self {
47        Frame {
48            data: Rc::new(RefCell::new(FrameData {
49                name: name.into(),
50                parent: None,
51                children: Vec::new(),
52                transform_to_parent: Isometry3::identity(),
53            })),
54        }
55    }
56
57    pub(crate) fn borrow(&self) -> std::cell::Ref<FrameData> {
58        self.data.borrow()
59    }
60
61    fn borrow_mut(&self) -> std::cell::RefMut<FrameData> {
62        self.data.borrow_mut()
63    }
64
65    pub(crate) fn downgrade(&self) -> Weak<RefCell<FrameData>> {
66        Rc::downgrade(&self.data)
67    }
68
69    pub(crate) fn walk_up_and_transform(
70        &self,
71        target: &Frame,
72    ) -> Result<Isometry3<f64>, CartesianTreeError> {
73        let mut transform = Isometry3::identity();
74        let mut current = self.clone();
75
76        while !current.is_same(target) {
77            let transform_to_its_parent = {
78                // Scope borrow
79                let current_data = current.borrow();
80
81                // If current frame is root and not target, then target is not an ancestor.
82                if current_data.parent.is_none() {
83                    return Err(CartesianTreeError::IsNoAncestor(target.name(), self.name()));
84                }
85                current_data.transform_to_parent
86            };
87
88            transform = transform_to_its_parent * transform;
89
90            let parent_frame_opt = current.parent();
91            current = parent_frame_opt
92                .ok_or_else(|| CartesianTreeError::IsNoAncestor(target.name(), self.name()))?;
93        }
94
95        Ok(transform)
96    }
97
98    /// Returns the name of the frame.
99    pub fn name(&self) -> String {
100        self.borrow().name.clone()
101    }
102
103    /// Returns the transformation from this frame to its parent frame.
104    ///
105    /// # Returns
106    /// - `Ok(Isometry3<f64>)` if the frame has a parent.
107    /// - `Err(String)` if the frame has no parent.
108    pub fn transform_to_parent(&self) -> Result<Isometry3<f64>, CartesianTreeError> {
109        if self.parent().is_none() {
110            return Err(CartesianTreeError::RootHasNoParent(self.name()));
111        }
112        Ok(self.borrow().transform_to_parent)
113    }
114
115    /// Updates the frame's transformation relative to its parent.
116    ///
117    /// This method modifies the frame's position and orientation relative to its parent frame.
118    /// It fails if the frame is a root frame (i.e., has no parent).
119    ///
120    /// # Arguments
121    /// - `position`: A 3D vector representing the new translational offset from the parent.
122    /// - `orientation`: An orientation convertible into a unit quaternion for new orientational offset from the parent.
123    ///
124    /// # Returns
125    /// - `Ok(())` if the transformation was updated successfully.
126    /// - `Err(String)` if the frame has no parent.
127    ///
128    /// # Example
129    /// ```
130    /// use cartesian_tree::Frame;
131    /// use nalgebra::{Vector3, UnitQuaternion};
132    ///
133    /// let root = Frame::new_origin("root");
134    /// let child = root
135    ///     .add_child("camera", Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity())
136    ///     .unwrap();
137    /// child.update_transform(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
138    ///     .unwrap();
139    /// ```
140    pub fn update_transform<O>(
141        &self,
142        position: Vector3<f64>,
143        orientation: O,
144    ) -> Result<(), CartesianTreeError>
145    where
146        O: IntoOrientation,
147    {
148        if self.parent().is_none() {
149            return Err(CartesianTreeError::CannotUpdateRootTransform(self.name()));
150        }
151        self.borrow_mut().transform_to_parent =
152            Isometry3::from_parts(Translation3::from(position), orientation.into_orientation());
153        Ok(())
154    }
155
156    /// Adds a new child frame to the current frame.
157    ///
158    /// The child is positioned and oriented relative to this frame.
159    ///
160    /// Returns an error if a child with the same name already exists.
161    ///
162    /// # Arguments
163    /// - `name`: The name of the new child frame.
164    /// - `position`: A 3D vector representing the translational offset from the parent.
165    /// - `orientation`: An orientation convertible into a unit quaternion.
166    ///
167    /// # Returns
168    /// - `Ok(Rc<Frame>)` the newly added child frame.
169    /// - `Err(String)` if a child with the same name already exists.
170    ///
171    /// # Example
172    /// ```
173    /// use cartesian_tree::Frame;
174    /// use nalgebra::{Vector3, UnitQuaternion};
175    ///
176    /// let root = Frame::new_origin("base");
177    /// let child = root
178    ///     .add_child("camera", Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity())
179    ///     .unwrap();
180    /// ```
181    pub fn add_child<O>(
182        &self,
183        name: impl Into<String>,
184        position: Vector3<f64>,
185        orientation: O,
186    ) -> Result<Frame, CartesianTreeError>
187    where
188        O: IntoOrientation,
189    {
190        let child_name = name.into();
191        {
192            let frame = self.borrow();
193            if frame
194                .children
195                .iter()
196                .any(|child| child.borrow().name == child_name)
197            {
198                return Err(CartesianTreeError::ChildNameConflict(
199                    child_name,
200                    self.name(),
201                ));
202            }
203        }
204        let quat = orientation.into_orientation();
205        let transform = Isometry3::from_parts(Translation3::from(position), quat);
206
207        let child = Frame {
208            data: Rc::new(RefCell::new(FrameData {
209                name: child_name,
210                parent: Some(Rc::downgrade(&self.data)),
211                children: Vec::new(),
212                transform_to_parent: transform,
213            })),
214        };
215
216        self.borrow_mut().children.push(child.clone());
217        Ok(child)
218    }
219
220    /// Adds a pose to the current frame.
221    ///
222    /// # Arguments
223    /// - `position`: The translational part of the pose.
224    /// - `orientation`: The orientational part of the pose.
225    ///
226    /// # Returns
227    /// - The newly added pose.
228    ///
229    /// # Example
230    /// ```
231    /// use cartesian_tree::Frame;
232    /// use nalgebra::{Vector3, UnitQuaternion};
233    ///
234    /// let frame = Frame::new_origin("base");
235    /// let pose = frame.add_pose(Vector3::new(0.5, 0.0, 0.0), UnitQuaternion::identity());
236    /// ```
237    pub fn add_pose<O>(&self, position: Vector3<f64>, orientation: O) -> Pose
238    where
239        O: IntoOrientation,
240    {
241        Pose::new(self.downgrade(), position, orientation)
242    }
243}
244
245impl HasParent for Frame {
246    type Node = Frame;
247
248    fn parent(&self) -> Option<Self::Node> {
249        self.borrow()
250            .parent
251            .clone()
252            .and_then(|data_weak| data_weak.upgrade().map(|data_rc| Frame { data: data_rc }))
253    }
254}
255
256impl NodeEquality for Frame {
257    fn is_same(&self, other: &Self) -> bool {
258        Rc::ptr_eq(&self.data, &other.data)
259    }
260}
261
262impl HasChildren for Frame {
263    type Node = Frame;
264    fn children(&self) -> Vec<Frame> {
265        self.borrow().children.clone()
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use nalgebra::{UnitQuaternion, Vector3};
273
274    #[test]
275    fn create_origin_frame() {
276        let root = Frame::new_origin("world");
277        let root_borrow = root.borrow();
278        assert_eq!(root_borrow.name, "world");
279        assert!(root_borrow.parent.is_none());
280        assert_eq!(root_borrow.children.len(), 0);
281    }
282
283    #[test]
284    fn add_child_frame_with_quaternion() {
285        let root = Frame::new_origin("world");
286        let child = root
287            .add_child(
288                "dummy",
289                Vector3::new(1.0, 0.0, 0.0),
290                UnitQuaternion::identity(),
291            )
292            .unwrap();
293
294        let root_borrow = root.borrow();
295        assert_eq!(root_borrow.children.len(), 1);
296
297        let child_borrow = child.borrow();
298        assert_eq!(child_borrow.name, "dummy");
299        assert!(child_borrow.parent.is_some());
300
301        let parent_name = child_borrow
302            .parent
303            .as_ref()
304            .unwrap()
305            .upgrade()
306            .unwrap()
307            .borrow()
308            .name
309            .clone();
310        assert_eq!(parent_name, "world");
311    }
312
313    #[test]
314    fn add_child_frame_with_rpy() {
315        let root = Frame::new_origin("world");
316        let child = root
317            .add_child(
318                "dummy",
319                Vector3::new(0.0, 1.0, 0.0),
320                (0.0, 0.0, std::f64::consts::FRAC_PI_2),
321            )
322            .unwrap();
323
324        let child_borrow = child.borrow();
325        assert_eq!(child_borrow.name, "dummy");
326
327        let rotation = child_borrow.transform_to_parent.rotation;
328        let expected = UnitQuaternion::from_euler_angles(0.0, 0.0, std::f64::consts::FRAC_PI_2);
329        assert!((rotation.angle() - expected.angle()).abs() < 1e-10);
330    }
331
332    #[test]
333    fn multiple_child_frames() {
334        let root = Frame::new_origin("world");
335
336        let a = root
337            .add_child("a", Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
338            .unwrap();
339        let b = root
340            .add_child("b", Vector3::new(0.0, 1.0, 0.0), UnitQuaternion::identity())
341            .unwrap();
342
343        let root_borrow = root.borrow();
344        assert_eq!(root_borrow.children.len(), 2);
345
346        let a_borrow = a.borrow();
347        let b_borrow = b.borrow();
348
349        assert_eq!(
350            a_borrow
351                .parent
352                .as_ref()
353                .unwrap()
354                .upgrade()
355                .unwrap()
356                .borrow()
357                .name,
358            "world"
359        );
360        assert_eq!(
361            b_borrow
362                .parent
363                .as_ref()
364                .unwrap()
365                .upgrade()
366                .unwrap()
367                .borrow()
368                .name,
369            "world"
370        );
371    }
372
373    #[test]
374    fn reject_duplicate_child_name() {
375        let root = Frame::new_origin("world");
376
377        let _ = root
378            .add_child(
379                "duplicate",
380                Vector3::new(1.0, 0.0, 0.0),
381                UnitQuaternion::identity(),
382            )
383            .unwrap();
384
385        let result = root.add_child(
386            "duplicate",
387            Vector3::new(2.0, 0.0, 0.0),
388            UnitQuaternion::identity(),
389        );
390        assert!(result.is_err());
391    }
392
393    #[test]
394    #[should_panic(expected = "already borrowed")]
395    fn test_borrow_conflict() {
396        let frame = Frame::new_origin("root");
397        let _borrow = frame.borrow(); // Immutable borrow
398        frame.borrow_mut(); // Should panic
399    }
400
401    #[test]
402    fn test_add_pose_to_frame() {
403        let frame = Frame::new_origin("dummy");
404        let pose = frame.add_pose(Vector3::new(1.0, 2.0, 3.0), UnitQuaternion::identity());
405
406        assert_eq!(pose.frame_name().as_deref(), Some("dummy"));
407    }
408
409    #[test]
410    fn test_update_transform() {
411        let root = Frame::new_origin("root");
412        let child = root
413            .add_child(
414                "dummy",
415                Vector3::new(0.0, 0.0, 1.0),
416                UnitQuaternion::identity(),
417            )
418            .unwrap();
419        child
420            .update_transform(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
421            .unwrap();
422        assert_eq!(
423            child.transform_to_parent().unwrap().translation.vector,
424            Vector3::new(1.0, 0.0, 0.0)
425        );
426
427        // Test root frame error
428        assert!(
429            root.update_transform(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity())
430                .is_err()
431        );
432    }
433
434    #[test]
435    fn test_pose_transformation_between_frames() {
436        let root = Frame::new_origin("root");
437
438        let f1 = root
439            .add_child(
440                "f1",
441                Vector3::new(1.0, 0.0, 0.0),
442                UnitQuaternion::identity(),
443            )
444            .unwrap();
445
446        let f2 = f1
447            .add_child(
448                "f2",
449                Vector3::new(0.0, 2.0, 0.0),
450                UnitQuaternion::identity(),
451            )
452            .unwrap();
453
454        let pose_in_f2 = f2.add_pose(Vector3::new(1.0, 1.0, 0.0), UnitQuaternion::identity());
455
456        let pose_in_root = pose_in_f2.in_frame(&root).unwrap();
457        let pos = pose_in_root.transformation().translation.vector;
458
459        // Total offset should be: f2 (0,2,0) + pose (1,1,0) + f1 (1,0,0)
460        assert!((pos - Vector3::new(2.0, 3.0, 0.0)).norm() < 1e-6);
461    }
462}