use core::f64::consts::PI;
use core::ops;
use crate::base::{LineF, PointF, RectF};
use crate::util::{fuzzy_compare, fuzzy_is_zero};
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix {
m11: f64,
m12: f64,
m21: f64,
m22: f64,
dx: f64,
dy: f64,
}
impl Default for Matrix {
fn default() -> Self {
Self::identity()
}
}
impl Matrix {
#[must_use]
pub const fn new() -> Self {
Self::identity()
}
#[must_use]
pub const fn from(m11: f64, m12: f64, m21: f64, m22: f64, dx: f64, dy: f64) -> Self {
Self {
m11,
m12,
m21,
m22,
dx,
dy,
}
}
#[must_use]
pub const fn identity() -> Self {
Self {
m11: 1.0,
m12: 0.0,
m21: 0.0,
m22: 1.0,
dx: 0.0,
dy: 0.0,
}
}
#[must_use]
#[inline]
pub const fn m11(&self) -> f64 {
self.m11
}
#[must_use]
#[inline]
pub const fn m12(&self) -> f64 {
self.m12
}
#[must_use]
#[inline]
pub const fn m21(&self) -> f64 {
self.m21
}
#[must_use]
#[inline]
pub const fn m22(&self) -> f64 {
self.m22
}
#[must_use]
pub fn determinant(&self) -> f64 {
self.m11 * self.m22 - self.m12 * self.m21
}
#[must_use]
#[inline]
pub const fn dx(&self) -> f64 {
self.dx
}
#[must_use]
#[inline]
pub const fn dy(&self) -> f64 {
self.dy
}
#[must_use]
pub fn inverted(&self) -> (Self, bool) {
let dtr = self.determinant();
if dtr == 0.0 {
(Self::new(), false)
} else {
let dinv = 1.0 / dtr;
let matrix = Self::from(
self.m22 * dinv,
-self.m12 * dinv,
-self.m21 * dinv,
self.m11 * dinv,
(self.m21 * self.dy - self.m22 * self.dx) * dinv,
(self.m12 * self.dx - self.m11 * self.dy) * dinv,
);
(matrix, true)
}
}
#[must_use]
pub fn is_identity(&self) -> bool {
fuzzy_is_zero(self.m11 - 1.0)
&& fuzzy_is_zero(self.m22 - 1.0)
&& fuzzy_is_zero(self.m12)
&& fuzzy_is_zero(self.m21)
&& fuzzy_is_zero(self.dx)
&& fuzzy_is_zero(self.dy)
}
#[must_use]
pub fn is_invertible(&self) -> bool {
!fuzzy_is_zero(self.m11 * self.m22 - self.m12 * self.m21)
}
#[must_use]
pub fn map(&self, x: f64, y: f64) -> (f64, f64) {
let nx = self.m11.mul_add(x, self.m21 * y) + self.dx;
let ny = self.m12.mul_add(x, self.m22 * y) + self.dy;
(nx, ny)
}
#[must_use]
pub fn map_point(&self, point: PointF) -> PointF {
let (nx, ny) = self.map(point.x(), point.y());
PointF::from(nx, ny)
}
#[must_use]
pub fn map_line(&self, line: &LineF) -> LineF {
let p1 = self.map_point(line.p1());
let p2 = self.map_point(line.p2());
LineF::from_points(p1, p2)
}
#[must_use]
#[allow(clippy::similar_names)]
pub fn map_rect(&self, rect: &RectF) -> RectF {
if self.m12 == 0.0 && self.m21 == 0.0 {
let mut x = self.m11.mul_add(rect.x(), self.dx);
let mut y = self.m22.mul_add(rect.y(), self.dy);
let mut w = self.m11 * rect.width();
let mut h = self.m22 * rect.height();
if w < 0.0 {
w = -w;
x -= w;
}
if h < 0.0 {
h = -h;
y -= h;
}
RectF::from(x, y, w, h)
} else {
let (x0, y0) = self.map(rect.x(), rect.y());
let mut xmin = x0;
let mut ymin = y0;
let mut xmax = x0;
let mut ymax = y0;
let (x, y) = self.map(rect.x() + rect.width(), rect.y());
xmin = xmin.min(x);
ymin = ymin.min(y);
xmax = xmax.max(x);
ymax = ymax.max(y);
let (x, y) = self.map(rect.x() + rect.width(), rect.y() + rect.height());
xmin = xmin.min(x);
ymin = ymin.min(y);
xmax = xmax.max(x);
ymax = ymax.max(y);
let (x, y) = self.map(rect.x(), rect.y() + rect.height());
xmin = xmin.min(x);
ymin = ymin.min(y);
xmax = xmax.max(x);
ymax = ymax.max(y);
RectF::from(xmin, ymin, xmax - xmin, ymax - ymin)
}
}
pub fn reset(&mut self) {
self.m11 = 1.0;
self.m12 = 0.0;
self.m21 = 0.0;
self.m22 = 1.0;
self.dx = 0.0;
self.dy = 0.0;
}
pub fn rotate(&mut self, degree: f64) -> &mut Self {
const DEG2RAD: f64 = PI / 180.0;
let mut sina = 0.0;
let mut cosa = 0.0;
if fuzzy_compare(degree, 90.0) || fuzzy_compare(degree, -270.0) {
sina = 1.0;
} else if fuzzy_compare(degree, 270.0) || fuzzy_compare(degree, -90.0) {
sina = -1.0;
} else if fuzzy_compare(degree, 180.0) {
cosa = -1.0;
} else {
let b = DEG2RAD * degree;
sina = b.sin();
cosa = b.cos();
}
let tm11 = cosa.mul_add(self.m11, sina * self.m21);
let tm12 = cosa.mul_add(self.m12, sina * self.m22);
let tm21 = (-sina).mul_add(self.m11, cosa * self.m21);
let tm22 = (-sina).mul_add(self.m12, cosa * self.m22);
self.m11 = tm11;
self.m12 = tm12;
self.m21 = tm21;
self.m22 = tm22;
self
}
pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
self.m11 *= sx;
self.m12 *= sx;
self.m21 *= sy;
self.m22 *= sy;
self
}
pub fn set_matrix(&mut self, m11: f64, m12: f64, m21: f64, m22: f64, dx: f64, dy: f64) {
self.m11 = m11;
self.m12 = m12;
self.m21 = m21;
self.m22 = m22;
self.dx = dx;
self.dy = dy;
}
pub fn shear(&mut self, sh: f64, sv: f64) -> &mut Self {
let tm11 = sv * self.m21;
let tm12 = sv * self.m22;
let tm21 = sh * self.m11;
let tm22 = sh * self.m12;
self.m11 += tm11;
self.m12 += tm12;
self.m21 += tm21;
self.m22 += tm22;
self
}
pub fn translate(&mut self, dx: f64, dy: f64) -> &mut Self {
self.dx += dx.mul_add(self.m11, dy * self.m21);
self.dy += dy.mul_add(self.m22, dx * self.m12);
self
}
#[must_use]
pub fn fuzzy_compare(&self, other: &Self) -> bool {
fuzzy_compare(self.m11, other.m11)
&& fuzzy_compare(self.m12, other.m12)
&& fuzzy_compare(self.m21, other.m21)
&& fuzzy_compare(self.m22, other.m22)
&& fuzzy_compare(self.dx, other.dx)
&& fuzzy_compare(self.dy, other.dy)
}
}
impl ops::Mul<&Matrix> for &Matrix {
type Output = Matrix;
#[allow(clippy::similar_names)]
fn mul(self, m: &Matrix) -> Self::Output {
let tm11 = self.m11.mul_add(m.m11, self.m12 * m.m21);
let tm12 = self.m11.mul_add(m.m12, self.m12 * m.m22);
let tm21 = self.m21.mul_add(m.m11, self.m22 * m.m21);
let tm22 = self.m21.mul_add(m.m12, self.m22 * m.m22);
let tdx = self.dx.mul_add(m.m11, self.dy * m.m21) + m.dx;
let tdy = self.dx.mul_add(m.m12, self.dy * m.m22) + m.dy;
Matrix::from(tm11, tm12, tm21, tm22, tdx, tdy)
}
}
impl ops::MulAssign<&Self> for Matrix {
#[allow(clippy::similar_names)]
fn mul_assign(&mut self, m: &Self) {
let tm11 = self.m11.mul_add(m.m11, self.m12 * m.m21);
let tm12 = self.m11.mul_add(m.m12, self.m12 * m.m22);
let tm21 = self.m21.mul_add(m.m11, self.m22 * m.m21);
let tm22 = self.m21.mul_add(m.m12, self.m22 * m.m22);
let tdx = self.dx.mul_add(m.m11, self.dy * m.m21) + m.dx;
let tdy = self.dx.mul_add(m.m12, self.dy * m.m22) + m.dy;
self.m11 = tm11;
self.m12 = tm12;
self.m21 = tm21;
self.m22 = tm22;
self.dx = tdx;
self.dy = tdy;
}
}
impl ops::Mul<&Matrix> for PointF {
type Output = Self;
fn mul(self, matrix: &Matrix) -> Self::Output {
matrix.map_point(self)
}
}
impl ops::Mul<&Matrix> for &LineF {
type Output = LineF;
fn mul(self, matrix: &Matrix) -> Self::Output {
matrix.map_line(self)
}
}