use approx::{AbsDiffEq, RelativeEq, UlpsEq};
pub use geometry_macros::{TransformMut, TranslateMut};
use impl_trait_for_tuples::impl_for_tuples;
use serde::{Deserialize, Serialize};
use super::orientation::Orientation;
use crate::point::Point;
use crate::wrap_angle;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Transformation {
pub(crate) a: [[f64; 2]; 2],
pub(crate) b: [f64; 2],
}
impl Default for Transformation {
fn default() -> Self {
Self::identity()
}
}
impl Transformation {
pub fn identity() -> Self {
Self {
a: [[1., 0.], [0., 1.]],
b: [0., 0.],
}
}
pub fn translate(x: f64, y: f64) -> Self {
Self {
a: [[1., 0.], [0., 1.]],
b: [x, y],
}
}
pub fn rotate(angle: f64) -> Self {
let sin = angle.to_radians().sin();
let cos = angle.to_radians().cos();
Self {
a: [[cos, -sin], [sin, cos]],
b: [0., 0.],
}
}
pub fn reflect_vert() -> Self {
Self {
a: [[1., 0.], [0., -1.]],
b: [0., 0.],
}
}
#[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: f64) -> Self {
Self::builder()
.point(offset)
.reflect_vert(reflect_vert)
.angle(angle)
.build()
}
pub fn cascade(parent: Transformation, child: Transformation) -> Transformation {
let mut b = matvec(&parent.a, &child.b);
b[0] += parent.b[0];
b[1] += parent.b[1];
let a = matmul(&parent.a, &child.a);
Self { a, b }
}
pub fn offset_point(&self) -> Point {
Point {
x: self.b[0].round() as i64,
y: self.b[1].round() as i64,
}
}
pub fn orientation(&self) -> Orientation {
let reflect_vert = self.a[0][0].signum() != self.a[1][1].signum();
let sin = self.a[1][0];
let cos = self.a[0][0];
let angle = cos.acos().to_degrees();
let angle = if sin > 0f64 {
angle
} else {
wrap_angle(-angle)
};
Orientation {
reflect_vert,
angle,
}
}
pub fn inv(&self) -> Transformation {
let inv = unitary_matinv(&self.a);
let invb = matvec(&inv, &self.b);
Self {
a: inv,
b: [-invb[0], -invb[1]],
}
}
}
impl<T> From<T> for Transformation
where
T: Into<Orientation>,
{
fn from(value: T) -> Self {
Self::builder().orientation(value).build()
}
}
impl AbsDiffEq for Transformation {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: f64) -> bool {
self.a[0].abs_diff_eq(&other.a[0], epsilon)
&& self.a[1].abs_diff_eq(&other.a[1], epsilon)
&& self.b.abs_diff_eq(&other.b, epsilon)
}
}
impl RelativeEq for Transformation {
fn default_max_relative() -> f64 {
f64::default_max_relative()
}
fn relative_eq(&self, other: &Self, epsilon: f64, max_relative: f64) -> bool {
self.a[0].relative_eq(&other.a[0], epsilon, max_relative)
&& self.a[1].relative_eq(&other.a[1], epsilon, max_relative)
&& self.b.relative_eq(&other.b, epsilon, max_relative)
}
}
impl UlpsEq for Transformation {
fn default_max_ulps() -> u32 {
f64::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
self.a[0].ulps_eq(&other.a[0], epsilon, max_ulps)
&& self.a[1].ulps_eq(&other.a[1], epsilon, max_ulps)
&& self.b.ulps_eq(&other.b, epsilon, max_ulps)
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TransformationBuilder {
x: f64,
y: f64,
reflect_vert: bool,
angle: f64,
}
impl TransformationBuilder {
pub fn point(&mut self, point: impl Into<Point>) -> &mut Self {
let point = point.into();
self.x = point.x as f64;
self.y = point.y as f64;
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: f64) -> &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 b = [self.x, self.y];
let sin = self.angle.to_radians().sin();
let cos = self.angle.to_radians().cos();
let sin_refl = if self.reflect_vert { sin } else { -sin };
let cos_refl = if self.reflect_vert { -cos } else { cos };
let a = [[cos, sin_refl], [sin, cos_refl]];
Transformation { a, b }
}
}
fn matmul(a: &[[f64; 2]; 2], b: &[[f64; 2]; 2]) -> [[f64; 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(a: &[[f64; 2]; 2], b: &[f64; 2]) -> [f64; 2] {
[
a[0][0] * b[0] + a[0][1] * b[1],
a[1][0] * b[0] + a[1][1] * b[1],
]
}
fn unitary_matinv(a: &[[f64; 2]; 2]) -> [[f64; 2]; 2] {
[[a[1][1], -a[0][1]], [-a[1][0], a[0][0]]]
}
#[impl_for_tuples(32)]
pub trait TransformMut {
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: TransformMut + Sized {
fn transform(mut self, trans: Transformation) -> Self {
self.transform_mut(trans);
self
}
}
impl<T: TransformMut + Sized> Transform for T {}
#[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 {}
pub trait HasTransformedView {
type TransformedView;
fn transformed_view(&self, trans: Transformation) -> Self::TransformedView;
}
pub type Transformed<T> = <T as HasTransformedView>::TransformedView;
impl HasTransformedView for () {
type TransformedView = ();
fn transformed_view(&self, _trans: Transformation) -> Self::TransformedView {}
}
impl<T: HasTransformedView> HasTransformedView for Vec<T> {
type TransformedView = Vec<Transformed<T>>;
fn transformed_view(&self, trans: Transformation) -> Self::TransformedView {
self.iter().map(|e| e.transformed_view(trans)).collect()
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
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(&a, &b), [17., 39.]);
}
#[test]
fn matmul_works() {
let a = [[1., 2.], [3., 4.]];
let b = [[5., 6.], [7., 8.]];
assert_eq!(matmul(&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(&a, &inv);
assert_relative_eq!(a_mul_inv[0][0], 1.);
assert_relative_eq!(a_mul_inv[0][1], 0.);
assert_relative_eq!(a_mul_inv[1][0], 0.);
assert_relative_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)
]
);
}
}