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/// A position in space with an orientation.
13///
14/// In general, use `OrientedPoint` as a [`Body`](crate::Body) property type
15/// with [`Point`](crate::Point) as the property type for its sites. Rarely
16/// `OrientedPoint` be used as a [`Site`](crate::Site) property type.
17///
18/// # Example
19///
20/// ```
21/// use hoomd_microstate::property::OrientedPoint;
22/// use hoomd_vector::{Angle, Cartesian};
23///
24/// let point = OrientedPoint {
25/// position: Cartesian::from([1.0, -3.0]),
26/// orientation: Angle::from(1.2),
27/// };
28/// ```
29#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
30pub struct OrientedPoint<V, R> {
31 /// The location of the extended body in space.
32 pub position: V,
33 /// Rotate from the body's reference frame into another frame.
34 pub orientation: R,
35}
36
37impl<V, R> Transform<Point<V>> for OrientedPoint<V, R>
38where
39 V: Vector,
40 R: Rotate<V>,
41{
42 /// Move [`Point`] properties from the local body frame to the system frame.
43 ///
44 /// ```math
45 /// \vec{r} = \vec{r}_\mathrm{body} + R_\mathrm{body}(\vec{r}_\mathrm{site})
46 /// ```
47 ///
48 /// ```
49 /// use approxim::assert_relative_eq;
50 /// use hoomd_microstate::{
51 /// Transform,
52 /// property::{OrientedPoint, Point},
53 /// };
54 /// use hoomd_vector::{Angle, Cartesian};
55 /// use std::f64::consts::PI;
56 ///
57 /// let body_properties = OrientedPoint {
58 /// position: Cartesian::from([1.0, -2.0]),
59 /// orientation: Angle::from(PI / 2.0),
60 /// };
61 /// let site_properties = Point::new(Cartesian::from([-1.0, 0.0]));
62 ///
63 /// let system_site = body_properties.transform(&site_properties);
64 /// assert_relative_eq!(system_site.position, [1.0, -3.0].into());
65 /// ```
66 #[inline]
67 fn transform(&self, site_properties: &Point<V>) -> Point<V> {
68 Point {
69 position: self.position + self.orientation.rotate(&site_properties.position),
70 }
71 }
72}
73
74impl<V, R> Transform<OrientedPoint<V, R>> for OrientedPoint<V, R>
75where
76 V: Vector,
77 R: Rotate<V> + Rotation,
78{
79 /// Move [`OrientedPoint`] 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}