Skip to main content

cartesian_tree/
pose.rs

1use crate::CartesianTreeError;
2use crate::frame::{Frame, FrameKind, SharedTree, read_tree};
3use crate::lazy_access::{LazyRotation, LazyTranslation};
4use crate::rotation::Rotation;
5use nalgebra::{Isometry3, Translation3, Vector3};
6use std::ops::{Add, Mul, Sub};
7use std::sync::Arc;
8
9/// Use [`Frame::add_pose`] to create a new pose.
10///
11/// A pose shares ownership of the tree of the frame it lives in, so holding a pose
12/// keeps the tree alive. `Pose` is `Send + Sync`.
13#[derive(Clone)]
14pub struct Pose {
15    /// The tree of the frame this pose lives in.
16    pub(crate) tree: SharedTree,
17    /// The frame this pose lives in.
18    pub(crate) anchor: FrameKind,
19    /// Transformation from this pose to its parent frame.
20    transform_to_parent: Isometry3<f64>,
21}
22
23impl std::fmt::Debug for Pose {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        // Deliberately does not lock the tree, so Debug is safe in any context.
26        f.debug_struct("Pose")
27            .field("anchor", &self.anchor)
28            .field("transform_to_parent", &self.transform_to_parent)
29            .finish_non_exhaustive()
30    }
31}
32
33impl Pose {
34    /// Creates a new pose relative to a frame.
35    ///
36    /// This function is intended for internal use. To create a pose associated with a frame,
37    /// use [`Frame::add_pose`], which handles the association safely.
38    pub(crate) fn new(
39        tree: SharedTree,
40        anchor: FrameKind,
41        position: Vector3<f64>,
42        orientation: impl Into<Rotation>,
43    ) -> Self {
44        Self {
45            tree,
46            anchor,
47            transform_to_parent: Isometry3::from_parts(
48                Translation3::from(position),
49                orientation.into().as_quaternion(),
50            ),
51        }
52    }
53
54    /// Returns the parent frame of this pose.
55    ///
56    /// # Returns
57    /// `Some(Frame)` if the frame still exists in the tree, or `None` if it has been
58    /// removed.
59    ///
60    /// # Example
61    /// ```
62    /// use cartesian_tree::Frame;
63    /// use nalgebra::{Vector3, UnitQuaternion};
64    ///
65    /// let frame = Frame::new_origin("base");
66    /// let pose = frame.add_pose(Vector3::new(0.0, 0.0, 0.0), UnitQuaternion::identity());
67    /// assert_eq!(pose.frame().unwrap().name(), "base");
68    /// ```
69    #[must_use]
70    pub fn frame(&self) -> Option<Frame> {
71        let guard = read_tree(&self.tree);
72        guard.contains(self.anchor.anchor()).then(|| Frame {
73            tree: Arc::clone(&self.tree),
74            kind: self.anchor.clone(),
75        })
76    }
77
78    /// Returns the transformation from this pose to its parent frame.
79    ///
80    /// # Returns
81    /// The transformation of the pose in its parent frame.
82    #[must_use]
83    pub const fn transformation(&self) -> Isometry3<f64> {
84        self.transform_to_parent
85    }
86
87    /// Returns the position of this pose relative to its parent frame.
88    /// # Returns
89    /// The position of the pose in its parent frame.
90    #[must_use]
91    pub const fn position(&self) -> Vector3<f64> {
92        self.transform_to_parent.translation.vector
93    }
94
95    /// Returns the orientation of this pose relative to its parent frame.
96    /// # Returns
97    /// The orientation of the pose in its parent frame.
98    #[must_use]
99    pub fn orientation(&self) -> Rotation {
100        self.transform_to_parent.rotation.into()
101    }
102
103    /// Sets the pose's transformation relative to its parent.
104    ///
105    /// # Arguments
106    /// - `position`: A 3D vector representing the new translational offset from the parent.
107    /// - `orientation`: An orientation convertible into a unit quaternion for new orientational offset from the parent.
108    ///
109    /// # Example
110    /// ```
111    /// use cartesian_tree::Frame;
112    /// use nalgebra::{Vector3, UnitQuaternion};
113    ///
114    /// let root = Frame::new_origin("root");
115    /// let mut pose = root.add_pose(Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity());
116    /// pose.set(Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity());
117    /// ```
118    pub fn set(&mut self, position: Vector3<f64>, orientation: impl Into<Rotation>) {
119        self.transform_to_parent = Isometry3::from_parts(
120            Translation3::from(position),
121            orientation.into().as_quaternion(),
122        );
123    }
124
125    /// Applies the provided isometry interpreted in the parent frame to the pose.
126    ///
127    /// # Arguments
128    /// - `isometry`: The isometry (describing a motion in the parent frame coordinates) to apply to the current transformation.
129    ///
130    /// # Example
131    /// ```
132    /// use cartesian_tree::Frame;
133    /// use nalgebra::{Isometry3, Translation3, Vector3, UnitQuaternion};
134    ///
135    /// let root = Frame::new_origin("root");
136    /// let mut pose = root.add_pose(Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity());
137    /// pose.apply_in_parent_frame(&Isometry3::from_parts(Translation3::new(1.0, 0.0, 0.0), UnitQuaternion::identity()));
138    /// ```
139    pub fn apply_in_parent_frame(&mut self, isometry: &Isometry3<f64>) {
140        self.transform_to_parent = isometry * self.transform_to_parent;
141    }
142
143    /// Applies the provided isometry interpreted in the body frame to this pose.
144    ///
145    /// # Arguments
146    /// - `isometry`: The isometry (describing a motion in the body frame coordinates) to apply to the current transformation.
147    ///
148    /// # Example
149    /// ```
150    /// use cartesian_tree::Frame;
151    /// use nalgebra::{Isometry3, Translation3, Vector3, UnitQuaternion};
152    ///
153    /// let root = Frame::new_origin("root");
154    /// let mut pose = root.add_pose(Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity());
155    /// pose.apply_in_local_frame(&Isometry3::from_parts(Translation3::new(1.0, 0.0, 0.0), UnitQuaternion::identity()));
156    /// ```
157    pub fn apply_in_local_frame(&mut self, isometry: &Isometry3<f64>) {
158        self.transform_to_parent *= isometry;
159    }
160
161    /// Transforms this pose into the coordinate system of the given target frame.
162    ///
163    /// The computation runs under a single read lock, so it sees a consistent snapshot
164    /// of the tree even while other threads are updating transforms.
165    ///
166    /// # Arguments
167    /// * `target` - The frame to express this pose in.
168    ///
169    /// # Returns
170    /// A new `Pose`, expressed in the `target` frame.
171    ///
172    /// # Errors
173    /// Returns a [`CartesianTreeError`] if:
174    /// - The pose's frame or the target frame has been removed from the tree.
175    /// - The frames belong to different trees.
176    /// - There is no common ancestor between `self` and `target`.
177    ///
178    /// # Example
179    /// ```
180    /// use cartesian_tree::Frame;
181    /// use nalgebra::{Vector3, UnitQuaternion};
182    ///
183    /// let root = Frame::new_origin("root");
184    /// let pose = root.add_pose(Vector3::new(0.0, 0.0, 1.0), UnitQuaternion::identity());
185    /// let new_frame = root.add_child("child", Vector3::new(1.0, 0.0, 0.0), UnitQuaternion::identity()).unwrap();
186    /// let pose_in_new_frame = pose.in_frame(&new_frame);
187    /// ```
188    pub fn in_frame(&self, target: &Frame) -> Result<Self, CartesianTreeError> {
189        if !Arc::ptr_eq(&self.tree, &target.tree) {
190            let own_name = self
191                .frame()
192                .map_or_else(|| "<removed>".to_owned(), |frame| frame.name());
193            return Err(CartesianTreeError::DifferentTrees(own_name, target.name()));
194        }
195
196        let guard = read_tree(&self.tree);
197        let source_anchor = self.anchor.anchor();
198        let target_anchor = target.kind.anchor();
199
200        let ancestor = guard.lca(source_anchor, target_anchor)?.ok_or_else(|| {
201            CartesianTreeError::NoCommonAncestor(
202                guard.name_of(source_anchor),
203                guard.name_of(target_anchor),
204            )
205        })?;
206
207        // Transformation from the pose up to the ancestor.
208        let tf_up = guard.transform_up(
209            source_anchor,
210            self.anchor.offset() * self.transform_to_parent,
211            ancestor,
212        )?;
213
214        // Transformation from the target's anchor up to the ancestor (to be inverted).
215        let tf_down = guard.transform_up(target_anchor, Isometry3::identity(), ancestor)?;
216
217        Ok(Self {
218            tree: Arc::clone(&self.tree),
219            anchor: target.kind.clone(),
220            transform_to_parent: target.kind.offset().inverse() * (tf_down.inverse() * tf_up),
221        })
222    }
223}
224
225impl Add<LazyTranslation> for &Pose {
226    type Output = Pose;
227
228    fn add(self, rhs: LazyTranslation) -> Self::Output {
229        let mut new_pose = self.clone();
230        new_pose.apply_in_parent_frame(&rhs.inner);
231        new_pose
232    }
233}
234
235impl Sub<LazyTranslation> for &Pose {
236    type Output = Pose;
237
238    fn sub(self, rhs: LazyTranslation) -> Self::Output {
239        let mut new_pose = self.clone();
240        new_pose.apply_in_parent_frame(&rhs.inner.inverse());
241        new_pose
242    }
243}
244
245impl Mul<LazyRotation> for &Pose {
246    type Output = Pose;
247
248    fn mul(self, rhs: LazyRotation) -> Self::Output {
249        let mut new_pose = self.clone();
250        new_pose.apply_in_local_frame(&rhs.inner);
251        new_pose
252    }
253}