autd3_core/geometry/
device.rs

1use super::{
2    Isometry3, Point3, Quaternion, Transducer, Translation3, UnitQuaternion, UnitVector3, Vector3,
3};
4
5/// An AUTD device unit.
6pub struct Device {
7    pub(crate) idx: u16,
8    pub(crate) transducers: Vec<Transducer>,
9    rotation: UnitQuaternion,
10    center: Point3,
11    x_direction: UnitVector3,
12    y_direction: UnitVector3,
13    axial_direction: UnitVector3,
14    inv: Isometry3,
15}
16
17impl Device {
18    fn init(&mut self) {
19        self.center = Point3::from(
20            self.transducers
21                .iter()
22                .map(|tr| tr.position().coords)
23                .sum::<Vector3>()
24                / self.transducers.len() as f32,
25        );
26        self.x_direction = Self::get_direction(Vector3::x(), &self.rotation);
27        self.y_direction = Self::get_direction(Vector3::y(), &self.rotation);
28        self.axial_direction = if cfg!(feature = "left_handed") {
29            Self::get_direction(-Vector3::z(), &self.rotation) // GRCOV_EXCL_LINE
30        } else {
31            Self::get_direction(Vector3::z(), &self.rotation)
32        };
33        self.inv = (Translation3::from(self.transducers[0].position()) * self.rotation).inverse();
34    }
35
36    #[doc(hidden)]
37    #[must_use]
38    pub fn new(rot: UnitQuaternion, transducers: Vec<Transducer>) -> Self {
39        let mut transducers = transducers;
40        transducers.iter_mut().enumerate().for_each(|(tr_idx, tr)| {
41            tr.idx = tr_idx as _;
42        });
43        let mut dev = Self {
44            idx: 0,
45            transducers,
46            rotation: rot,
47            center: Point3::origin(),
48            x_direction: Vector3::x_axis(),
49            y_direction: Vector3::y_axis(),
50            axial_direction: Vector3::z_axis(),
51            inv: Isometry3::identity(),
52        };
53        dev.init();
54        dev
55    }
56
57    /// Gets the index of the device.
58    #[must_use]
59    pub const fn idx(&self) -> usize {
60        self.idx as _
61    }
62
63    /// Gets the number of transducers of the device.
64    #[must_use]
65    pub const fn num_transducers(&self) -> usize {
66        self.transducers.len()
67    }
68
69    /// Gets the rotation of the device.
70    #[must_use]
71    pub const fn rotation(&self) -> UnitQuaternion {
72        self.rotation
73    }
74
75    /// Gets the center of the device.
76    #[must_use]
77    pub const fn center(&self) -> Point3 {
78        self.center
79    }
80
81    /// Gets the x-direction of the device.
82    #[must_use]
83    pub const fn x_direction(&self) -> UnitVector3 {
84        self.x_direction
85    }
86
87    /// Gets the y-direction of the device.
88    #[must_use]
89    pub const fn y_direction(&self) -> UnitVector3 {
90        self.y_direction
91    }
92
93    /// Gets the axial direction of the device.
94    #[must_use]
95    pub const fn axial_direction(&self) -> UnitVector3 {
96        self.axial_direction
97    }
98
99    #[doc(hidden)]
100    #[must_use]
101    pub const fn inv(&self) -> &Isometry3 {
102        &self.inv
103    }
104
105    #[must_use]
106    fn get_direction(dir: Vector3, rotation: &UnitQuaternion) -> UnitVector3 {
107        let dir: UnitQuaternion = UnitQuaternion::from_quaternion(Quaternion::from_imag(dir));
108        UnitVector3::new_normalize((rotation * dir * rotation.conjugate()).imag())
109    }
110}
111
112impl core::ops::Deref for Device {
113    type Target = [Transducer];
114
115    fn deref(&self) -> &Self::Target {
116        &self.transducers
117    }
118}
119
120impl core::iter::IntoIterator for Device {
121    type Item = Transducer;
122    type IntoIter = std::vec::IntoIter<Transducer>;
123
124    fn into_iter(self) -> Self::IntoIter {
125        self.transducers.into_iter()
126    }
127}
128
129impl<'a> IntoIterator for &'a Device {
130    type Item = &'a Transducer;
131    type IntoIter = core::slice::Iter<'a, Transducer>;
132
133    fn into_iter(self) -> Self::IntoIter {
134        self.transducers.iter()
135    }
136}
137
138#[cfg(test)]
139pub(crate) mod tests {
140    use super::*;
141    use crate::{
142        common::PI,
143        geometry::tests::{TestDevice, create_device},
144    };
145
146    macro_rules! assert_approx_eq_vec3 {
147        ($a:expr, $b:expr) => {
148            approx::assert_abs_diff_eq!($a.x, $b.x, epsilon = 1e-3);
149            approx::assert_abs_diff_eq!($a.y, $b.y, epsilon = 1e-3);
150            approx::assert_abs_diff_eq!($a.z, $b.z, epsilon = 1e-3);
151        };
152    }
153
154    #[test]
155    fn into_iter() {
156        let device = create_device(249);
157        let mut count = 0;
158        for tr in &device {
159            assert_eq!(count, tr.idx());
160            count += 1;
161        }
162        assert_eq!(249, count);
163
164        let mut count = 0;
165        device.into_iter().for_each(|tr| {
166            assert_eq!(count, tr.idx());
167            count += 1;
168        });
169        assert_eq!(249, count);
170    }
171
172    #[test]
173    fn idx() {
174        assert_eq!(0, create_device(249).idx());
175    }
176
177    #[rstest::rstest]
178    #[case(1)]
179    #[case(249)]
180    fn num_transducers(#[case] n: u8) {
181        assert_eq!(n, create_device(n).num_transducers() as u8);
182    }
183
184    #[test]
185    fn center() {
186        let device: Device = TestDevice::new_autd3(Point3::origin()).into();
187        let expected =
188            device.iter().map(|t| t.position().coords).sum::<Vector3>() / device.len() as f32;
189        assert_approx_eq_vec3!(expected, device.center().coords);
190    }
191
192    #[test]
193    fn direction() {
194        let device: Device = TestDevice::new_autd3(Point3::origin()).into();
195        assert_approx_eq_vec3!(Vector3::x(), device.x_direction().into_inner());
196        assert_approx_eq_vec3!(Vector3::y(), device.y_direction().into_inner());
197        if cfg!(feature = "left_handed") {
198            assert_approx_eq_vec3!(-Vector3::z(), device.axial_direction().into_inner()); // GRCOV_EXCL_LINE
199        } else {
200            assert_approx_eq_vec3!(Vector3::z(), device.axial_direction().into_inner());
201        }
202    }
203
204    #[rstest::rstest]
205    #[case(
206        Vector3::new(10., 20., 30.),
207        Vector3::new(10., 20., 30.),
208        Point3::origin(),
209        UnitQuaternion::identity()
210    )]
211    #[case(
212        Vector3::zeros(),
213        Vector3::new(10., 20., 30.),
214        Point3::new(10., 20., 30.),
215        UnitQuaternion::identity()
216    )]
217    #[case(
218        Vector3::new(20., -10., 30.),
219        Vector3::new(10., 20., 30.),
220        Point3::origin(),
221        UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.)
222    )]
223    #[case(
224        Vector3::new(30., 30., -30.),
225        Vector3::new(40., 50., 60.),
226        Point3::new(10., 20., 30.),
227        UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.)
228    )]
229    fn inv(
230        #[case] expected: Vector3,
231        #[case] target: Vector3,
232        #[case] origin: Point3,
233        #[case] rot: UnitQuaternion,
234    ) {
235        let device: Device = TestDevice::new_autd3_with_rot(origin, rot).into();
236        assert_approx_eq_vec3!(expected, device.inv * Point3::from(target));
237    }
238}