clifford 0.3.0

Geometric Algebra (Clifford Algebra) for Rust: rotors, motors, PGA for 3D rotations and rigid transforms
Documentation
//! Domain-specific extensions for 2D Euclidean GA types.
//!
//! This module adds geometric operations and convenience methods
//! to the generated types that are specific to Euclidean 2D geometry.

use super::generated::types::{Bivector, Rotor, Vector};
use crate::scalar::Float;

// ============================================================================
// Vector extensions
// ============================================================================

impl<T: Float> Vector<T> {
    /// Dot product (inner product): `a · b = a.x*b.x + a.y*b.y`.
    ///
    /// Returns the scalar part of the geometric product.
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::specialized::euclidean::dim2::Vector;
    ///
    /// let a = Vector::new(1.0, 2.0);
    /// let b = Vector::new(3.0, 4.0);
    /// assert_eq!(a.dot(b), 11.0); // 1*3 + 2*4
    /// ```
    #[inline]
    pub fn dot(self, other: Self) -> T {
        self.x() * other.x() + self.y() * other.y()
    }

    /// Perpendicular vector (90° counterclockwise rotation).
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::specialized::euclidean::dim2::Vector;
    ///
    /// let v = Vector::new(1.0, 0.0);
    /// let perp = v.perp();
    /// assert_eq!(perp.x(), 0.0);
    /// assert_eq!(perp.y(), 1.0);
    /// ```
    #[inline]
    pub fn perp(&self) -> Self {
        Self::new(-self.y(), self.x())
    }
}

// ============================================================================
// Bivector extensions
// ============================================================================

impl<T: Float> Bivector<T> {
    /// Creates the unit bivector `e₁₂`.
    #[inline]
    pub fn unit() -> Self {
        Self::unit_b()
    }

    /// Returns the coefficient (alias for `b()`).
    #[inline]
    pub fn value(&self) -> T {
        self.b()
    }
}

// ============================================================================
// Rotor extensions
// ============================================================================

impl<T: Float> Rotor<T> {
    /// Creates a rotor from a rotation angle.
    ///
    /// The rotor `R = cos(θ/2) - sin(θ/2)e₁₂` rotates counterclockwise by angle `θ`.
    ///
    /// Note: The bivector component is negated because the sandwich product
    /// `R v R̃` rotates in the opposite direction of the bivector angle.
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::specialized::euclidean::dim2::{Rotor, Vector};
    /// use clifford::ops::Transform;
    /// use std::f64::consts::FRAC_PI_2;
    /// use approx::abs_diff_eq;
    ///
    /// // 90 deg counterclockwise rotation
    /// let r = Rotor::from_angle(FRAC_PI_2);
    /// let v = Vector::unit_x();
    /// let rotated = r.transform(&v);
    /// assert!(abs_diff_eq!(rotated.y(), 1.0, epsilon = 1e-10));
    /// ```
    #[inline]
    pub fn from_angle(angle: T) -> Self {
        let half = angle / T::TWO;
        // Negate bivector: sandwich R v R̃ rotates opposite to the bivector angle
        Self::new_unchecked(half.cos(), -half.sin())
    }

    /// Creates a rotor that rotates vector `a` to vector `b`.
    ///
    /// Both vectors should be non-zero. The rotation is the shortest
    /// arc from `a` to `b`.
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::ops::Transform;
    /// use clifford::specialized::euclidean::dim2::{Rotor, Vector};
    /// use approx::abs_diff_eq;
    ///
    /// let a = Vector::<f64>::unit_x();
    /// let b = Vector::<f64>::unit_y();
    /// let r = Rotor::from_vectors(a, b);
    /// let rotated = r.transform(&a);
    /// assert!(abs_diff_eq!(rotated.x(), b.x(), epsilon = 1e-10));
    /// assert!(abs_diff_eq!(rotated.y(), b.y(), epsilon = 1e-10));
    /// ```
    #[inline]
    pub fn from_vectors(a: Vector<T>, b: Vector<T>) -> Self {
        // Compute signed angle from a to b using atan2 for stability
        let angle_a = a.y().atan2(a.x());
        let angle_b = b.y().atan2(b.x());
        let angle = angle_b - angle_a;
        Self::from_angle(angle)
    }

    /// Composes two rotations: `R₂ ∘ R₁ = R₂ R₁`.
    ///
    /// The result applies `self` first, then `other`.
    #[inline]
    pub fn compose(&self, other: Self) -> Self {
        other * *self
    }

