gdsr 0.0.1-alpha.2

A GDSII reader and writer for Rust
Documentation
use crate::{AngleInRadians, Point};

/// A rotation transformation defined by an angle (in radians) and a centre point.
#[derive(Clone, Debug, PartialEq)]
pub struct Rotation {
    angle: AngleInRadians,
    centre: Point,
}

impl std::fmt::Display for Rotation {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Rotation of {} rad about {}", self.angle, self.centre)
    }
}

impl Rotation {
    /// Creates a new rotation with the given angle (in radians) and centre point.
    pub const fn new(angle: AngleInRadians, centre: Point) -> Self {
        Self { angle, centre }
    }

    /// Returns the rotation angle in radians.
    pub const fn angle(&self) -> AngleInRadians {
        self.angle
    }

    /// Returns the centre point of the rotation.
    pub const fn centre(&self) -> &Point {
        &self.centre
    }

    /// Rotates a point around this rotation's centre and returns the new point.
    pub fn apply_to_point(&self, point: &Point) -> Point {
        let cos_angle = self.angle.cos();
        let sin_angle = self.angle.sin();

        let self_center_x = self.centre.x();
        let self_center_y = self.centre.y();

        let dx = point.x() - self_center_x;
        let dy = point.y() - self_center_y;

        let new_x = (dy * -sin_angle) + (dx * cos_angle) + self_center_x;
        let new_y = (dy * cos_angle) + (dx * sin_angle) + self_center_y;

        Point::new(new_x, new_y)
    }
}

#[cfg(test)]
mod tests {
    use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};

    use super::*;

    #[test]
    fn test_rotation_new() {
        let rotation = Rotation::new(FRAC_PI_2, Point::integer(10, 20, 1e-9));
        assert_eq!(rotation.angle(), FRAC_PI_2);
        assert_eq!(rotation.centre(), &Point::integer(10, 20, 1e-9));
    }

    #[test]
    fn test_rotation_getters() {
        let centre = Point::integer(5, 10, 1e-9);
        let rotation = Rotation::new(FRAC_PI_4, centre);
        assert_eq!(rotation.angle(), FRAC_PI_4);
        assert_eq!(rotation.centre(), &centre);
    }

    #[test]
    fn test_rotation_apply_at_centre() {
        let centre = Point::integer(10, 10, 1e-9);
        let rotation = Rotation::new(90.0, centre);
        let rotated = rotation.apply_to_point(&centre);

        // Point at centre should remain unchanged
        assert_eq!(rotated, centre);
    }

    #[test]
    fn test_rotation_display() {
        let rotation = Rotation::new(FRAC_PI_2, Point::integer(10, 20, 1e-9));
        insta::assert_snapshot!(rotation.to_string(), @"Rotation of 1.5707963267948966 rad about Point(10 (1.000e-9), 20 (1.000e-9))");
    }

    #[test]
    fn test_rotation_90_degrees() {
        let rotation = Rotation::new(90.0, Point::integer(0, 0, 1e-9));
        let point = Point::integer(10, 0, 1e-9);
        let rotated = rotation.apply_to_point(&point);

        // (10, 0) rotated 90° around origin should be approximately (0, 10)
        let expected = Point::integer(0, 10, 1e-9);
        // Allow small floating point errors
        assert!((rotated.x() - expected.x()).absolute_value().abs() < 1e-6);
        assert!((rotated.y() - expected.y()).absolute_value().abs() < 1e-6);
    }

    /// Rotation by 2pi + pi/2 should produce the same result as pi/2 alone.
    #[test]
    fn test_rotation_greater_than_2pi() {
        let origin = Point::integer(0, 0, 1e-9);
        let point = Point::integer(10, 0, 1e-9);

        let rotation_large = Rotation::new(std::f64::consts::TAU + FRAC_PI_2, origin);
        let rotation_normal = Rotation::new(FRAC_PI_2, origin);

        let result_large = rotation_large.apply_to_point(&point);
        let result_normal = rotation_normal.apply_to_point(&point);

        assert!(
            (result_large.x() - result_normal.x())
                .absolute_value()
                .abs()
                < 1e-6
        );
        assert!(
            (result_large.y() - result_normal.y())
                .absolute_value()
                .abs()
                < 1e-6
        );
    }

    #[test]
    fn test_rotation_negative_angle() {
        let origin = Point::integer(0, 0, 1e-9);
        let rotation = Rotation::new(-FRAC_PI_2, origin);
        let point = Point::integer(10, 0, 1e-9);
        let rotated = rotation.apply_to_point(&point);

        let expected = Point::integer(0, -10, 1e-9);
        assert!((rotated.x() - expected.x()).absolute_value().abs() < 1e-6);
        assert!((rotated.y() - expected.y()).absolute_value().abs() < 1e-6);
    }
}