affn 0.7.0

Affine geometry primitives: strongly-typed coordinate systems, reference frames, and centers for scientific computing.
Documentation
//! # Affine Operators Module
//!
//! This module provides pure mathematical operators for geometric transformations.
//! All types are domain-agnostic and contain no astronomy-specific vocabulary.
//!
//! ## Design Philosophy
//!
//! Operators in this module are:
//! - **Pure**: They perform only mathematical operations, no time-dependence or model selection.
//! - **Allocation-free**: All operations use stack-allocated storage.
//! - **Inline-friendly**: Heavily annotated with `#[inline]` for optimal performance.
//!
//! ## Operator Types
//!
//! - [`Rotation3`]: A 3x3 rotation matrix for pure orientation transforms.
//! - [`Translation3`]: A translation vector for pure center shifts.
//! - [`Isometry3`]: Combines rotation and translation for rigid body transforms.
//!
//! ## Unit-Aware Operations
//!
//! All operators integrate seamlessly with [`qtty`] typed quantities and `affn`'s
//! coordinate types. The following `Mul` implementations are provided for each
//! operator:
//!
//! | Operand | Result | Use case |
//! |---------|--------|----------|
//! | `[Quantity<U>; 3]` | `[Quantity<U>; 3]` | Raw typed array rotation |
//! | `XYZ<Quantity<U>>` | `XYZ<Quantity<U>>` | Internal storage rotation |
//! | `Position<C, F, U>` | `Position<C, F, U>` | Rotate/translate an affine point |
//! | `Vector<F, U>` | `Vector<F, U>` | Rotate a free vector (rotation only) |
//! | `Direction<F>` | `Direction<F>` | Rotate a unit direction (rotation only) |
//!
//! Frame and center tags are preserved; the caller is responsible for
//! reinterpreting them after a transform (see [`Position::reinterpret_frame`]).
//!
//! **Note**: translations apply to affine points (positions), not to free vectors
//! like directions.

mod isometry;
mod isometry2;
mod rotation;
mod rotation2;
mod translation;
mod translation2;

pub use isometry::Isometry3;
pub use isometry2::Isometry2;
pub use rotation::Rotation3;
pub use rotation2::Rotation2;
pub use translation::Translation3;
pub use translation2::Translation2;

use crate::cartesian::xyz::XYZ;
use crate::cartesian::{Direction, Position, Vector};
use crate::centers::ReferenceCenter;
use crate::frames::ReferenceFrame;
use qtty::length::LengthUnit;
use qtty::{Quantity, Unit};

// =============================================================================
// Quantity-array and XYZ impls (all three operator types)
// =============================================================================

/// Generates `Mul<[Quantity<U>; 3]>` and `Mul<XYZ<Quantity<U>>>` implementations
/// for an affine operator type, eliminating boilerplate for unit-aware operations.
macro_rules! impl_quantity_mul {
    ($OpType:ty, $apply_fn:ident, $apply_xyz_fn:ident) => {
        impl<U: Unit> std::ops::Mul<[Quantity<U>; 3]> for $OpType {
            type Output = [Quantity<U>; 3];

            #[inline]
            fn mul(self, rhs: [Quantity<U>; 3]) -> [Quantity<U>; 3] {
                let [x, y, z] = self.$apply_fn([rhs[0].value(), rhs[1].value(), rhs[2].value()]);
                [Quantity::new(x), Quantity::new(y), Quantity::new(z)]
            }
        }

        forward_ref_binop! { impl[U: Unit] Mul, mul for $OpType, [Quantity<U>; 3] }

        impl<U: Unit> std::ops::Mul<XYZ<Quantity<U>>> for $OpType {
            type Output = XYZ<Quantity<U>>;

            #[inline]
            fn mul(self, rhs: XYZ<Quantity<U>>) -> XYZ<Quantity<U>> {
                XYZ::from_raw(self.$apply_xyz_fn(rhs.to_raw()))
            }
        }

        forward_ref_binop! { impl[U: Unit] Mul, mul for $OpType, XYZ<Quantity<U>> }
    };
}

impl_quantity_mul!(Rotation3, apply_array, apply_xyz);

// Translation3<U> and Isometry3<U> are unit-typed, so they can only multiply
// quantities in the same unit U they were constructed with.
impl<U: Unit> std::ops::Mul<[Quantity<U>; 3]> for Translation3<U> {
    type Output = [Quantity<U>; 3];

    #[inline]
    fn mul(self, rhs: [Quantity<U>; 3]) -> [Quantity<U>; 3] {
        let [x, y, z] = self.apply_array([rhs[0].value(), rhs[1].value(), rhs[2].value()]);
        [Quantity::new(x), Quantity::new(y), Quantity::new(z)]
    }
}

forward_ref_binop! { impl[U: Unit] Mul, mul for Translation3<U>, [Quantity<U>; 3] }

impl<U: Unit> std::ops::Mul<XYZ<Quantity<U>>> for Translation3<U> {
    type Output = XYZ<Quantity<U>>;

    #[inline]
    fn mul(self, rhs: XYZ<Quantity<U>>) -> XYZ<Quantity<U>> {
        XYZ::from_raw(self.apply_xyz(rhs.to_raw()))
    }
}

forward_ref_binop! { impl[U: Unit] Mul, mul for Translation3<U>, XYZ<Quantity<U>> }

impl<U: Unit> std::ops::Mul<[Quantity<U>; 3]> for Isometry3<U> {
    type Output = [Quantity<U>; 3];

    #[inline]
    fn mul(self, rhs: [Quantity<U>; 3]) -> [Quantity<U>; 3] {
        let [x, y, z] = self.apply_point([rhs[0].value(), rhs[1].value(), rhs[2].value()]);
        [Quantity::new(x), Quantity::new(y), Quantity::new(z)]
    }
}

