1use std::f32::consts::PI;
2
3use bvh::aabb::Aabb;
4use derive_more::{Deref, IntoIterator};
5use getset::Getters;
6
7use crate::defined::{METER, ultrasound_freq};
8
9use super::{
10 Isometry, Point3, Quaternion, Transducer, Translation, UnitQuaternion, UnitVector3, Vector3,
11};
12
13#[derive(Getters, Deref, IntoIterator)]
15pub struct Device {
16 idx: u16,
17 #[deref]
18 #[into_iterator(ref)]
19 transducers: Vec<Transducer>,
20 pub enable: bool,
22 pub sound_speed: f32,
24 #[getset(get = "pub")]
25 rotation: UnitQuaternion,
27 #[getset(get = "pub")]
28 center: Point3,
30 #[getset(get = "pub")]
31 x_direction: UnitVector3,
33 #[getset(get = "pub")]
34 y_direction: UnitVector3,
36 #[getset(get = "pub")]
37 axial_direction: UnitVector3,
39 #[doc(hidden)]
40 #[getset(get = "pub")]
41 inv: Isometry,
42 #[getset(get = "pub")]
43 aabb: Aabb<f32, 3>,
45}
46
47impl Device {
48 fn init(&mut self) {
49 self.center = Point3::from(
50 self.transducers
51 .iter()
52 .map(|tr| tr.position().coords)
53 .sum::<Vector3>()
54 / self.transducers.len() as f32,
55 );
56 self.x_direction = Self::get_direction(Vector3::x(), &self.rotation);
57 self.y_direction = Self::get_direction(Vector3::y(), &self.rotation);
58 self.axial_direction = if cfg!(feature = "left_handed") {
59 Self::get_direction(-Vector3::z(), &self.rotation) } else {
61 Self::get_direction(Vector3::z(), &self.rotation)
62 };
63 self.inv = (nalgebra::Translation3::<f32>::from(*self.transducers[0].position())
64 * self.rotation)
65 .inverse();
66 self.aabb = self
67 .transducers
68 .iter()
69 .fold(Aabb::empty(), |aabb, tr| aabb.grow(tr.position()));
70 }
71
72 #[doc(hidden)]
73 #[must_use]
74 pub fn new(idx: u16, rot: UnitQuaternion, transducers: Vec<Transducer>) -> Self {
75 let mut dev = Self {
76 idx,
77 transducers,
78 enable: true,
79 sound_speed: 340.0 * METER,
80 rotation: rot,
81 center: Point3::origin(),
82 x_direction: Vector3::x_axis(),
83 y_direction: Vector3::y_axis(),
84 axial_direction: Vector3::z_axis(),
85 inv: nalgebra::Isometry3::identity(),
86 aabb: Aabb::empty(),
87 };
88 dev.init();
89 dev
90 }
91
92 #[must_use]
94 pub const fn idx(&self) -> usize {
95 self.idx as _
96 }
97
98 #[must_use]
100 pub fn num_transducers(&self) -> usize {
101 self.transducers.len()
102 }
103
104 pub fn translate_to(&mut self, t: Point3) {
106 self.translate(t - self.transducers[0].position());
107 }
108
109 pub fn rotate_to(&mut self, r: UnitQuaternion) {
111 self.rotate(r * self.rotation.conjugate());
112 }
113
114 pub fn translate(&mut self, t: Vector3) {
116 self.affine(t, UnitQuaternion::identity());
117 }
118
119 pub fn rotate(&mut self, r: UnitQuaternion) {
121 self.affine(Vector3::zeros(), r);
122 }
123
124 pub fn affine(&mut self, t: Vector3, r: UnitQuaternion) {
126 let isometry = Isometry {
127 translation: Translation::from(t),
128 rotation: r,
129 };
130 self.transducers
131 .iter_mut()
132 .for_each(|tr| tr.affine(&isometry));
133 self.rotation = r * self.rotation;
134 self.init();
135 }
136
137 pub fn set_sound_speed_from_temp(&mut self, temp: f32) {
141 self.set_sound_speed_from_temp_with(temp, 1.4, 8.314_463, 28.9647e-3);
142 }
143
144 pub fn set_sound_speed_from_temp_with(&mut self, temp: f32, k: f32, r: f32, m: f32) {
146 self.sound_speed = (k * r * (273.15 + temp) / m).sqrt() * METER;
147 }
148
149 #[must_use]
151 #[cfg_attr(not(feature = "dynamic_freq"), const_fn::const_fn)]
152 pub fn wavelength(&self) -> f32 {
153 self.sound_speed / ultrasound_freq().hz() as f32
154 }
155
156 #[must_use]
158 #[cfg_attr(not(feature = "dynamic_freq"), const_fn::const_fn)]
159 pub fn wavenumber(&self) -> f32 {
160 2.0 * PI * ultrasound_freq().hz() as f32 / self.sound_speed
161 }
162
163 #[must_use]
164 fn get_direction(dir: Vector3, rotation: &UnitQuaternion) -> UnitVector3 {
165 let dir: UnitQuaternion = UnitQuaternion::from_quaternion(Quaternion::from_imag(dir));
166 UnitVector3::new_normalize((rotation * dir * rotation.conjugate()).imag())
167 }
168}
169
170pub trait IntoDevice {
172 #[must_use]
174 fn into_device(self, dev_idx: u16) -> Device;
175}
176
177impl IntoDevice for Device {
178 fn into_device(mut self, dev_idx: u16) -> Device {
179 self.idx = dev_idx;
180 self
181 }
182}
183
184#[cfg(test)]
185pub(crate) mod tests {
186 use rand::Rng;
187
188 use super::*;
189 use crate::{
190 defined::{PI, mm},
191 geometry::tests::{TestDevice, create_device},
192 };
193
194 macro_rules! assert_approx_eq_vec3 {
195 ($a:expr, $b:expr) => {
196 approx::assert_abs_diff_eq!($a.x, $b.x, epsilon = 1e-3);
197 approx::assert_abs_diff_eq!($a.y, $b.y, epsilon = 1e-3);
198 approx::assert_abs_diff_eq!($a.z, $b.z, epsilon = 1e-3);
199 };
200 }
201
202 macro_rules! assert_approx_eq_quat {
203 ($a:expr, $b:expr) => {
204 approx::assert_abs_diff_eq!($a.w, $b.w, epsilon = 1e-3);
205 approx::assert_abs_diff_eq!($a.i, $b.i, epsilon = 1e-3);
206 approx::assert_abs_diff_eq!($a.j, $b.j, epsilon = 1e-3);
207 approx::assert_abs_diff_eq!($a.k, $b.k, epsilon = 1e-3);
208 };
209 }
210
211 #[rstest::rstest]
212 #[test]
213 #[case(0)]
214 #[case(1)]
215 fn idx(#[case] expect: u16) {
216 assert_eq!(expect, create_device(expect, 249).idx() as u16);
217 }
218
219 #[rstest::rstest]
220 #[test]
221 #[case(1)]
222 #[case(249)]
223 fn num_transducers(#[case] n: u8) {
224 assert_eq!(n, create_device(0, n).num_transducers() as u8);
225 }
226
227 #[test]
228 fn center() {
229 let device = TestDevice::new_autd3(Point3::origin()).into_device(0);
230 let expected =
231 device.iter().map(|t| t.position().coords).sum::<Vector3>() / device.len() as f32;
232 assert_approx_eq_vec3!(expected, device.center());
233 }
234
235 #[rstest::rstest]
236 #[test]
237 #[case(
238 Vector3::new(10., 20., 30.),
239 Vector3::new(10., 20., 30.),
240 Point3::origin(),
241 UnitQuaternion::identity()
242 )]
243 #[case(
244 Vector3::zeros(),
245 Vector3::new(10., 20., 30.),
246 Point3::new(10., 20., 30.),
247 UnitQuaternion::identity()
248 )]
249 #[case(
250 Vector3::new(20., -10., 30.),
251 Vector3::new(10., 20., 30.),
252 Point3::origin(),
253 UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.)
254 )]
255 #[case(
256 Vector3::new(30., 30., -30.),
257 Vector3::new(40., 50., 60.),
258 Point3::new(10., 20., 30.),
259 UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.)
260 )]
261 fn inv(
262 #[case] expected: Vector3,
263 #[case] target: Vector3,
264 #[case] origin: Point3,
265 #[case] rot: UnitQuaternion,
266 ) {
267 let device = TestDevice::new_autd3_with_rot(origin, rot).into_device(0);
268 assert_approx_eq_vec3!(expected, device.inv.transform_point(&Point3::from(target)));
269 }
270
271 #[test]
272 fn translate_to() {
273 let mut rng = rand::rng();
274 let origin = Point3::new(rng.random(), rng.random(), rng.random());
275
276 let mut device = TestDevice::new_autd3(origin).into_device(0);
277
278 let t = Point3::new(40., 50., 60.);
279 device.translate_to(t);
280
281 TestDevice::new_autd3(t)
282 .into_device(0)
283 .iter()
284 .zip(device.iter())
285 .for_each(|(expect, tr)| {
286 assert_approx_eq_vec3!(expect.position(), tr.position());
287 });
288 }
289
290 #[test]
291 fn rotate_to() {
292 let mut rng = rand::rng();
293 let mut device = TestDevice::new_autd3_with_rot(
294 Point3::origin(),
295 UnitQuaternion::from_axis_angle(&Vector3::x_axis(), rng.random())
296 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), rng.random())
297 * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), rng.random()),
298 )
299 .into_device(0);
300
301 let rot = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 0.)
302 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), 0.)
303 * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.);
304 device.rotate_to(rot);
305
306 let expect_x = Vector3::new(0., 1., 0.);
307 let expect_y = Vector3::new(-1., 0., 0.);
308 let expect_z = if cfg!(feature = "left_handed") {
309 Vector3::new(0., 0., -1.) } else {
311 Vector3::new(0., 0., 1.)
312 };
313 assert_approx_eq_quat!(rot, device.rotation());
314 assert_approx_eq_vec3!(expect_x, device.x_direction());
315 assert_approx_eq_vec3!(expect_y, device.y_direction());
316 assert_approx_eq_vec3!(expect_z, device.axial_direction());
317 TestDevice::new_autd3_with_rot(Point3::origin(), rot)
318 .into_device(0)
319 .iter()
320 .zip(device.iter())
321 .for_each(|(expect, tr)| {
322 assert_approx_eq_vec3!(expect.position(), tr.position());
323 });
324 }
325
326 #[test]
327 fn translate() {
328 let mut rng = rand::rng();
329 let origin = Point3::new(rng.random(), rng.random(), rng.random());
330
331 let mut device = TestDevice::new_autd3(origin).into_device(0);
332
333 let t = Vector3::new(40., 50., 60.);
334 device.translate(t);
335 TestDevice::new_autd3(origin + t)
336 .into_device(0)
337 .iter()
338 .zip(device.iter())
339 .for_each(|(expect, tr)| {
340 assert_approx_eq_vec3!(expect.position(), tr.position());
341 });
342 }
343
344 #[test]
345 fn rotate() {
346 let mut device = TestDevice::new_autd3(Point3::origin()).into_device(0);
347
348 let rot = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 0.)
349 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), 0.)
350 * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.);
351 device.rotate(rot);
352 let expect_x = Vector3::new(0., 1., 0.);
353 let expect_y = Vector3::new(-1., 0., 0.);
354 let expect_z = if cfg!(feature = "left_handed") {
355 Vector3::new(0., 0., -1.) } else {
357 Vector3::new(0., 0., 1.)
358 };
359 assert_approx_eq_quat!(rot, device.rotation());
360 assert_approx_eq_vec3!(expect_x, device.x_direction());
361 assert_approx_eq_vec3!(expect_y, device.y_direction());
362 assert_approx_eq_vec3!(expect_z, device.axial_direction());
363 TestDevice::new_autd3_with_rot(Point3::origin(), rot)
364 .into_device(0)
365 .iter()
366 .zip(device.iter())
367 .for_each(|(expect, tr)| {
368 assert_approx_eq_vec3!(expect.position(), tr.position());
369 });
370 }
371
372 #[test]
373 fn affine() {
374 let mut device = TestDevice::new_autd3(Point3::origin()).into_device(0);
375
376 let t = Vector3::new(40., 50., 60.);
377 let rot = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 0.)
378 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), 0.)
379 * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.);
380 device.affine(t, rot);
381
382 let expect_x = Vector3::new(0., 1., 0.);
383 let expect_y = Vector3::new(-1., 0., 0.);
384 let expect_z = if cfg!(feature = "left_handed") {
385 Vector3::new(0., 0., -1.) } else {
387 Vector3::new(0., 0., 1.)
388 };
389 assert_approx_eq_quat!(rot, device.rotation());
390 assert_approx_eq_vec3!(expect_x, device.x_direction());
391 assert_approx_eq_vec3!(expect_y, device.y_direction());
392 assert_approx_eq_vec3!(expect_z, device.axial_direction());
393
394 TestDevice::new_autd3_with_rot(Point3::from(t), rot)
395 .into_device(0)
396 .iter()
397 .zip(device.iter())
398 .for_each(|(expect, tr)| {
399 assert_approx_eq_vec3!(expect.position(), tr.position());
400 });
401 }
402
403 #[rstest::rstest]
404 #[test]
405 #[case(340.29525e3, 15.)]
406 #[case(343.23497e3, 20.)]
407 #[case(349.04013e3, 30.)]
408 fn set_sound_speed_from_temp(#[case] expected: f32, #[case] temp: f32) {
409 let mut device = create_device(0, 249);
410 device.set_sound_speed_from_temp(temp);
411 approx::assert_abs_diff_eq!(expected * mm, device.sound_speed, epsilon = 1e-3);
412 }
413
414 #[rstest::rstest]
415 #[test]
416 #[case(8.5, 340e3)]
417 #[case(10., 400e3)]
418 fn wavelength(#[case] expect: f32, #[case] c: f32) {
419 let mut device = create_device(0, 249);
420 device.sound_speed = c;
421 approx::assert_abs_diff_eq!(expect, device.wavelength());
422 }
423
424 #[rstest::rstest]
425 #[test]
426 #[case(0.739_198_27, 340e3)]
427 #[case(0.628_318_55, 400e3)]
428 fn wavenumber(#[case] expect: f32, #[case] c: f32) {
429 let mut device = create_device(0, 249);
430 device.sound_speed = c;
431 approx::assert_abs_diff_eq!(expect, device.wavenumber());
432 }
433
434 #[test]
435 fn into_device() {
436 let mut rng = rand::rng();
437 let t = Vector3::new(rng.random(), rng.random(), rng.random());
438 let rot = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), rng.random())
439 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), rng.random())
440 * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), rng.random());
441 let Device {
442 idx: _idx,
443 transducers: expect_transducers,
444 enable: expect_enable,
445 sound_speed: expect_sound_speed,
446 rotation: expect_rotation,
447 center: expect_center,
448 x_direction: expect_x_direction,
449 y_direction: expect_y_direction,
450 axial_direction: expect_axial_direction,
451 inv: expect_inv,
452 aabb: expect_aabb,
453 } = TestDevice::new_autd3_with_rot(Point3::from(t), rot).into_device(0);
454 let dev = TestDevice::new_autd3_with_rot(Point3::from(t), rot)
455 .into_device(0)
456 .into_device(1);
457 assert_eq!(1, dev.idx());
458 assert_eq!(expect_transducers, dev.transducers);
459 assert_eq!(expect_enable, dev.enable);
460 assert_eq!(expect_sound_speed, dev.sound_speed);
461 assert_eq!(expect_rotation, dev.rotation);
462 assert_eq!(expect_center, dev.center);
463 assert_eq!(expect_x_direction, dev.x_direction);
464 assert_eq!(expect_y_direction, dev.y_direction);
465 assert_eq!(expect_axial_direction, dev.axial_direction);
466 assert_eq!(expect_inv, dev.inv);
467 assert_eq!(expect_aabb.min, dev.aabb.min);
468 assert_eq!(expect_aabb.max, dev.aabb.max);
469 }
470}