Skip to main content

dynamics_joint/
revolute.rs

1//! Revolute joint, constraining two objects to rotate around a given axis.
2
3use crate::{
4    joint::{JointModel, JointType, JointWrapper},
5    joint_data::{JointData, JointDataWrapper, JointError},
6    limits::JointLimits,
7};
8use dynamics_spatial::{
9    configuration::Configuration,
10    motion::{SpatialMotion, SpatialRotation},
11    se3::SE3,
12    vector3d::Vector3D,
13};
14use rand::rngs::ThreadRng;
15
16/// Model of a revolute joint.
17///
18/// This joint constraints two objects to rotate around a given axis.
19#[derive(Clone, Debug)]
20pub struct JointModelRevolute {
21    /// The axis of rotation expressed in the local frame of the joint.
22    pub axis: Vector3D,
23    /// The joint limits.
24    pub limits: JointLimits,
25}
26
27impl JointModelRevolute {
28    /// Creates a new `JointModelRevolute` with the given axis of rotation and unbounded limits.
29    ///
30    /// # Arguments
31    ///
32    /// * `axis` - The axis of rotation expressed in the local frame of the joint.
33    ///
34    /// # Returns
35    /// A new `JointModelRevolute` object.
36    #[must_use]
37    pub fn new(axis: Vector3D) -> Self {
38        JointModelRevolute {
39            axis,
40            limits: JointLimits::new_unbounded(1),
41        }
42    }
43
44    /// Creates a new revolute joint model with `x` as axis of rotation.
45    ///
46    /// # Returns
47    /// A new `JointModelRevolute` object.
48    #[must_use]
49    pub fn new_rx() -> Self {
50        Self::new(Vector3D::x())
51    }
52
53    /// Creates a new revolute joint model with `y` as axis of rotation.
54    ///
55    /// # Returns
56    /// A new `JointModelRevolute` object.
57    #[must_use]
58    pub fn new_ry() -> Self {
59        Self::new(Vector3D::y())
60    }
61
62    /// Creates a new revolute joint model with `z` as axis of rotation.
63    ///
64    /// # Returns
65    /// A new `JointModelRevolute` object.
66    #[must_use]
67    pub fn new_rz() -> Self {
68        Self::new(Vector3D::z())
69    }
70}
71
72impl JointModel for JointModelRevolute {
73    fn get_joint_type(&self) -> JointType {
74        JointType::Revolute
75    }
76
77    fn nq(&self) -> usize {
78        1
79    }
80
81    fn nv(&self) -> usize {
82        1
83    }
84
85    fn neutral(&self) -> Configuration {
86        Configuration::zeros(1)
87    }
88
89    fn create_joint_data(&self) -> JointDataWrapper {
90        JointDataWrapper::revolute(JointDataRevolute::new(self))
91    }
92
93    fn get_axis(&self) -> Vec<SpatialMotion> {
94        vec![SpatialMotion::from_rotational_axis(&self.axis)]
95    }
96
97    fn random_configuration(&self, rng: &mut ThreadRng) -> Configuration {
98        Configuration::random(
99            1,
100            rng,
101            &self.limits.min_configuration,
102            &self.limits.max_configuration,
103        )
104    }
105
106    fn subspace(&self, v: &Configuration) -> SpatialMotion {
107        assert_eq!(
108            v.len(),
109            1,
110            "Revolute joint model expects a single velocity value."
111        );
112        v[0] * SpatialMotion::from_rotational_axis(&self.axis)
113    }
114
115    fn subspace_dual(&self, f: &dynamics_spatial::force::SpatialForce) -> Configuration {
116        Configuration::from_row_slice(&[f.rotation().dot(&self.axis)])
117    }
118
119    fn bias(&self) -> SpatialMotion {
120        SpatialMotion::zero()
121    }
122}
123
124/// Data structure containing the mutable properties of a revolute joint.
125#[derive(Debug, Clone)]
126pub struct JointDataRevolute {
127    /// The joint configuration vector (angle of rotation).
128    pub joint_q: Configuration,
129    /// The joint velocity vector (angle velocity).
130    pub joint_v: Configuration,
131    /// The placement of the joint in the local frame.
132    pub placement: SE3,
133    /// The joint velocity as a spatial motion.
134    pub joint_velocity: SpatialMotion,
135}
136
137impl JointDataRevolute {
138    /// Creates a new `JointDataRevolute` from given joint model, with the initial angle set to 0.0.
139    ///
140    /// # Arguments
141    ///
142    /// * `joint_model` - The revolute joint model.
143    ///
144    /// # Returns
145    /// A new `JointDataRevolute` object.
146    #[must_use]
147    pub fn new(_joint_model: &JointModelRevolute) -> Self {
148        JointDataRevolute {
149            joint_q: Configuration::zeros(1),
150            joint_v: Configuration::zeros(1),
151            placement: SE3::identity(),
152            joint_velocity: SpatialMotion::zero(),
153        }
154    }
155}
156
157impl JointData for JointDataRevolute {
158    fn get_joint_q(&self) -> &Configuration {
159        &self.joint_q
160    }
161
162    fn get_joint_v(&self) -> &Configuration {
163        &self.joint_v
164    }
165
166    fn get_joint_placement(&self) -> SE3 {
167        self.placement
168    }
169
170    fn update(
171        &mut self,
172        joint_model: &JointWrapper,
173        joint_q: &Configuration,
174        joint_v: Option<&Configuration>,
175    ) -> Result<(), JointError> {
176        assert_eq!(
177            joint_q.len(),
178            1,
179            "Revolute joint model expects a single angle."
180        );
181        if let Some(joint_v) = joint_v {
182            assert_eq!(
183                joint_v.len(),
184                1,
185                "Revolute joint model expects a single velocity value."
186            );
187        }
188
189        // store q and v
190        self.joint_q = joint_q.clone();
191        if let Some(joint_v) = joint_v {
192            self.joint_v = joint_v.clone();
193            self.joint_velocity = self.joint_v[0] * joint_model.get_axis()[0].clone();
194        }
195
196        let axis = match joint_model.get_axis().len() {
197            1 => &joint_model.get_axis()[0],
198            _ => return Err(JointError::MissingAttributeError("axis".to_string())),
199        };
200
201        let rot = SpatialRotation::from_axis_angle(&axis.rotation(), self.joint_q[0]);
202        self.placement = rot.to_se3(&Vector3D::zeros());
203        Ok(())
204    }
205
206    fn get_joint_velocity(&self) -> &SpatialMotion {
207        &self.joint_velocity
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use approx::assert_relative_eq;
214
215    use super::*;
216
217    #[test]
218    fn test_joint_model_revolute() {
219        let joint = JointModelRevolute::new(Vector3D::new(1.0, 0.0, 0.0));
220        assert_eq!(joint.get_joint_type(), JointType::Revolute);
221        assert_eq!(joint.nq(), 1);
222        assert_eq!(joint.nv(), 1);
223        assert_eq!(joint.neutral(), Configuration::zeros(1));
224        let _ = joint.create_joint_data();
225        let _ = joint.get_axis();
226        let _ = joint.random_configuration(&mut rand::rng());
227    }
228
229    #[test]
230    fn test_joint_data_revolute_xaxis() {
231        let joint_model = JointModelRevolute::new(Vector3D::new(1.0, 0.0, 0.0));
232        let mut joint_data = joint_model.create_joint_data();
233        let q = Configuration::ones(1);
234        joint_data
235            .update(&JointWrapper::revolute(joint_model), &q, None)
236            .unwrap();
237
238        assert_relative_eq!(joint_data.get_joint_placement().rotation().angle(), q[0]);
239        assert_eq!(
240            joint_data.get_joint_placement().translation(),
241            Vector3D::zeros()
242        );
243    }
244}