use crate::{Angle, Point, Vector};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct DecomposedTransform<T = f32> {
pub translation: Vector<T>,
pub scale: Vector<T>,
pub rotation: Angle<T>,
pub skew: Angle<T>,
}
impl<T: en::Float> Default for DecomposedTransform<T> {
fn default() -> Self {
Self::identity()
}
}
impl<T: en::Float> DecomposedTransform<T> {
pub fn identity() -> Self {
Self {
translation: Vector::zero(),
scale: Vector::one(),
rotation: Angle::ZERO(),
skew: Angle::ZERO(),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct Transform<T = f32> {
pub m11: T,
pub m12: T,
pub m21: T,
pub m22: T,
pub m31: T,
pub m32: T,
}
impl<T: en::Num> Default for Transform<T> {
fn default() -> Self {
Self::identity()
}
}
impl<T: en::Num> Transform<T> {
pub fn row_major(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Self {
Self {
m11,
m12,
m21,
m22,
m31,
m32,
}
}
pub fn identity() -> Self {
let (_0, _1) = (T::zero(), T::one());
Self::row_major(_1, _0, _0, _1, _0, _0)
}
pub fn from_scale(x: T, y: T) -> Self {
Self {
m11: x,
m22: y,
..Self::identity()
}
}
pub fn from_scale_vector(scale: Vector<T>) -> Self {
Self::from_scale(scale.dx, scale.dy)
}
pub fn from_rotation(theta: Angle<T>) -> Self
where
T: en::Float,
{
let (sin, cos) = theta.sin_cos();
Self {
m11: cos,
m12: -sin,
m21: sin,
m22: cos,
..Self::identity()
}
}
pub fn from_rotation_with_fixed_point(theta: Angle<T>, point: Point<T>) -> Self
where
T: en::Float,
{
Self::from_rotation(theta)
.pre_translate(-point.x, -point.y)
.post_translate(point.x, point.y)
}
pub fn from_skew(theta: Angle<T>) -> Self
where
T: en::Float,
{
let (sin, cos) = theta.sin_cos();
Self {
m21: -sin,
m22: cos,
..Self::identity()
}
}
pub fn from_translation(x: T, y: T) -> Self {
Self {
m31: x,
m32: y,
..Self::identity()
}
}
pub fn from_translation_vector(translation: Vector<T>) -> Self {
Self::from_translation(translation.dx, translation.dy)
}
pub fn from_decomposed(
DecomposedTransform {
translation,
scale,
rotation,
skew,
}: DecomposedTransform<T>,
) -> Self
where
T: en::Float,
{
let (s1, c1) = rotation.sin_cos();
let k = if scale.dx * scale.dy < T::zero() {
skew
} else {
skew.map_radians(T::neg)
};
let (s2, c2) = (rotation + k).sin_cos();
let m11 = c1 * scale.dx;
let m12 = -s1 * scale.dx;
let m21 = s2 * scale.dy;
let m22 = c2 * scale.dy;
let m31 = translation.dx;
let m32 = translation.dy;
Self::row_major(m11, m12, m21, m22, m31, m32)
}
pub fn determinant(&self) -> T {
self.m11 * self.m22 - self.m12 * self.m21
}
pub fn post_mul(&self, mat: Self) -> Self {
Self::row_major(
self.m11 * mat.m11 + self.m12 * mat.m21,
self.m11 * mat.m12 + self.m12 * mat.m22,
self.m21 * mat.m11 + self.m22 * mat.m21,
self.m21 * mat.m12 + self.m22 * mat.m22,
self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31,
self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32,
)
}
pub fn pre_mul(&self, mat: Self) -> Self {
mat.post_mul(*self)
}
pub fn post_translate(&self, x: T, y: T) -> Self {
self.post_mul(Self::from_translation(x, y))
}
pub fn post_translate_vector(&self, translation: Vector<T>) -> Self {
self.post_mul(Self::from_translation_vector(translation))
}
pub fn pre_translate(&self, x: T, y: T) -> Self {
self.pre_mul(Self::from_translation(x, y))
}
pub fn pre_translate_vector(&self, translation: Vector<T>) -> Self {
self.pre_mul(Self::from_translation_vector(translation))
}
pub fn post_scale(&self, x: T, y: T) -> Self {
self.post_mul(Self::from_scale(x, y))
}
pub fn post_scale_vector(&self, scale: Vector<T>) -> Self {
self.post_mul(Self::from_scale_vector(scale))
}
pub fn pre_scale(&self, x: T, y: T) -> Self {
self.pre_mul(Self::from_scale(x, y))
}
pub fn pre_scale_vector(&self, scale: Vector<T>) -> Self {
self.pre_mul(Self::from_scale_vector(scale))
}
pub fn post_rotate(&self, theta: Angle<T>) -> Self
where
T: en::Float,
{
self.post_mul(Self::from_rotation(theta))
}
pub fn pre_rotate(&self, theta: Angle<T>) -> Self
where
T: en::Float,
{
self.pre_mul(Self::from_rotation(theta))
}
pub fn post_skew(&self, theta: Angle<T>) -> Self
where
T: en::Float,
{
self.post_mul(Self::from_skew(theta))
}
pub fn pre_skew(&self, theta: Angle<T>) -> Self
where
T: en::Float,
{
self.pre_mul(Self::from_skew(theta))
}
pub fn inverse(&self) -> Option<Self> {
let det = self.determinant();
let _0: T = T::zero();
let _1: T = T::one();
if det == _0 {
return None;
}
let inv_det = _1 / det;
Some(Self::row_major(
inv_det * self.m22,
inv_det * (_0 - self.m12),
inv_det * (_0 - self.m21),
inv_det * self.m11,
inv_det * (self.m21 * self.m32 - self.m22 * self.m31),
inv_det * (self.m31 * self.m12 - self.m11 * self.m32),
))
}
pub fn decompose(&self) -> DecomposedTransform<T>
where
T: en::Float,
{
let row_x = Vector::new(self.m11, self.m12);
let row_y = Vector::new(self.m21, self.m22);
let row_z = Vector::new(self.m31, self.m32);
let rotation_x = row_x.angle();
let rotation_y = row_y.angle();
let (skew, y_flip) = {
let skew = (rotation_y - rotation_x).normalize();
let (skew, y_flip) = if skew.radians() < T::zero() {
(skew.map_radians(T::abs), T::one())
} else {
(skew, -T::one())
};
(skew - Angle::FRAC_PI_2(), y_flip)
};
DecomposedTransform {
translation: row_z,
scale: Vector::new(row_x.magnitude(), row_y.magnitude() * y_flip),
rotation: rotation_x,
skew,
}
}
pub fn map<U: en::Num>(self, mut f: impl FnMut(T) -> U) -> Transform<U> {
Transform::row_major(
f(self.m11),
f(self.m12),
f(self.m21),
f(self.m22),
f(self.m31),
f(self.m32),
)
}
impl_casts_and_cast!(Transform);
}
#[cfg(feature = "euclid")]
impl<T, Src, Dst> From<Transform<T>> for euclid::Transform2D<T, Src, Dst> {
fn from(t: Transform<T>) -> euclid::Transform2D<T, Src, Dst> {
Self::new(t.m11, t.m12, t.m21, t.m22, t.m31, t.m32)
}
}
#[cfg(feature = "euclid")]
impl<T, Src, Dst> From<euclid::Transform2D<T, Src, Dst>> for Transform<T> {
fn from(t: euclid::Transform2D<T, Src, Dst>) -> Transform<T> {
Self {
m11: t.m11,
m12: t.m12,
m21: t.m21,
m22: t.m22,
m31: t.m31,
m32: t.m32,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{assert_approx_eq, Direction};
#[test]
fn rotation() {
let v = Direction::East.unit_vector();
let r_act = v.transform(Transform::from_rotation(Angle::from_degrees(90.0)));
let r_exp = Direction::North.unit_vector();
assert_approx_eq!(
r_act.dx,
r_exp.dx,
"90º rotation didn't produce expected dx"
);
assert_approx_eq!(
r_act.dy,
r_exp.dy,
"90º rotation didn't produce expected dy"
);
let r_act = v.transform(Transform::from_rotation(Angle::from_degrees(-90.0)));
let r_exp = Direction::South.unit_vector();
assert_approx_eq!(
r_act.dx,
r_exp.dx,
"-90º rotation didn't produce expected dx"
);
assert_approx_eq!(
r_act.dy,
r_exp.dy,
"-90º rotation didn't produce expected dy"
);
}
macro_rules! check_decomposition {
($transform:expr, $expected:expr) => {
let actual = $transform.decompose();
assert_approx_eq!(
actual.translation.dx,
$expected.translation.dx,
"decomposed translation dx didn't match input translation dx"
);
assert_approx_eq!(
actual.translation.dy,
$expected.translation.dy,
"decomposed translation dy didn't match input translation dy"
);
assert_approx_eq!(
actual.scale.dx,
$expected.scale.dx,
"decomposed scale dx didn't match input scale dx"
);
assert_approx_eq!(
actual.scale.dy,
$expected.scale.dy,
"decomposed scale dy didn't match input scale dy"
);
assert_approx_eq!(
actual.rotation.degrees(),
$expected.rotation.degrees(),
"decomposed angle didn't match input angle"
);
assert_approx_eq!(
actual.skew.degrees(),
$expected.skew.degrees(),
"decomposed skew didn't match input skew"
);
};
}
#[test]
fn decompose_translation() {
let translation = Vector::new(1.0, 2.0);
check_decomposition!(
Transform::from_translation_vector(translation),
DecomposedTransform {
translation,
..Default::default()
}
);
}
#[test]
fn decompose_rotation() {
let rotation = Angle::from_degrees(30.0);
check_decomposition!(
Transform::from_rotation(rotation),
DecomposedTransform {
rotation,
..Default::default()
}
);
}
#[test]
fn decompose_scale() {
let scale = Vector::new(2.0, 3.0);
check_decomposition!(
Transform::from_scale_vector(scale),
DecomposedTransform {
scale,
..Default::default()
}
);
}
#[test]
fn decompose_skew() {
let skew = Angle::from_degrees(30.0);
check_decomposition!(
Transform::from_skew(skew),
DecomposedTransform {
skew,
..Default::default()
}
);
}
macro_rules! check_recomposition {
($expected:expr) => {
let actual = Transform::from_decomposed($expected.decompose());
assert_approx_eq!(
actual.m11,
$expected.m11,
"output m11 didn't match input m11"
);
assert_approx_eq!(
actual.m12,
$expected.m12,
"output m12 didn't match input m12"
);
assert_approx_eq!(
actual.m21,
$expected.m21,
"output m21 didn't match input m21"
);
assert_approx_eq!(
actual.m22,
$expected.m22,
"output m22 didn't match input m22"
);
assert_approx_eq!(
actual.m31,
$expected.m31,
"output m31 didn't match input m31"
);
assert_approx_eq!(
actual.m32,
$expected.m32,
"output m32 didn't match input m32"
);
};
}
#[test]
fn recompose() {
check_recomposition!(Transform::row_major(1.0, 0.5, -0.5, 0.866025, 0.3, 0.6));
}
#[test]
fn recompose_reflection() {
check_recomposition!(Transform::row_major(1.0, 0.5, 0.5, -0.866025, 0.3, 0.6));
}
}