Skip to main content

hoomd_microstate/property/
oriented_point.rs

1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4//! Implement `OrientedPoint`
5
6use serde::{Deserialize, Serialize};
7
8use super::{Orientation, Point, Position};
9use crate::Transform;
10use hoomd_vector::{Rotate, Rotation, Vector};
11
12/// The position and orientation of an extended body.
13///
14/// Use [`OrientedPoint`] as a [`Body`](crate::Body) or [`Site`](crate::Site) property type.
15///
16/// # Example
17///
18/// ```
19/// use hoomd_microstate::property::OrientedPoint;
20/// use hoomd_vector::{Angle, Cartesian};
21///
22/// let point = OrientedPoint {
23///     position: Cartesian::from([1.0, -3.0]),
24///     orientation: Angle::from(1.2),
25/// };
26/// ```
27#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
28pub struct OrientedPoint<V, R> {
29    /// The location of the extended body in space.
30    pub position: V,
31    /// Rotate from the body's reference frame into another frame.
32    pub orientation: R,
33}
34
35/// Treat [`Point`] sites as constituents of oriented rigid bodies.
36impl<V, R> Transform<Point<V>> for OrientedPoint<V, R>
37where
38    V: Vector,
39    R: Rotate<V>,
40{
41    /// Move [`Point`] properties from the local body frame to the system frame.
42    ///
43    /// ```math
44    /// \vec{r} = \vec{r}_\mathrm{body} + R_\mathrm{body}(\vec{r}_\mathrm{site})
45    /// ```
46    ///
47    /// ```
48    /// use approxim::assert_relative_eq;
49    /// use hoomd_microstate::{
50    ///     Transform,
51    ///     property::{OrientedPoint, Point},
52    /// };
53    /// use hoomd_vector::{Angle, Cartesian};
54    /// use std::f64::consts::PI;
55    ///
56    /// let body_properties = OrientedPoint {
57    ///     position: Cartesian::from([1.0, -2.0]),
58    ///     orientation: Angle::from(PI / 2.0),
59    /// };
60    /// let site_properties = Point::new(Cartesian::from([-1.0, 0.0]));
61    ///
62    /// let system_site = body_properties.transform(&site_properties);
63    /// assert_relative_eq!(system_site.position, [1.0, -3.0].into());
64    /// ```
65    #[inline]
66    fn transform(&self, site_properties: &Point<V>) -> Point<V> {
67        Point {
68            position: self.position + self.orientation.rotate(&site_properties.position),
69        }
70    }
71}
72
73/// Treat [`OrientedPoint`] sites as constituents of oriented rigid bodies.
74impl<V, R> Transform<OrientedPoint<V, R>> for OrientedPoint<V, R>
75where
76    V: Vector,
77    R: Rotate<V> + Rotation,
78{
79    /// Move [`Point`] properties from the local body frame to the system frame.
80    ///
81    /// ```math
82    /// \vec{r} = \vec{r}_\mathrm{body} + R_\mathrm{body}(\vec{r}_\mathrm{site})
83    /// ```
84    /// ```math
85    /// R = R_\mathrm{body}(R_\mathrm{site})
86    /// ```
87    ///
88    /// ```
89    /// use approxim::assert_relative_eq;
90    /// use hoomd_microstate::{Transform, property::OrientedPoint};
91    /// use hoomd_vector::{Angle, Cartesian};
92    /// use std::f64::consts::PI;
93    ///
94    /// let body_properties = OrientedPoint {
95    ///     position: Cartesian::from([1.0, -2.0]),
96    ///     orientation: Angle::from(PI / 2.0),
97    /// };
98    /// let site_properties = OrientedPoint {
99    ///     position: Cartesian::from([-1.0, 0.0]),
100    ///     orientation: Angle::from(PI / 4.0),
101    /// };
102    ///
103    /// let system_site = body_properties.transform(&site_properties);
104    /// assert_relative_eq!(system_site.position, [1.0, -3.0].into());
105    /// assert_relative_eq!(system_site.orientation.theta, 3.0 * PI / 4.0);
106    /// ```
107    #[inline]
108    fn transform(&self, site_properties: &OrientedPoint<V, R>) -> OrientedPoint<V, R> {
109        OrientedPoint {
110            position: self.position + self.orientation.rotate(&site_properties.position),
111            orientation: self.orientation.combine(&site_properties.orientation),
112        }
113    }
114}
115
116impl<P, R> Position for OrientedPoint<P, R> {
117    type Position = P;
118
119    #[inline]
120    fn position(&self) -> &P {
121        &self.position
122    }
123
124    #[inline]
125    fn position_mut(&mut self) -> &mut P {
126        &mut self.position
127    }
128}
129
130impl<V, R> Orientation for OrientedPoint<V, R> {
131    type Rotation = R;
132
133    #[inline]
134    fn orientation(&self) -> &R {
135        &self.orientation
136    }
137
138    #[inline]
139    fn orientation_mut(&mut self) -> &mut R {
140        &mut self.orientation
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use approxim::assert_relative_eq;
148    use std::f64::consts::PI;
149
150    use hoomd_vector::{Cartesian, Versor};
151
152    #[test]
153    fn transform_point() {
154        let body = OrientedPoint {
155            position: Cartesian::from([3.0, -4.0, 5.0]),
156            orientation: Versor::from_axis_angle(
157                [0.0, 1.0, 0.0]
158                    .try_into()
159                    .expect("hard-coded vector should be non-zero"),
160                -PI / 2.0,
161            ),
162        };
163
164        let site = Point::new(Cartesian::from([-1.0, 2.0, -3.0]));
165        let transformed_site = body.transform(&site);
166        assert_relative_eq!(transformed_site.position, [6.0, -2.0, 4.0].into());
167    }
168
169    #[test]
170    fn transform_oriented_point() {
171        let body = OrientedPoint {
172            position: Cartesian::from([3.0, -4.0, 5.0]),
173            orientation: Versor::from_axis_angle(
174                [0.0, 1.0, 0.0]
175                    .try_into()
176                    .expect("hard-coded vector should be non-zero"),
177                -PI / 2.0,
178            ),
179        };
180
181        let site = OrientedPoint {
182            position: Cartesian::from([-1.0, 2.0, -3.0]),
183            orientation: Versor::from_axis_angle(
184                [1.0, 0.0, 0.0]
185                    .try_into()
186                    .expect("hard-coded vector should be non-zero"),
187                PI / 2.0,
188            ),
189        };
190        let transformed_site = body.transform(&site);
191        assert_relative_eq!(transformed_site.position, [6.0, -2.0, 4.0].into());
192        assert_relative_eq!(
193            transformed_site.orientation.get(),
194            &[0.5, 0.5, -0.5, 0.5].into()
195        );
196    }
197}