1use std::f32::consts::PI;
2
3use bvh::aabb::Aabb;
4use derive_more::{Deref, IntoIterator};
5use getset::Getters;
6
7use crate::common::{METER, ULTRASOUND_FREQ};
8
9use super::{Isometry, Point3, Quaternion, Transducer, UnitQuaternion, UnitVector3, Vector3};
10
11#[derive(Getters, Deref, IntoIterator)]
13pub struct Device {
14 pub(crate) idx: u16,
15 #[deref]
16 #[into_iterator(ref)]
17 pub(crate) transducers: Vec<Transducer>,
18 pub sound_speed: f32,
20 #[getset(get = "pub")]
21 rotation: UnitQuaternion,
23 #[getset(get = "pub")]
24 center: Point3,
26 #[getset(get = "pub")]
27 x_direction: UnitVector3,
29 #[getset(get = "pub")]
30 y_direction: UnitVector3,
32 #[getset(get = "pub")]
33 axial_direction: UnitVector3,
35 #[doc(hidden)]
36 #[getset(get = "pub")]
37 inv: Isometry,
38 #[getset(get = "pub")]
39 aabb: Aabb<f32, 3>,
41}
42
43impl Device {
44 fn init(&mut self) {
45 self.center = Point3::from(
46 self.transducers
47 .iter()
48 .map(|tr| tr.position().coords)
49 .sum::<Vector3>()
50 / self.transducers.len() as f32,
51 );
52 self.x_direction = Self::get_direction(Vector3::x(), &self.rotation);
53 self.y_direction = Self::get_direction(Vector3::y(), &self.rotation);
54 self.axial_direction = if cfg!(feature = "left_handed") {
55 Self::get_direction(-Vector3::z(), &self.rotation) } else {
57 Self::get_direction(Vector3::z(), &self.rotation)
58 };
59 self.inv = (nalgebra::Translation3::<f32>::from(*self.transducers[0].position())
60 * self.rotation)
61 .inverse();
62 self.aabb = self
63 .transducers
64 .iter()
65 .fold(Aabb::empty(), |aabb, tr| aabb.grow(tr.position()));
66 }
67
68 #[doc(hidden)]
69 #[must_use]
70 pub fn new(rot: UnitQuaternion, transducers: Vec<Transducer>) -> Self {
71 let mut transducers = transducers;
72 transducers.iter_mut().enumerate().for_each(|(tr_idx, tr)| {
73 tr.idx = tr_idx as _;
74 });
75 let mut dev = Self {
76 idx: 0,
77 transducers,
78 sound_speed: 340.0 * METER,
79 rotation: rot,
80 center: Point3::origin(),
81 x_direction: Vector3::x_axis(),
82 y_direction: Vector3::y_axis(),
83 axial_direction: Vector3::z_axis(),
84 inv: nalgebra::Isometry3::identity(),
85 aabb: Aabb::empty(),
86 };
87 dev.init();
88 dev
89 }
90
91 #[must_use]
93 pub const fn idx(&self) -> usize {
94 self.idx as _
95 }
96
97 #[must_use]
99 pub const fn num_transducers(&self) -> usize {
100 self.transducers.len()
101 }
102
103 pub fn set_sound_speed_from_temp(&mut self, temp: f32) {
107 self.set_sound_speed_from_temp_with(temp, 1.4, 8.314_463, 28.9647e-3);
108 }
109
110 pub fn set_sound_speed_from_temp_with(&mut self, temp: f32, k: f32, r: f32, m: f32) {
112 self.sound_speed = (k * r * (273.15 + temp) / m).sqrt() * METER;
113 }
114
115 #[must_use]
117 pub const fn wavelength(&self) -> f32 {
118 self.sound_speed / ULTRASOUND_FREQ.hz() as f32
119 }
120
121 #[must_use]
123 pub const fn wavenumber(&self) -> f32 {
124 2.0 * PI * ULTRASOUND_FREQ.hz() as f32 / self.sound_speed
125 }
126
127 #[must_use]
128 fn get_direction(dir: Vector3, rotation: &UnitQuaternion) -> UnitVector3 {
129 let dir: UnitQuaternion = UnitQuaternion::from_quaternion(Quaternion::from_imag(dir));
130 UnitVector3::new_normalize((rotation * dir * rotation.conjugate()).imag())
131 }
132}
133
134#[cfg(test)]
135pub(crate) mod tests {
136 use super::*;
137 use crate::{
138 common::{PI, mm},
139 geometry::tests::{TestDevice, create_device},
140 };
141
142 macro_rules! assert_approx_eq_vec3 {
143 ($a:expr, $b:expr) => {
144 approx::assert_abs_diff_eq!($a.x, $b.x, epsilon = 1e-3);
145 approx::assert_abs_diff_eq!($a.y, $b.y, epsilon = 1e-3);
146 approx::assert_abs_diff_eq!($a.z, $b.z, epsilon = 1e-3);
147 };
148 }
149
150 #[test]
151 fn idx() {
152 assert_eq!(0, create_device(249).idx());
153 }
154
155 #[rstest::rstest]
156 #[test]
157 #[case(1)]
158 #[case(249)]
159 fn num_transducers(#[case] n: u8) {
160 assert_eq!(n, create_device(n).num_transducers() as u8);
161 }
162
163 #[test]
164 fn center() {
165 let device: Device = TestDevice::new_autd3(Point3::origin()).into();
166 let expected =
167 device.iter().map(|t| t.position().coords).sum::<Vector3>() / device.len() as f32;
168 assert_approx_eq_vec3!(expected, device.center());
169 }
170
171 #[rstest::rstest]
172 #[test]
173 #[case(
174 Vector3::new(10., 20., 30.),
175 Vector3::new(10., 20., 30.),
176 Point3::origin(),
177 UnitQuaternion::identity()
178 )]
179 #[case(
180 Vector3::zeros(),
181 Vector3::new(10., 20., 30.),
182 Point3::new(10., 20., 30.),
183 UnitQuaternion::identity()
184 )]
185 #[case(
186 Vector3::new(20., -10., 30.),
187 Vector3::new(10., 20., 30.),
188 Point3::origin(),
189 UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.)
190 )]
191 #[case(
192 Vector3::new(30., 30., -30.),
193 Vector3::new(40., 50., 60.),
194 Point3::new(10., 20., 30.),
195 UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.)
196 )]
197 fn inv(
198 #[case] expected: Vector3,
199 #[case] target: Vector3,
200 #[case] origin: Point3,
201 #[case] rot: UnitQuaternion,
202 ) {
203 let device: Device = TestDevice::new_autd3_with_rot(origin, rot).into();
204 assert_approx_eq_vec3!(expected, device.inv.transform_point(&Point3::from(target)));
205 }
206
207 #[rstest::rstest]
208 #[test]
209 #[case(340.29525e3, 15.)]
210 #[case(343.23497e3, 20.)]
211 #[case(349.04013e3, 30.)]
212 fn set_sound_speed_from_temp(#[case] expected: f32, #[case] temp: f32) {
213 let mut device = create_device(249);
214 device.set_sound_speed_from_temp(temp);
215 approx::assert_abs_diff_eq!(expected * mm, device.sound_speed, epsilon = 1e-3);
216 }
217
218 #[rstest::rstest]
219 #[test]
220 #[case(8.5, 340e3)]
221 #[case(10., 400e3)]
222 fn wavelength(#[case] expect: f32, #[case] c: f32) {
223 let mut device = create_device(249);
224 device.sound_speed = c;
225 approx::assert_abs_diff_eq!(expect, device.wavelength());
226 }
227
228 #[rstest::rstest]
229 #[test]
230 #[case(0.739_198_27, 340e3)]
231 #[case(0.628_318_55, 400e3)]
232 fn wavenumber(#[case] expect: f32, #[case] c: f32) {
233 let mut device = create_device(249);
234 device.sound_speed = c;
235 approx::assert_abs_diff_eq!(expect, device.wavenumber());
236 }
237}