use num_traits::{Float, FloatConst};
use crate::geometry::{Dimension, Size};
use crate::linalg::{Quaternion3D, Transform3D};
use crate::physics::Angle;
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct TransformBuilder<T>
where
T: Float,
{
transforms: [Transform<T>; 8],
index: usize,
}
impl<T> TransformBuilder<T>
where
T: Float,
{
pub fn new() -> TransformBuilder<T>
where
T: Default,
{
Default::default()
}
pub fn push(
mut self,
transform: Transform<T>,
) -> Result<TransformBuilder<T>, TransformBuilder<T>> {
match self.index {
0 => self.transforms[0] = transform,
n => match self.transforms[n - 1].concat(transform) {
Some(transform) => {
self.transforms[n - 1] = transform;
return Ok(self);
}
None if n == self.transforms.len() => return Err(self),
None => self.transforms[n] = transform,
},
}
self.index += 1;
Ok(self)
}
pub fn len(&self) -> usize {
self.index
}
pub fn into_transforms(self) -> [Transform<T>; 8] {
self.transforms
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Transform<T>
where
T: Float,
{
pub matrix: Transform3D<T>,
pub relative_translation: (T, T),
}
impl<T> Transform<T>
where
T: Float,
{
pub fn with_transform(matrix: Transform3D<T>) -> Transform<T> {
Transform {
matrix,
..Default::default()
}
}
pub fn with_matrix(matrix: [T; 6]) -> Transform<T> {
let [a, b, c, d, tx, ty] = matrix;
let mut transform = Transform::default();
transform.matrix.columns[0][0] = a;
transform.matrix.columns[0][1] = b;
transform.matrix.columns[1][0] = c;
transform.matrix.columns[1][1] = d;
transform.matrix.columns[3][0] = tx;
transform.matrix.columns[3][1] = ty;
transform
}
pub fn with_matrix3d(matrix: [T; 16]) -> Transform<T> {
let [m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44] =
matrix;
let mut transform = Transform::default();
transform.matrix.columns[0] = [m11, m12, m13, m14];
transform.matrix.columns[1] = [m21, m22, m23, m24];
transform.matrix.columns[2] = [m31, m32, m33, m34];
transform.matrix.columns[3] = [m41, m42, m43, m44];
transform
}
pub fn with_perspective(d: T) -> Transform<T> {
Transform {
matrix: Transform3D::with_perspective(d),
..Default::default()
}
}
pub fn with_rotation(rx: T, ry: T, rz: T, angle: Angle<T>) -> Transform<T>
where
T: FloatConst,
{
let q = Quaternion3D::with_angle(angle.to_radians(), -rx, -ry, rz);
Transform {
matrix: Transform3D::with_rotation(q),
..Default::default()
}
}
pub fn with_scale(sx: T, sy: T, sz: T) -> Transform<T> {
Transform {
matrix: Transform3D::with_scale(sx, sy, sz),
..Default::default()
}
}
pub fn with_skew_x(sx: Angle<T>) -> Transform<T> {
Transform {
matrix: Transform3D::with_skew_x(sx.to_radians()),
..Default::default()
}
}
pub fn with_skew_y(sy: Angle<T>) -> Transform<T> {
Transform {
matrix: Transform3D::with_skew_y(sy.to_radians()),
..Default::default()
}
}
pub fn with_translation(tx: Dimension<T>, ty: Dimension<T>, tz: T) -> Transform<T> {
let (tx, rx) = match tx {
Dimension::Percentage(rx) => (T::zero(), rx),
Dimension::Points(tx) => (tx, T::zero()),
_ => (T::zero(), T::zero()),
};
let (ty, ry) = match ty {
Dimension::Percentage(ry) => (T::zero(), ry),
Dimension::Points(ty) => (ty, T::zero()),
_ => (T::zero(), T::zero()),
};
Transform {
matrix: Transform3D::with_translation(tx, ty, tz),
relative_translation: (rx, ry),
}
}
pub fn is_resolved(&self) -> bool {
let (tx, ty) = self.relative_translation;
tx.is_zero() && ty.is_zero()
}
pub fn resolve(&self, size: Size<T>) -> Transform3D<T> {
let (rtx, rty) = self.relative_translation;
self.matrix
.translate(rtx * size.width, rty * size.height, T::zero())
}
pub fn concat(&self, other: Transform<T>) -> Option<Transform<T>> {
match self.is_resolved() {
true => Some(Transform {
matrix: self.matrix.concat(other.matrix),
relative_translation: other.relative_translation,
}),
false => None,
}
}
pub fn squash(transforms: [Transform<T>; 8], size: Size<T>) -> Transform3D<T> {
transforms
.iter()
.fold(Transform3D::identity(), |current, transform| {
current.concat(transform.resolve(size))
})
}
}
impl<T> Default for Transform<T>
where
T: Float,
{
fn default() -> Self {
Transform {
matrix: Default::default(),
relative_translation: (T::zero(), T::zero()),
}
}
}
#[cfg(test)]
mod tests {
use super::{Angle, Dimension, Quaternion3D, Size, Transform, Transform3D, TransformBuilder};
#[test]
fn test_transform() {
assert_eq!(
Transform::with_rotation(1.0, 0.0, 0.0, Angle::with_degrees(45.0)).is_resolved(),
true
);
assert_eq!(Transform::with_scale(1.0, 2.0, 3.0).is_resolved(), true);
assert_eq!(
Transform::with_translation(Dimension::Points(10.0), Dimension::Points(20.0), 30.0)
.is_resolved(),
true
);
assert_eq!(
Transform::with_translation(Dimension::Percentage(10.0), Dimension::Points(20.0), 30.0)
.is_resolved(),
false
);
assert_eq!(
Transform::with_translation(Dimension::Points(10.0), Dimension::Percentage(20.0), 30.0)
.is_resolved(),
false
);
assert_eq!(
Transform::with_translation(
Dimension::Percentage(0.0),
Dimension::Percentage(0.0),
30.0
)
.is_resolved(),
true
);
}
#[test]
fn test_transform_builder() {
let builder = TransformBuilder::new()
.push(Transform::with_scale(2.0, 3.0, 1.0))
.unwrap()
.push(Transform::with_translation(
Dimension::Percentage(0.5),
Dimension::Percentage(0.3),
20.0,
))
.unwrap()
.push(Transform::with_rotation(
1.0,
0.0,
0.0,
Angle::with_degrees(45.0),
))
.unwrap();
assert_eq!(builder.len(), 2);
let resolved = Transform::squash(builder.into_transforms(), Size::new(320.0, 480.0));
assert_eq!(
resolved,
Transform3D::with_scale(2.0, 3.0, 1.0)
.translate(160.0, 144.0, 20.0)
.rotate(Quaternion3D::with_angle(
Angle::with_degrees(45.0).to_radians(),
-1.0,
0.0,
0.0
))
);
}
}