use crate::{Coord, CoordFloat, CoordNum, MapCoords, MapCoordsInPlace};
use std::fmt;
pub trait AffineOps<T: CoordNum> {
#[must_use]
fn affine_transform(&self, transform: &AffineTransform<T>) -> Self;
fn affine_transform_mut(&mut self, transform: &AffineTransform<T>);
}
impl<T: CoordNum, M: MapCoordsInPlace<T> + MapCoords<T, T, Output = Self>> AffineOps<T> for M {
fn affine_transform(&self, transform: &AffineTransform<T>) -> Self {
self.map_coords(|c| transform.apply(c))
}
fn affine_transform_mut(&mut self, transform: &AffineTransform<T>) {
self.map_coords_in_place(|c| transform.apply(c))
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct AffineTransform<T: CoordNum = f64>([[T; 3]; 3]);
impl<T: CoordNum> Default for AffineTransform<T> {
fn default() -> Self {
Self::identity()
}
}
impl<T: CoordNum> AffineTransform<T> {
#[must_use]
pub fn compose(&self, other: &Self) -> Self {
Self([
[
(self.0[0][0] * other.0[0][0])
+ (self.0[0][1] * other.0[1][0])
+ (self.0[0][2] * other.0[2][0]),
(self.0[0][0] * other.0[0][1])
+ (self.0[0][1] * other.0[1][1])
+ (self.0[0][2] * other.0[2][1]),
(self.0[0][0] * other.0[0][2])
+ (self.0[0][1] * other.0[1][2])
+ (self.0[0][2] * other.0[2][2]),
],
[
(self.0[1][0] * other.0[0][0])
+ (self.0[1][1] * other.0[1][0])
+ (self.0[1][2] * other.0[2][0]),
(self.0[1][0] * other.0[0][1])
+ (self.0[1][1] * other.0[1][1])
+ (self.0[1][2] * other.0[2][1]),
(self.0[1][0] * other.0[0][2])
+ (self.0[1][1] * other.0[1][2])
+ (self.0[1][2] * other.0[2][2]),
],
[
(self.0[2][0] * other.0[0][0])
+ (self.0[2][1] * other.0[1][0])
+ (self.0[2][2] * other.0[2][0]),
(self.0[2][0] * other.0[0][1])
+ (self.0[2][1] * other.0[1][1])
+ (self.0[2][2] * other.0[2][1]),
(self.0[2][0] * other.0[0][2])
+ (self.0[2][1] * other.0[1][2])
+ (self.0[2][2] * other.0[2][2]),
],
])
}
pub fn identity() -> Self {
Self::new(
T::one(),
T::zero(),
T::zero(),
T::zero(),
T::one(),
T::zero(),
)
}
pub fn is_identity(&self) -> bool {
self == &Self::identity()
}
pub fn scale(xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
let (x0, y0) = origin.into().x_y();
let xoff = x0 - (x0 * xfact);
let yoff = y0 - (y0 * yfact);
Self::new(xfact, T::zero(), xoff, T::zero(), yfact, yoff)
}
#[must_use]
pub fn scaled(mut self, xfact: T, yfact: T, origin: impl Into<Coord<T>>) -> Self {
self.0 = self.compose(&Self::scale(xfact, yfact, origin)).0;
self
}
pub fn translate(xoff: T, yoff: T) -> Self {
Self::new(T::one(), T::zero(), xoff, T::zero(), T::one(), yoff)
}
#[must_use]
pub fn translated(mut self, xoff: T, yoff: T) -> Self {
self.0 = self.compose(&Self::translate(xoff, yoff)).0;
self
}
pub fn apply(&self, coord: Coord<T>) -> Coord<T> {
Coord {
x: (self.0[0][0] * coord.x + self.0[0][1] * coord.y + self.0[0][2]),
y: (self.0[1][0] * coord.x + self.0[1][1] * coord.y + self.0[1][2]),
}
}
pub fn new(a: T, b: T, xoff: T, d: T, e: T, yoff: T) -> Self {
Self([[a, b, xoff], [d, e, yoff], [T::zero(), T::zero(), T::one()]])
}
}
impl<T: CoordNum> fmt::Debug for AffineTransform<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AffineTransform")
.field("a", &self.0[0][0])
.field("b", &self.0[0][1])
.field("xoff", &self.0[1][2])
.field("d", &self.0[1][0])
.field("e", &self.0[1][1])
.field("yoff", &self.0[1][2])
.finish()
}
}
impl<T: CoordNum> From<[T; 6]> for AffineTransform<T> {
fn from(arr: [T; 6]) -> Self {
Self::new(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5])
}
}
impl<T: CoordNum> From<(T, T, T, T, T, T)> for AffineTransform<T> {
fn from(tup: (T, T, T, T, T, T)) -> Self {
Self::new(tup.0, tup.1, tup.2, tup.3, tup.4, tup.5)
}
}
impl<U: CoordFloat> AffineTransform<U> {
pub fn rotate(degrees: U, origin: impl Into<Coord<U>>) -> Self {
let (sin_theta, cos_theta) = degrees.to_radians().sin_cos();
let (x0, y0) = origin.into().x_y();
let xoff = x0 - (x0 * cos_theta) + (y0 * sin_theta);
let yoff = y0 - (x0 * sin_theta) - (y0 * cos_theta);
Self::new(cos_theta, -sin_theta, xoff, sin_theta, cos_theta, yoff)
}
#[must_use]
pub fn rotated(mut self, angle: U, origin: impl Into<Coord<U>>) -> Self {
self.0 = self.compose(&Self::rotate(angle, origin)).0;
self
}
pub fn skew(xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
let Coord { x: x0, y: y0 } = origin.into();
let mut tanx = xs.to_radians().tan();
let mut tany = ys.to_radians().tan();
if tanx.abs() < U::from::<f64>(2.5e-16).unwrap() {
tanx = U::zero();
}
if tany.abs() < U::from::<f64>(2.5e-16).unwrap() {
tany = U::zero();
}
let xoff = -y0 * tanx;
let yoff = -x0 * tany;
Self::new(U::one(), tanx, xoff, tany, U::one(), yoff)
}
#[must_use]
pub fn skewed(mut self, xs: U, ys: U, origin: impl Into<Coord<U>>) -> Self {
self.0 = self.compose(&Self::skew(xs, ys, origin)).0;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{polygon, Point};
#[test]
fn matrix_multiply() {
let a = AffineTransform::new(1, 2, 5, 3, 4, 6);
let b = AffineTransform::new(7, 8, 11, 9, 10, 12);
let composed = a.compose(&b);
assert_eq!(composed.0[0][0], 25);
assert_eq!(composed.0[0][1], 28);
assert_eq!(composed.0[0][2], 40);
assert_eq!(composed.0[1][0], 57);
assert_eq!(composed.0[1][1], 64);
assert_eq!(composed.0[1][2], 87);
}
#[test]
fn test_transform_composition() {
let p0 = Point::new(0.0f64, 0.0);
let mut scale_a = AffineTransform::default().scaled(2.0, 2.0, p0);
scale_a = scale_a.rotated(45.0, p0);
scale_a = scale_a.rotated(-45.0, p0);
scale_a = scale_a.scaled(2.0, 2.0, p0);
let scale_b = AffineTransform::default().scaled(2.0, 2.0, p0);
let scale_c = AffineTransform::default().scaled(4.0, 4.0, p0);
assert_ne!(&scale_a.0, &scale_b.0);
assert_eq!(&scale_a.0, &scale_c.0);
}
#[test]
fn affine_transformed() {
let transform = AffineTransform::translate(1.0, 1.0).scaled(2.0, 2.0, (0.0, 0.0));
let mut poly = polygon![(x: 0.0, y: 0.0), (x: 0.0, y: 2.0), (x: 1.0, y: 2.0)];
poly.affine_transform_mut(&transform);
let expected = polygon![(x: 1.0, y: 1.0), (x: 1.0, y: 5.0), (x: 3.0, y: 5.0)];
assert_eq!(expected, poly);
}
}