use std::{
fmt::{Debug, Display},
ops::{Mul, MulAssign},
};
use embed_doc_image::embed_doc_image;
use crate::geom::Vector2d;
#[derive(Copy, Clone)]
#[embed_doc_image("matrix_props1.jpg", "src/geom/docs/assets/matrix_props1.jpg")]
#[embed_doc_image("matrix_props2.jpg", "src/geom/docs/assets/matrix_props2.jpg")]
#[embed_doc_image("matrix_translate.jpg", "src/geom/docs/assets/matrix_translate.jpg")]
#[embed_doc_image("matrix_translate_image.jpg", "src/geom/docs/assets/matrix_translate_image.jpg")]
#[embed_doc_image("matrix_scale.jpg", "src/geom/docs/assets/matrix_scale.jpg")]
#[embed_doc_image("matrix_scale_image.jpg", "src/geom/docs/assets/matrix_scale_image.jpg")]
#[embed_doc_image("matrix_rotate.jpg", "src/geom/docs/assets/matrix_rotate.jpg")]
#[embed_doc_image("matrix_rotate_image.jpg", "src/geom/docs/assets/matrix_rotate_image.jpg")]
#[embed_doc_image("matrix_skew.jpg", "src/geom/docs/assets/matrix_skew.jpg")]
#[embed_doc_image("matrix_skew_image.jpg", "src/geom/docs/assets/matrix_skew_image.jpg")]
pub struct Matrix2d {
a: f64,
b: f64,
c: f64,
d: f64,
tx: f64,
ty: f64,
}
impl Debug for Matrix2d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl Display for Matrix2d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(a={}, b={}, c={}, d={}, tx={}, ty={})", self.a(), self.b(), self.c(), self.d(), self.tx(), self.ty())
}
}
impl Default for Matrix2d {
#[embed_doc_image("matrix_identity.jpg", "src/geom/docs/assets/matrix_identity.jpg")]
fn default() -> Self {
Matrix2d { a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0 }
}
}
impl Matrix2d {
pub fn new(a: f64, b: f64, c: f64, d: f64, tx: f64, ty: f64) -> Self {
Self { a, b, c, d, tx, ty }
}
pub fn a(&self) -> f64 {
self.a
}
pub fn set_a(&mut self, value: f64) {
self.a = value;
}
pub fn b(&self) -> f64 {
self.b
}
pub fn set_b(&mut self, value: f64) {
self.b = value;
}
pub fn c(&self) -> f64 {
self.c
}
pub fn set_c(&mut self, value: f64) {
self.c = value;
}
pub fn d(&self) -> f64 {
self.d
}
pub fn set_d(&mut self, value: f64) {
self.d = value;
}
pub fn tx(&self) -> f64 {
self.tx
}
pub fn set_tx(&mut self, value: f64) {
self.tx = value;
}
pub fn ty(&self) -> f64 {
self.ty
}
pub fn set_ty(&mut self, value: f64) {
self.ty = value;
}
pub fn create_box(&mut self, scale: &Vector2d, rotation_radians: f64, translation: &Vector2d) {
self.identity();
self.rotate(rotation_radians);
self.scale(&scale);
self.translate(&translation);
}
#[embed_doc_image("create_gradient_box_1.jpg", "src/geom/docs/assets/create_gradient_box_1.jpg")]
#[embed_doc_image("create_gradient_box_2.jpg", "src/geom/docs/assets/create_gradient_box_2.jpg")]
#[embed_doc_image("create_gradient_box_3.jpg", "src/geom/docs/assets/create_gradient_box_3.jpg")]
#[embed_doc_image("create_gradient_box_4.jpg", "src/geom/docs/assets/create_gradient_box_4.jpg")]
pub fn create_gradient_box(&mut self, size: &Vector2d, rotation_radians: f64, translation: &Vector2d) {
self.identity();
self.rotate(rotation_radians);
self.scale(&size);
self.translate(&((*size / 2.0) + *translation));
}
pub fn delta_transform_point(&mut self, point: &Vector2d) -> Vector2d {
Vector2d(
self.a() * point.x() + self.c() * point.y(),
self.b() * point.x() + self.d() * point.y(),
)
}
#[embed_doc_image("matrix_identity.jpg", "src/geom/docs/assets/matrix_identity.jpg")]
pub fn identity(&mut self) {
self.set_a(1.0);
self.set_b(0.0);
self.set_c(0.0);
self.set_d(1.0);
self.set_tx(0.0);
self.set_ty(0.0);
}
pub fn invert(&mut self) {
let norm = self.a() * self.d() - self.b() * self.c();
if norm == 0.0 {
self.set_a(0.0);
self.set_b(0.0);
self.set_c(0.0);
self.set_d(0.0);
self.set_tx(-self.tx());
self.set_ty(-self.ty());
} else {
let norm = 1.0 / norm;
let a1 = self.d() * norm;
self.set_d(self.a() * norm);
self.set_a(a1);
self.set_b(self.b() * -norm);
self.set_c(self.c() * -norm);
let tx1 = -self.a() * self.tx() - self.c() * self.ty();
self.set_ty(-self.b() * self.tx() - self.d() * self.ty());
self.set_tx(tx1);
}
}
#[embed_doc_image("matrix_rotate.jpg", "src/geom/docs/assets/matrix_rotate.jpg")]
pub fn rotate(&mut self, rotation_radians: f64) {
let cos = f64::cos(rotation_radians);
let sin = f64::sin(rotation_radians);
let new_a = self.a() * cos - self.b() * sin;
let new_b = self.a() * sin + self.b() * cos;
let new_c = self.c() * cos - self.d() * sin;
let new_d = self.c() * sin + self.d() * cos;
let new_tx = self.tx() * cos - self.ty() * sin;
let new_ty = self.tx() * sin + self.ty() * cos;
self.set_a(new_a);
self.set_b(new_b);
self.set_c(new_c);
self.set_d(new_d);
self.set_tx(new_tx);
self.set_ty(new_ty);
}
#[embed_doc_image("matrix_scale.jpg", "src/geom/docs/assets/matrix_scale.jpg")]
pub fn scale(&mut self, scale: &Vector2d) {
let new_a = self.a() * scale.x();
let new_b = self.b() * scale.y();
let new_c = self.c() * scale.x();
let new_d = self.d() * scale.y();
let new_tx = self.tx() * scale.x();
let new_ty = self.ty() * scale.y();
self.set_a(new_a);
self.set_b(new_b);
self.set_c(new_c);
self.set_d(new_d);
self.set_tx(new_tx);
self.set_ty(new_ty);
}
pub fn transform_point(&mut self, point: &Vector2d) -> Vector2d {
self.delta_transform_point(point) + Vector2d(self.tx, self.ty)
}
pub fn translate(&mut self, translation: &Vector2d) {
self.set_tx(self.tx() + translation.x());
self.set_ty(self.ty() + translation.y());
}
}
impl Mul for Matrix2d {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self::new(self.a() * rhs.a(), self.b() * rhs.b(), self.c() * rhs.c(), self.d() * rhs.d(), self.tx() * rhs.tx(), self.ty() * rhs.ty())
}
}
impl MulAssign for Matrix2d {
fn mul_assign(&mut self, rhs: Self) {
self.set_a(self.a() * rhs.a());
self.set_b(self.b() * rhs.b());
self.set_c(self.c() * rhs.c());
self.set_d(self.d() * rhs.d());
self.set_tx(self.tx() * rhs.tx());
self.set_ty(self.ty() * rhs.ty());
}
}
#[cfg(test)]
mod tests {
use crate::geom::*;
use std::f64::consts::PI;
#[test]
fn test_transform_point() {
let mut matrix = Matrix2d::default();
matrix.identity();
matrix.rotate(PI / 4.0);
matrix.scale(&Vector2d(2.0, 2.0));
matrix.translate(&Vector2d(10.0, 20.0));
println!(
"matrix\n\
- Got: {matrix}\n\
- Expected approximation = (a=1.4142135623730951, b=1.414213562373095, c=-1.414213562373095, d=1.4142135623730951, tx=10, ty=20)"
);
println!(
"matrix.delta_transform_point(&Vector2d(0.0, 0.0))\n\
- Got: {}\n\
- Expected approximation: (x=0, y=0)",
matrix.delta_transform_point(&Vector2d(0.0, 0.0))
);
println!(
"matrix.delta_transform_point(&Vector2d(1.0, 1.0))\n\
- Got: {}\n\
- Expected approximation: (x=2.220446049250313e-16, y=2.82842712474619)",
matrix.delta_transform_point(&Vector2d(1.0, 1.0))
);
println!(
"matrix.transform_point(&Vector2d(0.0, 0.0))\n\
- Got: {}\n\
- Expected approximation: (x=10, y=20)",
matrix.transform_point(&Vector2d(0.0, 0.0))
);
println!(
"matrix.transform_point(&Vector2d(1.0, 1.0))\n\
- Got: {}\n\
- Expected approximation: (x=10, y=22.82842712474619)",
matrix.transform_point(&Vector2d(1.0, 1.0))
);
println!(
"matrix.transform_point(&Vector2d(128.0, 56.0))\n\
- Got: {}\n\
- Expected approximation: (x=111.82337649086287, y=280.2152954766495)",
matrix.transform_point(&Vector2d(128.0, 56.0))
);
}
#[test]
fn test_invert() {
let mut matrix = Matrix2d::default();
matrix.identity();
matrix.rotate(PI / 4.0);
matrix.scale(&Vector2d(2.0, 2.0));
matrix.translate(&Vector2d(10.0, 20.0));
matrix.invert();
println!(
"matrix\n\
- Got: {matrix}\n\
- Expected approximation = (a=0.3535533905932738, b=-0.35355339059327373, c=0.35355339059327373, d=0.3535533905932738, tx=-10.606601717798213, ty=-3.535533905932738)"
);
}
}