mod autd3;
mod device;
mod groups;
#[cfg(feature = "serde")]
mod layout;
mod mask;
pub use autd3::Autd3;
pub use device::Device;
pub use groups::TransducerGroups;
#[cfg(feature = "serde")]
pub use layout::LayoutError;
pub use mask::{TransducerMask, TransducerMaskError};
pub use nalgebra::{Point3, Quaternion, UnitQuaternion, UnitVector3, Vector3};
use crate::common::Length;
use crate::value::Emission;
#[must_use]
pub fn point(x: Length, y: Length, z: Length) -> Point3<f32> {
Point3::new(x.mm(), y.mm(), z.mm())
}
#[must_use]
pub fn offset(x: Length, y: Length, z: Length) -> Vector3<f32> {
Vector3::new(x.mm(), y.mm(), z.mm())
}
#[derive(Clone, Debug)]
pub struct Geometry {
devices: Vec<Device>,
}
impl Geometry {
#[must_use]
pub fn new<D: Into<Device>>(devices: Vec<D>) -> Self {
Self {
devices: devices
.into_iter()
.enumerate()
.map(|(i, d)| {
let mut device = d.into();
device.set_idx(i);
device
})
.collect(),
}
}
#[must_use]
pub const fn num_devices(&self) -> usize {
self.devices.len()
}
#[must_use]
pub fn pattern_buffer(&self) -> Vec<Vec<Emission>> {
self.devices
.iter()
.map(|d| vec![Emission::default(); d.num_transducers()])
.collect()
}
#[must_use]
pub fn num_transducers(&self) -> usize {
self.devices.iter().map(Device::num_transducers).sum()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.devices.is_empty()
}
pub fn iter(&self) -> core::slice::Iter<'_, Device> {
self.devices.iter()
}
#[must_use]
pub fn center(&self) -> Point3<f32> {
let n = self.devices.len() as f32;
let sum = self
.devices
.iter()
.fold(Vector3::zeros(), |acc, d| acc + d.center().coords);
Point3::from(sum / n)
}
}
impl core::ops::Index<usize> for Geometry {
type Output = Device;
fn index(&self, index: usize) -> &Device {
&self.devices[index]
}
}
impl<'a> IntoIterator for &'a Geometry {
type Item = &'a Device;
type IntoIter = core::slice::Iter<'a, Device>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use nalgebra::UnitQuaternion;
use super::*;
use crate::value::{Intensity, Phase};
#[test]
fn geometry_sets_device_idx_and_num_transducers() {
let g = Geometry::new(vec![Autd3::default(), Autd3::default()]);
assert_eq!(g[0].idx(), 0);
assert_eq!(g[1].idx(), 1);
assert_eq!(g.num_transducers(), 2 * Autd3::NUM_TRANSDUCERS);
}
#[test]
fn pattern_buffer_starts_at_zero_phase_max_intensity() {
let g = Geometry::new(vec![Autd3::default(), Autd3::default()]);
let buf = g.pattern_buffer();
assert_eq!(buf.len(), 2);
for dev in &buf {
assert_eq!(dev.len(), Autd3::NUM_TRANSDUCERS);
for &e in dev {
assert_eq!(e.phase, Phase::ZERO);
assert_eq!(e.intensity, Intensity::MAX);
}
}
}
#[test]
fn device_basis_directions_for_identity() {
let g = Geometry::new(vec![Autd3::default()]);
let dev = &g[0];
assert_abs_diff_eq!(dev.x_direction().into_inner(), Vector3::x(), epsilon = 1e-4);
assert_abs_diff_eq!(dev.y_direction().into_inner(), Vector3::y(), epsilon = 1e-4);
assert_abs_diff_eq!(
dev.axial_direction().into_inner(),
Vector3::z(),
epsilon = 1e-4
);
assert_abs_diff_eq!(
dev.rotation().angle_to(&UnitQuaternion::identity()),
0.0,
epsilon = 1e-4
);
}
#[test]
fn device_rotation_tracks_quarter_turn_about_x() {
let rot = UnitQuaternion::from_axis_angle(&Vector3::x_axis(), core::f32::consts::FRAC_PI_2);
let g = Geometry::new(vec![Autd3::new(Point3::origin(), rot)]);
let dev = &g[0];
assert_abs_diff_eq!(
dev.axial_direction().into_inner(),
(rot * Vector3::z_axis()).into_inner(),
epsilon = 1e-4
);
assert_abs_diff_eq!(dev.rotation().angle_to(&rot), 0.0, epsilon = 1e-4);
}
}