    /// Linear interpolation (normalized).
    ///
    /// Interpolates linearly between two rotors and normalizes the result.
    ///
    /// # Arguments
    ///
    /// * `other` - Target rotor
    /// * `t` - Interpolation parameter in [0, 1]
    ///
    /// # Panics
    ///
    /// Panics if `self` and `other` are opposite rotors (s₁ ≈ -s₂, xy₁ ≈ -xy₂),
    /// as the linear interpolation passes through zero at t ≈ 0.5.
    /// Use [`slerp`](Self::slerp) for robust interpolation in all cases.
    #[inline]
    pub fn lerp(&self, other: Self, t: T) -> Self {
        Self::new_unchecked(
            self.s() * (T::one() - t) + other.s() * t,
            self.b() * (T::one() - t) + other.b() * t,
        )
        .normalize()
    }

    /// Spherical linear interpolation between two rotors.
    ///
    /// Interpolates along the geodesic (great arc) between two rotors.
    /// Produces constant angular velocity.
    ///
    /// # Arguments
    ///
    /// * `other` - Target rotor
    /// * `t` - Interpolation parameter in [0, 1]
    #[inline]
    pub fn slerp(&self, other: Self, t: T) -> Self {
        let dot = self.s() * other.s() + self.b() * other.b();

        let dot = if dot > T::one() {
            T::one()
        } else if dot < -T::one() {
            -T::one()
        } else {
            dot
        };

        let theta = dot.acos();
        let sin_theta = theta.sin();

        // Fall back to lerp when sin_theta is near zero (rotors nearly identical or opposite)
        if sin_theta.abs() < T::epsilon() {
            return self.lerp(other, t);
        }
        let s1 = ((T::one() - t) * theta).sin() / sin_theta;
        let s2 = (t * theta).sin() / sin_theta;

        // Spherical interpolation preserves unit norm
        Self::new_unchecked(
            self.s() * s1 + other.s() * s2,
            self.b() * s1 + other.b() * s2,
        )
    }

    /// Returns the rotation angle in radians.
    ///
    /// Returns the counterclockwise rotation angle θ where the rotor
    /// was created via `from_angle(θ)`.
    #[inline]
    pub fn angle(&self) -> T {
        // Negate xy because from_angle stores -sin(θ/2) in the bivector component
        T::atan2(-self.b(), self.s()) * T::TWO
    }

    /// Creates a rotor that both rotates and dilates (scales uniformly).
    ///
    /// A unit rotor (`|R| = 1`) performs pure rotation. A scaled rotor
    /// (`|R| = k`) rotates and scales vectors by `k²` via the sandwich product.
    ///
    /// This method creates a rotor with magnitude `√dilation` so that
    /// `R v R†` scales vectors by `dilation`.
    ///
    /// # Arguments
    ///
    /// * `angle` - Rotation angle in radians (counterclockwise)
    /// * `dilation` - Scaling factor for vectors (must be positive)
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::ops::Transform;
    /// use clifford::specialized::euclidean::dim2::{Rotor, Vector};
    /// use approx::abs_diff_eq;
    ///
    /// // Rotate 90° and scale by 2×
    /// let r = Rotor::with_dilation(std::f64::consts::FRAC_PI_2, 2.0);
    /// let v = Vector::new(1.0, 0.0);
    /// let result = r.transform(&v);
    ///
    /// // Should be at (0, 2) - rotated and doubled
    /// assert!(abs_diff_eq!(result.x(), 0.0, epsilon = 1e-10));
    /// assert!(abs_diff_eq!(result.y(), 2.0, epsilon = 1e-10));
    /// ```
    #[inline]
    pub fn with_dilation(angle: T, dilation: T) -> Self {
        let half = angle / T::TWO;
        let scale = dilation.sqrt();
        // Negate bivector: sandwich R v R̃ rotates opposite to the bivector angle
        Self::new_unchecked(scale * half.cos(), -scale * half.sin())
    }

    /// Returns the dilation (scaling) factor of this rotor.
    ///
    /// For a rotor with magnitude `|R| = k`, the sandwich product `R v R†`
    /// scales vectors by `k²`. This method returns that scaling factor.
    ///
    /// For unit rotors, returns `1.0`.
    ///
    /// # Example
    ///
    /// ```
    /// use clifford::specialized::euclidean::dim2::Rotor;
    /// use approx::abs_diff_eq;
    ///
    /// let r = Rotor::with_dilation(0.5, 4.0);
    /// assert!(abs_diff_eq!(r.dilation_factor(), 4.0, epsilon = 1e-10));
    ///
    /// let unit = Rotor::from_angle(1.0);
    /// assert!(abs_diff_eq!(unit.dilation_factor(), 1.0, epsilon = 1e-10));
    /// ```
    #[inline]
    pub fn dilation_factor(&self) -> T {
        self.norm_squared()
    }
}