use std::ops::{Add, Mul};
use crate::{Matrix4, Offset};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vector3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vector3 {
pub const fn new(x: f32, y: f32, z: f32) -> Vector3 {
Vector3 { x, y, z }
}
pub const ZERO: Vector3 = Vector3::new(0.0, 0.0, 0.0);
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
}
impl Add for Vector3 {
type Output = Vector3;
fn add(self, other: Vector3) -> Vector3 {
Vector3::new(self.x + other.x, self.y + other.y, self.z + other.z)
}
}
impl Mul<f32> for Vector3 {
type Output = Vector3;
fn mul(self, scale: f32) -> Vector3 {
Vector3::new(self.x * scale, self.y * scale, self.z * scale)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Quaternion {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
impl Quaternion {
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Quaternion {
Quaternion { x, y, z, w }
}
pub const IDENTITY: Quaternion = Quaternion::new(0.0, 0.0, 0.0, 1.0);
pub fn length(&self) -> f32 {
self.length2().sqrt()
}
pub fn length2(&self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
}
pub fn scaled(&self, scale: f32) -> Quaternion {
Quaternion::new(
self.x * scale,
self.y * scale,
self.z * scale,
self.w * scale,
)
}
pub fn normalized(&self) -> Quaternion {
let length = self.length();
if length == 0.0 {
return *self;
}
self.scaled(1.0 / length)
}
}
impl Default for Quaternion {
fn default() -> Quaternion {
Quaternion::IDENTITY
}
}
impl Add for Quaternion {
type Output = Quaternion;
fn add(self, other: Quaternion) -> Quaternion {
Quaternion::new(
self.x + other.x,
self.y + other.y,
self.z + other.z,
self.w + other.w,
)
}
}
pub fn transform3(matrix: Matrix4, point: Offset) -> Offset {
let m = matrix.to_flutter_array();
let (x, y) = (point.dx() as f32, point.dy() as f32);
Offset::new(
(m[0] * x + m[4] * y + m[12]) as f64,
(m[1] * x + m[5] * y + m[13]) as f64,
)
}
pub fn decompose(matrix: Matrix4) -> (Vector3, Quaternion, Vector3) {
let m = matrix.to_flutter_array();
let mut sx = Vector3::new(m[0], m[1], m[2]).length();
let sy = Vector3::new(m[4], m[5], m[6]).length();
let sz = Vector3::new(m[8], m[9], m[10]).length();
if determinant(&m) < 0.0 {
sx = -sx;
}
let translation = Vector3::new(m[12], m[13], m[14]);
let inv_sx = 1.0 / sx;
let inv_sy = 1.0 / sy;
let inv_sz = 1.0 / sz;
let rotation_matrix = [
m[0] * inv_sx,
m[1] * inv_sx,
m[2] * inv_sx,
m[4] * inv_sy,
m[5] * inv_sy,
m[6] * inv_sy,
m[8] * inv_sz,
m[9] * inv_sz,
m[10] * inv_sz,
];
let rotation = quaternion_from_rotation(&rotation_matrix);
(translation, rotation, Vector3::new(sx, sy, sz))
}
pub fn compose(translation: Vector3, rotation: Quaternion, scale: Vector3) -> Matrix4 {
let (x, y, z, w) = (rotation.x, rotation.y, rotation.z, rotation.w);
let (x2, y2, z2) = (x + x, y + y, z + z);
let (xx, xy, xz) = (x * x2, x * y2, x * z2);
let (yy, yz, zz) = (y * y2, y * z2, z * z2);
let (wx, wy, wz) = (w * x2, w * y2, w * z2);
let mut m = [
1.0 - (yy + zz),
xy + wz,
xz - wy,
0.0,
xy - wz,
1.0 - (xx + zz),
yz + wx,
0.0,
xz + wy,
yz - wx,
1.0 - (xx + yy),
0.0,
translation.x,
translation.y,
translation.z,
1.0,
];
for (column, factor) in [scale.x, scale.y, scale.z, 1.0].into_iter().enumerate() {
for row in 0..4 {
m[column * 4 + row] *= factor;
}
}
Matrix4::from_flutter_array(&m)
}
fn determinant(m: &[f32; 16]) -> f32 {
let det2_01_01 = m[0] * m[5] - m[1] * m[4];
let det2_01_02 = m[0] * m[6] - m[2] * m[4];
let det2_01_03 = m[0] * m[7] - m[3] * m[4];
let det2_01_12 = m[1] * m[6] - m[2] * m[5];
let det2_01_13 = m[1] * m[7] - m[3] * m[5];
let det2_01_23 = m[2] * m[7] - m[3] * m[6];
let det3_201_012 = m[8] * det2_01_12 - m[9] * det2_01_02 + m[10] * det2_01_01;
let det3_201_013 = m[8] * det2_01_13 - m[9] * det2_01_03 + m[11] * det2_01_01;
let det3_201_023 = m[8] * det2_01_23 - m[10] * det2_01_03 + m[11] * det2_01_02;
let det3_201_123 = m[9] * det2_01_23 - m[10] * det2_01_13 + m[11] * det2_01_12;
-det3_201_123 * m[12] + det3_201_023 * m[13] - det3_201_013 * m[14] + det3_201_012 * m[15]
}
fn quaternion_from_rotation(m: &[f32; 9]) -> Quaternion {
fn index(row: usize, col: usize) -> usize {
col * 3 + row
}
let trace = m[0] + m[4] + m[8];
let mut q = [0.0f32; 4];
if trace > 0.0 {
let mut s = (trace + 1.0).sqrt();
q[3] = s * 0.5;
s = 0.5 / s;
q[0] = (m[5] - m[7]) * s;
q[1] = (m[6] - m[2]) * s;
q[2] = (m[1] - m[3]) * s;
} else {
let i = if m[0] < m[4] {
if m[4] < m[8] { 2 } else { 1 }
} else if m[0] < m[8] {
2
} else {
0
};
let j = (i + 1) % 3;
let k = (i + 2) % 3;
let mut s = (m[index(i, i)] - m[index(j, j)] - m[index(k, k)] + 1.0).sqrt();
q[i] = s * 0.5;
s = 0.5 / s;
q[3] = (m[index(k, j)] - m[index(j, k)]) * s;
q[j] = (m[index(j, i)] + m[index(i, j)]) * s;
q[k] = (m[index(k, i)] + m[index(i, k)]) * s;
}
Quaternion::new(q[0], q[1], q[2], q[3])
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_close(a: Matrix4, b: Matrix4) {
let (a, b) = (a.to_flutter_array(), b.to_flutter_array());
for (index, (a, b)) in a.iter().zip(b.iter()).enumerate() {
assert!((a - b).abs() < 1e-5, "entry {index}: {a} vs {b}");
}
}
#[test]
fn decompose_reads_back_a_translation_and_a_scale() {
let matrix = Matrix4::translation(3.0, 4.0).then(&Matrix4::scale(2.0, 5.0));
let (translation, rotation, scale) = decompose(matrix);
assert_eq!(translation, Vector3::new(3.0, 4.0, 0.0));
assert_eq!(rotation, Quaternion::IDENTITY);
assert_eq!(scale, Vector3::new(2.0, 5.0, 1.0));
}
#[test]
fn compose_inverts_decompose() {
let matrix = Matrix4::translation(3.0, 4.0)
.then(&Matrix4::rotation(0.7))
.then(&Matrix4::scale(2.0, 5.0));
let (translation, rotation, scale) = decompose(matrix);
assert_close(compose(translation, rotation, scale), matrix);
}
#[test]
fn a_rotation_round_trips_through_a_quaternion() {
let matrix = Matrix4::rotation(std::f32::consts::FRAC_PI_3);
let (translation, rotation, scale) = decompose(matrix);
assert!((rotation.length() - 1.0).abs() < 1e-6);
assert_close(compose(translation, rotation, scale), matrix);
}
#[test]
fn a_mirrored_matrix_decomposes_with_a_negative_x_scale() {
let matrix = Matrix4::scale(-2.0, 3.0);
let (_, _, scale) = decompose(matrix);
assert_eq!(scale.x, -2.0);
assert_eq!(scale.y, 3.0);
}
}