autd3-rs-core 0.1.0

Core types and link abstraction shared across the AUTD3 phased-array sdk crates.
Documentation
use nalgebra::{Point3, Quaternion, UnitQuaternion, UnitVector3, Vector3};

use super::autd3::is_missing_transducer;
use super::{Autd3, Device};

pub struct Autd3Unity {
    pub origin: Point3<f32>,
    pub rotation: Quaternion<f32>,
}

impl From<Autd3Unity> for Device {
    fn from(u: Autd3Unity) -> Device {
        let rot = UnitQuaternion::new_normalize(u.rotation);
        let origin_rh = Point3::new(
            u.origin.x * 1000.0,
            u.origin.y * 1000.0,
            -u.origin.z * 1000.0,
        );

        let flip_z = |v: Vector3<f32>| Vector3::new(v.x, v.y, -v.z);
        let x_rh = UnitVector3::new_normalize(flip_z((rot * Vector3::x_axis()).into_inner()));
        let y_rh = UnitVector3::new_normalize(flip_z((rot * Vector3::y_axis()).into_inner()));
        let dir_rh = UnitVector3::new_normalize(flip_z((rot * Vector3::z_axis()).into_inner()));
        let rotation = UnitQuaternion::from_basis_unchecked(&[
            x_rh.into_inner(),
            y_rh.into_inner(),
            dir_rh.into_inner(),
        ]);

        let mut positions = Vec::with_capacity(Autd3::NUM_TRANSDUCERS);
        let mut directions = Vec::with_capacity(Autd3::NUM_TRANSDUCERS);
        for y in 0..Autd3::GRID_Y {
            for x in 0..Autd3::GRID_X {
                if !is_missing_transducer(x, y) {
                    let local =
                        Vector3::new(x as f32 * Autd3::PITCH_MM, y as f32 * Autd3::PITCH_MM, 0.0);
                    let unity_offset = rot * local;
                    positions.push(
                        origin_rh + Vector3::new(unity_offset.x, unity_offset.y, -unity_offset.z),
                    );
                    directions.push(dir_rh);
                }
            }
        }
        debug_assert_eq!(positions.len(), Autd3::NUM_TRANSDUCERS);
        Device::new(rotation, positions, directions)
    }
}

#[cfg(test)]
mod tests {
    use approx::assert_abs_diff_eq;

    use super::*;

    #[test]
    fn unity_origin_converts_to_mm_and_flips_z() {
        let dev: Device = Autd3Unity {
            origin: Point3::new(0.1, 0.2, 0.3),
            rotation: Quaternion::identity(),
        }
        .into();
        let p = dev.position(0);
        assert_abs_diff_eq!(p.x, 100.0, epsilon = 1e-3);
        assert_abs_diff_eq!(p.y, 200.0, epsilon = 1e-3);
        assert_abs_diff_eq!(p.z, -300.0, epsilon = 1e-3);
    }

    #[test]
    fn unity_transducer_pitch_is_in_mm() {
        let dev: Device = Autd3Unity {
            origin: Point3::origin(),
            rotation: Quaternion::identity(),
        }
        .into();
        let p1 = dev.position(1);
        assert_abs_diff_eq!(p1.x, Autd3::PITCH_MM, epsilon = 1e-3);
        assert_abs_diff_eq!(p1.y, 0.0, epsilon = 1e-3);
        assert_abs_diff_eq!(p1.z, 0.0, epsilon = 1e-3);
    }

    #[test]
    fn unity_identity_rotation_direction_is_neg_z() {
        let dev: Device = Autd3Unity {
            origin: Point3::origin(),
            rotation: Quaternion::identity(),
        }
        .into();
        let dir = dev.direction(0);
        assert_abs_diff_eq!(dir.x, 0.0, epsilon = 1e-4);
        assert_abs_diff_eq!(dir.y, 0.0, epsilon = 1e-4);
        assert_abs_diff_eq!(dir.z, -1.0, epsilon = 1e-4);
    }

    #[test]
    fn unity_90deg_around_y_rotates_positions_and_direction() {
        let q = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), std::f32::consts::FRAC_PI_2);
        let dev: Device = Autd3Unity {
            origin: Point3::origin(),
            rotation: *q.quaternion(),
        }
        .into();

        let p1 = dev.position(1);
        assert_abs_diff_eq!(p1.x, 0.0, epsilon = 1e-3);
        assert_abs_diff_eq!(p1.y, 0.0, epsilon = 1e-3);
        assert_abs_diff_eq!(p1.z, Autd3::PITCH_MM, epsilon = 1e-3);

        let dir = dev.direction(0);
        assert_abs_diff_eq!(dir.x, 1.0, epsilon = 1e-4);
        assert_abs_diff_eq!(dir.y, 0.0, epsilon = 1e-4);
        assert_abs_diff_eq!(dir.z, 0.0, epsilon = 1e-4);
    }
}