forward_ref_binop! { impl[U: Unit] Mul, mul for Isometry3<U>, [Quantity<U>; 3] }

impl<U: Unit> std::ops::Mul<XYZ<Quantity<U>>> for Isometry3<U> {
    type Output = XYZ<Quantity<U>>;

    #[inline]
    fn mul(self, rhs: XYZ<Quantity<U>>) -> XYZ<Quantity<U>> {
        XYZ::from_raw(self.apply_xyz(rhs.to_raw()))
    }
}

forward_ref_binop! { impl[U: Unit] Mul, mul for Isometry3<U>, XYZ<Quantity<U>> }

// =============================================================================
// Coordinate-type impls: Position, Vector, Direction
// =============================================================================

/// Generates `Mul<Position<C,F,U>>` for an operator that transforms affine points.
///
/// The center, frame, and unit tags are preserved. The caller is responsible for
/// reinterpreting the frame tag after the transform.
macro_rules! impl_position_mul {
    ($OpType:ty, $apply_fn:ident) => {
        impl<C, F, U> std::ops::Mul<Position<C, F, U>> for $OpType
        where
            C: ReferenceCenter,
            F: ReferenceFrame,
            U: LengthUnit,
            C::Params: Clone,
        {
            type Output = Position<C, F, U>;

            #[inline]
            fn mul(self, rhs: Position<C, F, U>) -> Position<C, F, U> {
                let [x, y, z] = self.$apply_fn([rhs.x().value(), rhs.y().value(), rhs.z().value()]);
                Position::new_with_params(
                    rhs.center_params().clone(),
                    Quantity::<U>::new(x),
                    Quantity::<U>::new(y),
                    Quantity::<U>::new(z),
                )
            }
        }

        forward_ref_binop! {
            impl[C, F, U] Mul, mul for $OpType, Position<C, F, U>
            where (
                C: ReferenceCenter,
                F: ReferenceFrame,
                U: LengthUnit,
                C::Params: Copy,
            )
        }
    };
}

impl_position_mul!(Rotation3, apply_array);

// Translation3<U> and Isometry3<U> enforce that the translation unit matches
// the position unit at compile time.
impl<C, F, U> std::ops::Mul<Position<C, F, U>> for Translation3<U>
where
    C: ReferenceCenter,
    F: ReferenceFrame,
    U: LengthUnit,
    C::Params: Clone,
{
    type Output = Position<C, F, U>;

    #[inline]
    fn mul(self, rhs: Position<C, F, U>) -> Position<C, F, U> {
        let [x, y, z] = self.apply_array([rhs.x().value(), rhs.y().value(), rhs.z().value()]);
        Position::new_with_params(
            rhs.center_params().clone(),
            Quantity::<U>::new(x),
            Quantity::<U>::new(y),
            Quantity::<U>::new(z),
        )
    }
}

forward_ref_binop! {
    impl[C, F, U] Mul, mul for Translation3<U>, Position<C, F, U>
    where (
        C: ReferenceCenter,
        F: ReferenceFrame,
        U: LengthUnit,
        C::Params: Copy,
    )
}

impl<C, F, U> std::ops::Mul<Position<C, F, U>> for Isometry3<U>
where
    C: ReferenceCenter,
    F: ReferenceFrame,
    U: LengthUnit,
    C::Params: Clone,
{
    type Output = Position<C, F, U>;

    #[inline]
    fn mul(self, rhs: Position<C, F, U>) -> Position<C, F, U> {
        let [x, y, z] = self.apply_point([rhs.x().value(), rhs.y().value(), rhs.z().value()]);
        Position::new_with_params(
            rhs.center_params().clone(),
            Quantity::<U>::new(x),
            Quantity::<U>::new(y),
            Quantity::<U>::new(z),
        )
    }
}

forward_ref_binop! {
    impl[C, F, U] Mul, mul for Isometry3<U>, Position<C, F, U>
    where (
        C: ReferenceCenter,
        F: ReferenceFrame,
        U: LengthUnit,
        C::Params: Copy,
    )
}

/// `Rotation3 * Vector<F, U>` — rotates a free vector, preserving frame and unit.
///
/// Translations do **not** apply to free vectors (they are translation-invariant),
/// so this is only implemented for `Rotation3`.
impl<F, U> std::ops::Mul<Vector<F, U>> for Rotation3
where
    F: ReferenceFrame,
    U: Unit,
{
    type Output = Vector<F, U>;

    #[inline]
    fn mul(self, rhs: Vector<F, U>) -> Vector<F, U> {
        let [x, y, z] = self.apply_array([rhs.x().value(), rhs.y().value(), rhs.z().value()]);
        Vector::new(
            Quantity::<U>::new(x),
            Quantity::<U>::new(y),
            Quantity::<U>::new(z),
        )
    }
}

forward_ref_binop! {
    impl[F, U] Mul, mul for Rotation3, Vector<F, U>
    where (
        F: ReferenceFrame,
        U: Unit,
    )
}

/// `Rotation3 * Direction<F>` — rotates a unit direction, preserving frame.
///
/// Translations do **not** apply to directions; only rotation changes their
/// orientation. The result is guaranteed to remain a unit vector because
/// rotation preserves norms.
impl<F: ReferenceFrame> std::ops::Mul<Direction<F>> for Rotation3 {
    type Output = Direction<F>;

    #[inline]
    fn mul(self, rhs: Direction<F>) -> Direction<F> {
        let [x, y, z] = self.apply_array([rhs.x(), rhs.y(), rhs.z()]);
        // Rotation preserves norm → result is still unit-length.
        Direction::new_unchecked(x, y, z)
    }
}

forward_ref_binop! { impl[F: ReferenceFrame] Mul, mul for Rotation3, Direction<F> }