Skip to main content

dynamics_joint/
continuous.rs

1//! Continuous joint, constraining two objects to rotate around a given axis, without limits.
2//!
3//! This can be seen as a revolute joint without limits.
4//! On top of that, the parametrization of the configuration is different:
5//! instead of using an angle $\theta \in [-\pi, \pi]$, continuous joints use
6//! unit circle parametrization $(\cos(\theta), \sin(\theta))$.
7
8use std::fmt::Display;
9
10use dynamics_spatial::{
11    configuration::Configuration,
12    force::SpatialForce,
13    motion::{SpatialMotion, SpatialRotation},
14    se3::SE3,
15    vector3d::Vector3D,
16};
17use rand::Rng;
18
19use crate::{
20    joint::{JointModel, JointType, JointWrapper},
21    joint_data::{JointData, JointDataWrapper},
22    limits::JointLimits,
23};
24
25/// Model of a continuous joint.
26///
27/// This joint constraints two objects to rotate around a given axis, without limits.
28#[derive(Clone, Debug)]
29pub struct JointModelContinuous {
30    /// The axis of rotation expressed in the local frame of the joint.
31    pub axis: SpatialMotion,
32    /// The joint limits.
33    pub limits: JointLimits,
34    /// The (null) bias of the joint.
35    pub bias: SpatialMotion,
36}
37
38impl JointModelContinuous {
39    /// Creates a new `JointModelContinuous` with the given axis of rotation and unbounded limits.
40    ///
41    /// The axis of rotation should be normalized and expressed in the local frame of the joint.
42    ///
43    /// # Arguments
44    ///
45    /// * `axis` - The normalized axis of rotation expressed in the local frame of the joint.
46    ///
47    /// # Returns
48    /// A new `JointModelContinuous` object.
49    #[must_use]
50    pub fn new(axis: Vector3D) -> Self {
51        let mut limits = JointLimits::new_unbounded(2);
52        limits.min_configuration[0] = -1.01;
53        limits.max_configuration[0] = 1.01;
54        limits.min_configuration[1] = -1.01;
55        limits.max_configuration[1] = 1.01;
56
57        JointModelContinuous {
58            axis: SpatialMotion::from_rotational_axis(&axis),
59            limits,
60            bias: SpatialMotion::zero(),
61        }
62    }
63
64    /// Creates a new continuous joint model with `x` as axis of rotation.
65    ///
66    /// # Returns
67    /// A new `JointModelContinuous` object.
68    #[must_use]
69    pub fn new_rux() -> Self {
70        Self::new(Vector3D::x())
71    }
72
73    /// Creates a new continuous joint model with `y` as axis of rotation.
74    ///
75    /// # Returns
76    /// A new `JointModelContinuous` object.
77    #[must_use]
78    pub fn new_ruy() -> Self {
79        Self::new(Vector3D::y())
80    }
81
82    /// Creates a new continuous joint model with `z` as axis of rotation.
83    ///
84    /// # Returns
85    /// A new `JointModelContinuous` object.
86    #[must_use]
87    pub fn new_ruz() -> Self {
88        Self::new(Vector3D::z())
89    }
90}
91
92impl JointModel for JointModelContinuous {
93    fn get_joint_type(&self) -> JointType {
94        JointType::Continuous
95    }
96
97    fn nq(&self) -> usize {
98        2
99    }
100
101    fn nv(&self) -> usize {
102        1
103    }
104
105    fn neutral(&self) -> Configuration {
106        Configuration::from_row_slice(&[1.0, 0.0])
107    }
108
109    fn get_axis(&self) -> &SpatialMotion {
110        &self.axis
111    }
112
113    fn create_joint_data(&self) -> crate::joint_data::JointDataWrapper {
114        JointDataWrapper::continuous(JointDataContinuous::new())
115    }
116
117    fn random_configuration(&self, rng: &mut rand::rngs::ThreadRng) -> Configuration {
118        let angle: f64 = rng.random_range(0.0..(2.0 * std::f64::consts::PI));
119        Configuration::from_row_slice(&[angle.cos(), angle.sin()])
120    }
121
122    fn subspace(&self, v: &Configuration) -> SpatialMotion {
123        assert_eq!(
124            v.len(),
125            1,
126            "Continuous joint model expects a single velocity value."
127        );
128        SpatialMotion::from_rotational_axis(&(v[0] * self.axis.rotation()))
129    }
130
131    fn subspace_dual(&self, f: &SpatialForce) -> Configuration {
132        Configuration::from_row_slice(&[f.rotation().dot(&self.axis.rotation())])
133    }
134
135    fn subspace_se3(&self, se3: &SE3) -> SpatialMotion {
136        let rot = se3.rotation();
137        let v = rot * &self.axis.rotation();
138        SpatialMotion::from_rotational_axis(&v)
139    }
140
141    fn bias(&self) -> &SpatialMotion {
142        &self.bias
143    }
144
145    fn integrate(&self, q: &Configuration, v: &Configuration) -> Configuration {
146        // compute angle from cosine and sine
147        let angle = q[1].atan2(q[0]);
148
149        // integrate angle
150        let new_angle = angle + v[0];
151
152        // return new configuration as cosine and sine of the new angle
153        Configuration::from_row_slice(&[new_angle.cos(), new_angle.sin()])
154    }
155}
156
157impl Display for JointModelContinuous {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        write!(f, "JointModelContinuous(axis: {:?})", self.axis.as_slice())
160    }
161}
162
163/// Data structure containing the mutable properties of a continuous joint.
164#[derive(Debug, Clone)]
165pub struct JointDataContinuous {
166    /// The joint configuration vector (cosine and sine of the angle).
167    pub joint_q: Configuration,
168    /// The joint velocity vector (angular velocity).
169    pub joint_v: Configuration,
170    /// The placement of the joint in the local frame.
171    pub placement: SE3,
172    /// The joint velocity as a spatial motion.
173    pub joint_velocity: SpatialMotion,
174}
175
176impl Default for JointDataContinuous {
177    fn default() -> Self {
178        JointDataContinuous {
179            joint_q: Configuration::from_row_slice(&[1.0, 0.0]),
180            joint_v: Configuration::from_row_slice(&[0.0]),
181            placement: SE3::identity(),
182            joint_velocity: SpatialMotion::zero(),
183        }
184    }
185}
186
187impl JointDataContinuous {
188    /// Creates a new `JointDataContinuous` object.
189    ///
190    /// # Arguments
191    ///
192    /// * `model` - The continuous joint model.
193    ///
194    /// # Returns
195    /// A new `JointDataContinuous` object.
196    #[must_use]
197    pub fn new() -> Self {
198        JointDataContinuous::default()
199    }
200
201    /// Returns the cosine of the joint angle.
202    #[must_use]
203    pub fn cos(&self) -> f64 {
204        self.joint_q[0]
205    }
206
207    /// Returns the sine of the joint angle.
208    #[must_use]
209    pub fn sin(&self) -> f64 {
210        self.joint_q[1]
211    }
212}
213
214impl JointData for JointDataContinuous {
215    fn get_joint_q(&self) -> &Configuration {
216        &self.joint_q
217    }
218
219    fn get_joint_v(&self) -> &Configuration {
220        &self.joint_v
221    }
222
223    fn update(
224        &mut self,
225        joint_model: &JointWrapper,
226        joint_q: &Configuration,
227        joint_v: Option<&Configuration>,
228    ) {
229        // TODO: optimize this method to avoid computing the angle
230
231        assert_eq!(
232            joint_q.len(),
233            2,
234            "Continuous joint model expects two values (cosine and sine)."
235        );
236        if let Some(joint_v) = joint_v {
237            assert_eq!(
238                joint_v.len(),
239                1,
240                "Continuous joint model expects a single velocity value."
241            );
242        }
243
244        // store q and v
245        self.joint_q = joint_q.clone();
246        if let Some(joint_v) = joint_v {
247            self.joint_v = joint_v.clone();
248            self.joint_velocity = self.joint_v[0] * joint_model.get_axis().clone();
249        }
250
251        // compute angle from cosine and sine
252        let angle = self.sin().atan2(self.cos());
253
254        // compute placement
255        let rot = SpatialRotation::from_axis_angle(&joint_model.get_axis().rotation(), angle);
256        self.placement = rot.to_se3(&Vector3D::zeros());
257    }
258
259    fn get_joint_placement(&self) -> SE3 {
260        self.placement
261    }
262
263    fn get_joint_velocity(&self) -> &SpatialMotion {
264        &self.joint_velocity
265    }
266}