Skip to main content

dynamics_joint/
revolute.rs

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