use crate::{AngleInRadians, 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 {
pub const fn new(angle: AngleInRadians, centre: Point) -> Self {
Self { angle, centre }
}
pub const fn angle(&self) -> AngleInRadians {
self.angle
}
pub const fn centre(&self) -> &Point {
&self.centre
}
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(), ¢re);
}
#[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(¢re);
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);
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);
}
#[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);
}
}