1use bvh::aabb::Aabb;
2use derive_more::{Deref, IntoIterator};
3use getset::Getters;
4
5use super::{Isometry, Point3, Quaternion, Transducer, UnitQuaternion, UnitVector3, Vector3};
6
7#[derive(Getters, Deref, IntoIterator)]
9pub struct Device {
10 pub(crate) idx: u16,
11 #[deref]
12 #[into_iterator(ref)]
13 pub(crate) transducers: Vec<Transducer>,
14 #[getset(get = "pub")]
15 rotation: UnitQuaternion,
17 #[getset(get = "pub")]
18 center: Point3,
20 #[getset(get = "pub")]
21 x_direction: UnitVector3,
23 #[getset(get = "pub")]
24 y_direction: UnitVector3,
26 #[getset(get = "pub")]
27 axial_direction: UnitVector3,
29 #[doc(hidden)]
30 #[getset(get = "pub")]
31 inv: Isometry,
32 #[getset(get = "pub")]
33 aabb: Aabb<f32, 3>,
35}
36
37impl Device {
38 fn init(&mut self) {
39 self.center = Point3::from(
40 self.transducers
41 .iter()
42 .map(|tr| tr.position().coords)
43 .sum::<Vector3>()
44 / self.transducers.len() as f32,
45 );
46 self.x_direction = Self::get_direction(Vector3::x(), &self.rotation);
47 self.y_direction = Self::get_direction(Vector3::y(), &self.rotation);
48 self.axial_direction = if cfg!(feature = "left_handed") {
49 Self::get_direction(-Vector3::z(), &self.rotation) } else {
51 Self::get_direction(Vector3::z(), &self.rotation)
52 };
53 self.inv = (nalgebra::Translation3::<f32>::from(*self.transducers[0].position())
54 * self.rotation)
55 .inverse();
56 self.aabb = self
57 .transducers
58 .iter()
59 .fold(Aabb::empty(), |aabb, tr| aabb.grow(tr.position()));
60 }
61
62 #[doc(hidden)]
63 #[must_use]
64 pub fn new(rot: UnitQuaternion, transducers: Vec<Transducer>) -> Self {
65 let mut transducers = transducers;
66 transducers.iter_mut().enumerate().for_each(|(tr_idx, tr)| {
67 tr.idx = tr_idx as _;
68 });
69 let mut dev = Self {
70 idx: 0,
71 transducers,
72 rotation: rot,
73 center: Point3::origin(),
74 x_direction: Vector3::x_axis(),
75 y_direction: Vector3::y_axis(),
76 axial_direction: Vector3::z_axis(),
77 inv: nalgebra::Isometry3::identity(),
78 aabb: Aabb::empty(),
79 };
80 dev.init();
81 dev
82 }
83
84 #[must_use]
86 pub const fn idx(&self) -> usize {
87 self.idx as _
88 }
89
90 #[must_use]
92 pub const fn num_transducers(&self) -> usize {
93 self.transducers.len()
94 }
95
96 #[must_use]
97 fn get_direction(dir: Vector3, rotation: &UnitQuaternion) -> UnitVector3 {
98 let dir: UnitQuaternion = UnitQuaternion::from_quaternion(Quaternion::from_imag(dir));
99 UnitVector3::new_normalize((rotation * dir * rotation.conjugate()).imag())
100 }
101}
102
103#[cfg(test)]
104pub(crate) mod tests {
105 use super::*;
106 use crate::{
107 common::PI,
108 geometry::tests::{TestDevice, create_device},
109 };
110
111 macro_rules! assert_approx_eq_vec3 {
112 ($a:expr, $b:expr) => {
113 approx::assert_abs_diff_eq!($a.x, $b.x, epsilon = 1e-3);
114 approx::assert_abs_diff_eq!($a.y, $b.y, epsilon = 1e-3);
115 approx::assert_abs_diff_eq!($a.z, $b.z, epsilon = 1e-3);
116 };
117 }
118
119 #[test]
120 fn idx() {
121 assert_eq!(0, create_device(249).idx());
122 }
123
124 #[rstest::rstest]
125 #[test]
126 #[case(1)]
127 #[case(249)]
128 fn num_transducers(#[case] n: u8) {
129 assert_eq!(n, create_device(n).num_transducers() as u8);
130 }
131
132 #[test]
133 fn center() {
134 let device: Device = TestDevice::new_autd3(Point3::origin()).into();
135 let expected =
136 device.iter().map(|t| t.position().coords).sum::<Vector3>() / device.len() as f32;
137 assert_approx_eq_vec3!(expected, device.center());
138 }
139
140 #[rstest::rstest]
141 #[test]
142 #[case(
143 Vector3::new(10., 20., 30.),
144 Vector3::new(10., 20., 30.),
145 Point3::origin(),
146 UnitQuaternion::identity()
147 )]
148 #[case(
149 Vector3::zeros(),
150 Vector3::new(10., 20., 30.),
151 Point3::new(10., 20., 30.),
152 UnitQuaternion::identity()
153 )]
154 #[case(
155 Vector3::new(20., -10., 30.),
156 Vector3::new(10., 20., 30.),
157 Point3::origin(),
158 UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.)
159 )]
160 #[case(
161 Vector3::new(30., 30., -30.),
162 Vector3::new(40., 50., 60.),
163 Point3::new(10., 20., 30.),
164 UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.)
165 )]
166 fn inv(
167 #[case] expected: Vector3,
168 #[case] target: Vector3,
169 #[case] origin: Point3,
170 #[case] rot: UnitQuaternion,
171 ) {
172 let device: Device = TestDevice::new_autd3_with_rot(origin, rot).into();
173 assert_approx_eq_vec3!(expected, device.inv.transform_point(&Point3::from(target)));
174 }
175}