use std::f64::consts::PI;
pub use geometry_macros::{TransformMut, TransformRef, TranslateMut, TranslateRef};
use impl_trait_for_tuples::impl_for_tuples;
use serde::{Deserialize, Serialize};
use super::orientation::Orientation;
use crate::point::Point;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct Transformation {
pub(crate) mat: TransformationMatrix,
pub(crate) b: Point,
}
impl Default for Transformation {
fn default() -> Self {
Self::identity()
}
}
#[derive(Debug, Clone, Copy, Default, Eq, Ord, PartialOrd, PartialEq, Serialize, Deserialize)]
pub enum Rotation {
#[default]
R0,
R90,
R180,
R270,
}
impl std::ops::Add<Rotation> for Rotation {
type Output = Rotation;
fn add(self, rhs: Rotation) -> Self::Output {
use Rotation::*;
match (self, rhs) {
(R0, R0) => R0,
(R0, R90) => R90,
(R0, R180) => R180,
(R0, R270) => R270,
(R90, R0) => R90,
(R90, R90) => R180,
(R90, R180) => R270,
(R90, R270) => R0,
(R180, R0) => R180,
(R180, R90) => R270,
(R180, R180) => R0,
(R180, R270) => R90,
(R270, R0) => R270,
(R270, R90) => R0,
(R270, R180) => R90,
(R270, R270) => R180,
}
}
}
impl std::ops::AddAssign for Rotation {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl std::ops::Sub<Rotation> for Rotation {
type Output = Rotation;
fn sub(self, rhs: Rotation) -> Self::Output {
use Rotation::*;
match (self, rhs) {
(R0, R0) => R0,
(R0, R90) => R270,
(R0, R180) => R180,
(R0, R270) => R90,
(R90, R0) => R90,
(R90, R90) => R0,
(R90, R180) => R270,
(R90, R270) => R180,
(R180, R0) => R180,
(R180, R90) => R90,
(R180, R180) => R0,
(R180, R270) => R270,
(R270, R0) => R270,
(R270, R90) => R180,
(R270, R180) => R90,
(R270, R270) => R0,
}
}
}
impl std::ops::SubAssign for Rotation {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Rotation {
#[inline]
pub fn transformation_matrix(&self) -> TransformationMatrix {
TransformationMatrix::from(*self)
}
pub fn degrees(&self) -> f64 {
match self {
Rotation::R0 => 0.,
Rotation::R90 => 90.,
Rotation::R180 => 180.,
Rotation::R270 => 270.,
}
}
pub fn radians(&self) -> f64 {
match self {
Rotation::R0 => 0.,
Rotation::R90 => PI / 2.,
Rotation::R180 => PI,
Rotation::R270 => PI * 1.5,
}
}
}
pub struct NonManhattanAngleError;
impl TryFrom<f64> for Rotation {
type Error = NonManhattanAngleError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
let value = (((value % 360.) + 360.) % 360.).round() as i64;
match value {
0 => Ok(Rotation::R0),
90 => Ok(Rotation::R90),
180 => Ok(Rotation::R180),
270 => Ok(Rotation::R270),
_ => Err(NonManhattanAngleError),
}
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct TransformationMatrix([[i8; 2]; 2]);
impl TransformationMatrix {
#[inline]
pub fn identity() -> Self {
Self([[1, 0], [0, 1]])
}
pub fn rotate(&self, angle: Rotation) -> Self {
angle.transformation_matrix() * *self
}
pub fn reflect_vert(&self) -> Self {
Self([[1, 0], [0, -1]]) * *self
}
pub fn inverse(&self) -> Self {
Self(unitary_matinv(&self.0))
}
}
impl From<Rotation> for TransformationMatrix {
fn from(value: Rotation) -> Self {
Self(match value {
Rotation::R0 => [[1, 0], [0, 1]],
Rotation::R90 => [[0, -1], [1, 0]],
Rotation::R180 => [[-1, 0], [0, -1]],
Rotation::R270 => [[0, 1], [-1, 0]],
})
}
}
fn matmul_i8(a: &[[i8; 2]; 2], b: &[[i8; 2]; 2]) -> [[i8; 2]; 2] {
[
[
a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1],
],
]
}
fn matvec_i8_i64(a: &[[i8; 2]; 2], b: &[i64; 2]) -> [i64; 2] {
[
a[0][0] as i64 * b[0] + a[0][1] as i64 * b[1],
a[1][0] as i64 * b[0] + a[1][1] as i64 * b[1],
]
}
impl std::ops::Mul<TransformationMatrix> for TransformationMatrix {
type Output = Self;
fn mul(self, rhs: TransformationMatrix) -> Self::Output {
Self(matmul_i8(&self.0, &rhs.0))
}
}
impl std::ops::Mul<Point> for TransformationMatrix {
type Output = Point;
fn mul(self, rhs: Point) -> Self::Output {
let out = matvec_i8_i64(&self.0, &[rhs.x, rhs.y]);
Point::new(out[0], out[1])
}
}
impl std::ops::Deref for TransformationMatrix {
type Target = [[i8; 2]; 2];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for TransformationMatrix {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Default for TransformationMatrix {
#[inline]
fn default() -> Self {
Self::identity()
}
}
impl Transformation {
pub fn identity() -> Self {
Self {
mat: TransformationMatrix::identity(),
b: Point::zero(),
}
}
pub fn translate(x: i64, y: i64) -> Self {
Self {
mat: TransformationMatrix::identity(),
b: Point::new(x, y),
}
}
pub fn translate_ref(&self, x: i64, y: i64) -> Self {
Self {
mat: self.mat,
b: Point::new(x, y) + self.b,
}
}
pub fn rotate(angle: Rotation) -> Self {
let mat = TransformationMatrix::from(angle);
Self {
mat,
b: Point::zero(),
}
}
pub fn reflect_vert() -> Self {
Self {
mat: TransformationMatrix([[1, 0], [0, -1]]),
b: Point::zero(),
}
}
#[inline]
pub fn builder() -> TransformationBuilder {
TransformationBuilder::default()
}
pub fn from_offset(offset: Point) -> Self {
Self::builder()
.point(offset)
.orientation(Orientation::default())
.build()
}
pub fn from_offset_and_orientation(offset: Point, orientation: impl Into<Orientation>) -> Self {
Self::builder()
.point(offset)
.orientation(orientation.into())
.build()
}
pub fn from_opts(offset: Point, reflect_vert: bool, angle: Rotation) -> Self {
Self::builder()
.point(offset)
.reflect_vert(reflect_vert)
.angle(angle)
.build()
}
pub fn cascade(parent: Transformation, child: Transformation) -> Transformation {
let mut b = parent.mat * child.b;
b += parent.b;
let mat = parent.mat * child.mat;
Self { mat, b }
}
pub fn offset_point(&self) -> Point {
self.b
}
pub fn orientation(&self) -> Orientation {
let reflect_vert = if self.mat[0][0] == 0 {
self.mat[0][1].signum() == self.mat[1][0].signum()
} else {
self.mat[0][0].signum() != self.mat[1][1].signum()
};
let cos = self.mat[0][0];
let sin = self.mat[1][0];
let angle = match (cos, sin) {
(1, 0) => Rotation::R0,
(0, 1) => Rotation::R90,
(-1, 0) => Rotation::R180,
(0, -1) => Rotation::R270,
_ => panic!("transformation did not represent a valid Manhattan transformation"),
};
Orientation {
reflect_vert,
angle,
}
}
pub fn inv(&self) -> Transformation {
let inv = self.mat.inverse();
let invb = inv * self.b;
Self { mat: inv, b: -invb }
}
}
impl<T> From<T> for Transformation
where
T: Into<Orientation>,
{
fn from(value: T) -> Self {
Self::builder().orientation(value).build()
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TransformationBuilder {
x: i64,
y: i64,
reflect_vert: bool,
angle: Rotation,
}
impl TransformationBuilder {
pub fn point(&mut self, point: impl Into<Point>) -> &mut Self {
let point = point.into();
self.x = point.x;
self.y = point.y;
self
}
pub fn orientation(&mut self, o: impl Into<Orientation>) -> &mut Self {
let o = o.into();
self.reflect_vert = o.reflect_vert;
self.angle = o.angle;
self
}
pub fn angle(&mut self, angle: Rotation) -> &mut Self {
self.angle = angle;
self
}
pub fn reflect_vert(&mut self, reflect_vert: bool) -> &mut Self {
self.reflect_vert = reflect_vert;
self
}
pub fn build(&mut self) -> Transformation {
let mut mat = self.angle.transformation_matrix();
if self.reflect_vert {
mat[0][1] = -mat[0][1];
mat[1][1] = -mat[1][1];
}
Transformation {
mat,
b: Point::new(self.x, self.y),
}
}
}
fn unitary_matinv(a: &[[i8; 2]; 2]) -> [[i8; 2]; 2] {
[[a[1][1], -a[0][1]], [-a[1][0], a[0][0]]]
}
pub trait TransformRef: TranslateRef {
fn transform_ref(&self, trans: Transformation) -> Self;
}
impl TransformRef for () {
fn transform_ref(&self, _trans: Transformation) -> Self {}
}
impl<T: TransformRef> TransformRef for Vec<T> {
fn transform_ref(&self, trans: Transformation) -> Self {
self.iter().map(|elt| elt.transform_ref(trans)).collect()
}
}
impl<T: TransformRef> TransformRef for Option<T> {
fn transform_ref(&self, trans: Transformation) -> Self {
self.as_ref().map(move |elt| elt.transform_ref(trans))
}
}
#[impl_for_tuples(32)]
pub trait TransformMut: TranslateMut {
fn transform_mut(&mut self, trans: Transformation);
}
impl<T: TransformMut> TransformMut for Vec<T> {
fn transform_mut(&mut self, trans: Transformation) {
for i in self.iter_mut() {
i.transform_mut(trans);
}
}
}
impl<T: TransformMut> TransformMut for Option<T> {
fn transform_mut(&mut self, trans: Transformation) {
if let Some(inner) = self.as_mut() {
inner.transform_mut(trans);
}
}
}
pub trait Transform: Translate + TransformMut + Sized {
#[inline]
fn transform(mut self, trans: Transformation) -> Self {
self.transform_mut(trans);
self
}
}
impl<T: TransformMut + Sized> Transform for T {}
pub trait TranslateRef: Sized {
fn translate_ref(&self, p: Point) -> Self;
}
impl TranslateRef for () {
fn translate_ref(&self, _p: Point) -> Self {}
}
impl<T: TranslateRef> TranslateRef for Vec<T> {
fn translate_ref(&self, p: Point) -> Self {
self.iter().map(|elt| elt.translate_ref(p)).collect()
}
}
impl<T: TranslateRef> TranslateRef for Option<T> {
fn translate_ref(&self, p: Point) -> Self {
self.as_ref().map(move |elt| elt.translate_ref(p))
}
}
#[impl_for_tuples(32)]
pub trait TranslateMut {
fn translate_mut(&mut self, p: Point);
}
impl<T: TranslateMut> TranslateMut for Vec<T> {
fn translate_mut(&mut self, p: Point) {
for i in self.iter_mut() {
i.translate_mut(p);
}
}
}
impl<T: TranslateMut> TranslateMut for Option<T> {
fn translate_mut(&mut self, p: Point) {
if let Some(inner) = self.as_mut() {
inner.translate_mut(p);
}
}
}
pub trait Translate: TranslateMut + Sized {
fn translate(mut self, p: Point) -> Self {
self.translate_mut(p);
self
}
}
impl<T: TranslateMut + Sized> Translate for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{orientation::NamedOrientation, rect::Rect};
#[test]
fn matvec_works() {
let a = [[1, 2], [3, 4]];
let b = [5, 6];
assert_eq!(matvec_i8_i64(&a, &b), [17, 39]);
}
#[test]
fn matmul_works() {
let a = [[1, 2], [3, 4]];
let b = [[5, 6], [7, 8]];
assert_eq!(matmul_i8(&a, &b), [[19, 22], [43, 50]]);
}
#[test]
fn unitary_matinv_works() {
let a = [[1, 1], [3, 4]];
let inv = unitary_matinv(&a);
let a_mul_inv = matmul_i8(&a, &inv);
assert_eq!(a_mul_inv[0][0], 1);
assert_eq!(a_mul_inv[0][1], 0);
assert_eq!(a_mul_inv[1][0], 0);
assert_eq!(a_mul_inv[1][1], 1);
}
#[test]
fn cascade_identity_preserves_transformation() {
for orientation in NamedOrientation::all_rectangular() {
let tf = Transformation::from_offset_and_orientation(Point::new(520, 130), orientation);
let casc = Transformation::cascade(tf, Transformation::identity());
assert_eq!(
tf, casc,
"Cascading with identity produced incorrect transformation for orientation {:?}",
orientation
);
}
}
#[test]
fn transformation_offset_and_orientation_preserves_components() {
let pt = Point::new(8930, 730);
for orientation in NamedOrientation::all_rectangular() {
println!("Testing orientation {:?}", orientation);
let tf = Transformation::from_offset_and_orientation(pt, orientation);
assert_eq!(tf.orientation(), orientation.into());
assert_eq!(tf.offset_point(), pt);
}
}
#[test]
fn transformation_equivalent_to_offset_and_orientation() {
for orientation in NamedOrientation::all_rectangular() {
println!("Testing orientation {:?}", orientation);
let tf1 =
Transformation::from_offset_and_orientation(Point::new(380, 340), orientation);
assert_eq!(tf1.orientation(), orientation.into());
let tf2 =
Transformation::from_offset_and_orientation(tf1.offset_point(), tf1.orientation());
assert_eq!(tf1, tf2);
}
}
#[test]
fn point_transformations_work() {
let pt = Point::new(2, 1);
let pt_reflect_vert = pt.transform(Transformation::from_offset_and_orientation(
Point::zero(),
NamedOrientation::ReflectVert,
));
assert_eq!(pt_reflect_vert, Point::new(2, -1));
let pt_reflect_horiz = pt.transform(Transformation::from_offset_and_orientation(
Point::zero(),
NamedOrientation::ReflectHoriz,
));
assert_eq!(pt_reflect_horiz, Point::new(-2, 1));
let pt_r90 = pt.transform(Transformation::from_offset_and_orientation(
Point::new(23, 11),
NamedOrientation::R90,
));
assert_eq!(pt_r90, Point::new(22, 13));
let pt_r180 = pt.transform(Transformation::from_offset_and_orientation(
Point::new(-50, 10),
NamedOrientation::R180,
));
assert_eq!(pt_r180, Point::new(-52, 9));
let pt_r270 = pt.transform(Transformation::from_offset_and_orientation(
Point::new(80, 90),
NamedOrientation::R270,
));
assert_eq!(pt_r270, Point::new(81, 88));
let pt_r90cw = pt.transform(Transformation::from_offset_and_orientation(
Point::new(5, 13),
NamedOrientation::R90Cw,
));
assert_eq!(pt_r90cw, Point::new(6, 11));
let pt_r180cw = pt.transform(Transformation::from_offset_and_orientation(
Point::zero(),
NamedOrientation::R180Cw,
));
assert_eq!(pt_r180cw, Point::new(-2, -1));
let pt_r270cw = pt.transform(Transformation::from_offset_and_orientation(
Point::new(1, 100),
NamedOrientation::R270Cw,
));
assert_eq!(pt_r270cw, Point::new(0, 102));
let pt_flip_yx = pt.transform(Transformation::from_offset_and_orientation(
Point::new(-65, -101),
NamedOrientation::FlipYx,
));
assert_eq!(pt_flip_yx, Point::new(-64, -99));
let pt_flip_minus_yx = pt.transform(Transformation::from_offset_and_orientation(
Point::new(1, -5),
NamedOrientation::FlipMinusYx,
));
assert_eq!(pt_flip_minus_yx, Point::new(0, -7));
}
#[test]
fn translate_works_for_tuples() {
let mut tuple = (
Rect::from_sides(0, 0, 100, 200),
Rect::from_sides(50, -50, 150, 0),
);
tuple.translate_mut(Point::new(5, 10));
assert_eq!(
tuple,
(
Rect::from_sides(5, 10, 105, 210),
Rect::from_sides(55, -40, 155, 10)
)
);
}
#[test]
fn translate_works_for_vecs() {
let mut v = vec![
Rect::from_sides(0, 0, 100, 200),
Rect::from_sides(50, -50, 150, 0),
];
v.translate_mut(Point::new(5, 10));
assert_eq!(
v,
vec![
Rect::from_sides(5, 10, 105, 210),
Rect::from_sides(55, -40, 155, 10)
]
);
}
#[test]
fn transform_works_for_tuples() {
let mut tuple = (
Rect::from_sides(0, 0, 100, 200),
Rect::from_sides(50, -50, 150, 0),
);
tuple.transform_mut(Transformation::from_offset_and_orientation(
Point::zero(),
NamedOrientation::R90,
));
assert_eq!(
tuple,
(
Rect::from_sides(-200, 0, 0, 100),
Rect::from_sides(0, 50, 50, 150)
)
);
}
#[test]
fn transform_works_for_vecs() {
let mut v = vec![
Rect::from_sides(0, 0, 100, 200),
Rect::from_sides(50, -50, 150, 0),
];
v.transform_mut(Transformation::from_offset_and_orientation(
Point::zero(),
NamedOrientation::R90,
));
assert_eq!(
v,
vec![
Rect::from_sides(-200, 0, 0, 100),
Rect::from_sides(0, 50, 50, 150)
]
);
}
#[test]
fn transform_macros_work() {
#[derive(Debug, Copy, Clone, Eq, PartialEq, TranslateMut, TransformMut)]
pub struct TwoPointGroup {
p1: Point,
p2: Point,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, TranslateMut, TransformMut)]
pub enum PointEnum {
First(Point),
Second { pt: Point },
}
let group = TwoPointGroup {
p1: Point::new(100, 200),
p2: Point::new(-400, 300),
};
let group = group.translate(Point::new(100, 50));
assert_eq!(
group,
TwoPointGroup {
p1: Point::new(200, 250),
p2: Point::new(-300, 350),
}
);
let mut group = PointEnum::First(Point::new(100, 200));
group = group.transform(NamedOrientation::ReflectVert.into());
assert_eq!(group, PointEnum::First(Point::new(100, -200)),);
let mut group = PointEnum::Second {
pt: Point::new(100, 200),
};
group = group.transform(NamedOrientation::ReflectVert.into());
assert_eq!(
group,
PointEnum::Second {
pt: Point::new(100, -200)
}
);
}
}