use std::fmt::Debug;
use std::ops::Mul;
use crate::math::{Bounds3, Mat4, Point3, Ray, Vec3};
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct Transform {
pub mat4: Mat4,
pub mat4_inverse: Mat4,
}
impl Transform {
pub fn new(mat4: Mat4, mat4_inverse: Mat4) -> Self {
Transform { mat4, mat4_inverse }
}
pub fn inverse(&self) -> Self {
Transform::new(self.mat4_inverse, self.mat4)
}
pub fn translate(offset: Vec3) -> Self {
#[rustfmt::skip]
let mat4 = Mat4::new(
1.0, 0.0, 0.0, offset.x,
0.0, 1.0, 0.0, offset.y,
0.0, 0.0, 1.0, offset.z,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
1.0, 0.0, 0.0, -offset.x,
0.0, 1.0, 0.0, -offset.y,
0.0, 0.0, 1.0, -offset.z,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn rotate_x(origin: Point3, angle: f64) -> Self {
let angle_sin = angle.sin();
let angle_cos = angle.cos();
#[rustfmt::skip]
let mat4 = Mat4::new(
1.0, 0.0, 0.0, 0.0,
0.0, angle_cos, -angle_sin, origin.y - angle_cos * origin.y + angle_sin * origin.z,
0.0, angle_sin, angle_cos, origin.z - angle_sin * origin.y - angle_cos * origin.z,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
1.0, 0.0, 0.0, 0.0,
0.0, angle_cos, angle_sin, origin.y - angle_cos * origin.y - angle_sin * origin.z,
0.0, -angle_sin, angle_cos, origin.z + angle_sin * origin.y - angle_cos * origin.z,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn rotate_y(origin: Point3, angle: f64) -> Self {
let angle_sin = angle.sin();
let angle_cos = angle.cos();
#[rustfmt::skip]
let mat4 = Mat4::new(
angle_cos, 0.0, angle_sin, origin.x - angle_cos * origin.x - angle_sin * origin.z,
0.0, 1.0, 0.0, 0.0,
-angle_sin, 0.0, angle_cos, origin.z + angle_sin * origin.x - angle_cos * origin.z,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
angle_cos, 0.0, -angle_sin, origin.x - angle_cos * origin.x + angle_sin * origin.z,
0.0, 1.0, 0.0, 0.0,
angle_sin, 0.0, angle_cos, origin.z - angle_sin * origin.x - angle_cos * origin.z,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn rotate_z(origin: Point3, angle: f64) -> Self {
let angle_sin = angle.sin();
let angle_cos = angle.cos();
#[rustfmt::skip]
let mat4 = Mat4::new(
angle_cos, -angle_sin, 0.0, origin.x - angle_cos * origin.x + angle_sin * origin.y,
angle_sin, angle_cos, 0.0, origin.y - angle_sin * origin.x - angle_cos * origin.y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
angle_cos, angle_sin, 0.0, origin.x - angle_cos * origin.x - angle_sin * origin.y,
-angle_sin, angle_cos, 0.0, origin.y + angle_sin * origin.x - angle_cos * origin.y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn rotate(origin: Point3, axis: Vec3, angle: f64) -> Self {
let n = axis.normalize();
let n_xx = n.x.powi(2);
let n_xy = n.x * n.y;
let n_xz = n.x * n.z;
let n_yy = n.y.powi(2);
let n_yz = n.y * n.z;
let n_zz = n.z.powi(2);
let sin = angle.sin();
let cos = angle.cos();
let a00 = n_xx + (1.0 - n_xx) * cos;
let a01 = n_xy * (1.0 - cos) - n.z * sin;
let a02 = n_xz * (1.0 - cos) + n.y * sin;
let a10 = n_xy * (1.0 - cos) + n.z * sin;
let a11 = n_yy + (1.0 - n_yy) * cos;
let a12 = n_yz * (1.0 - cos) - n.x * sin;
let a20 = n_xz * (1.0 - cos) - n.y * sin;
let a21 = n_yz * (1.0 - cos) + n.x * sin;
let a22 = n_zz + (1.0 - n_zz) * cos;
#[rustfmt::skip]
let mat4 = Mat4::new(
a00, a01, a02, origin.x - a00 * origin.x - a01 * origin.y - a02 * origin.z,
a10, a11, a12, origin.y - a10 * origin.x - a11 * origin.y - a12 * origin.z,
a20, a21, a22, origin.z - a20 * origin.x - a21 * origin.y - a22 * origin.z,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
a00, a10, a20, origin.x - a00 * origin.x - a10 * origin.y - a20 * origin.z,
a01, a11, a21, origin.y - a01 * origin.x - a11 * origin.y - a21 * origin.z,
a02, a12, a22, origin.z - a02 * origin.x - a12 * origin.y - a22 * origin.z,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn scale(origin: Point3, scale: Vec3) -> Self {
let scale_inverse = Vec3::new(1.0 / scale.x, 1.0 / scale.y, 1.0 / scale.z);
#[rustfmt::skip]
let mat4 = Mat4::new(
scale.x, 0.0, 0.0, (1.0 - scale.x) * origin.x,
0.0, scale.y, 0.0, (1.0 - scale.y) * origin.y,
0.0, 0.0, scale.z, (1.0 - scale.z) * origin.z,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
let mat4_inverse = Mat4::new(
scale_inverse.x, 0.0, 0.0, (1.0 - scale_inverse.x) * origin.x,
0.0, scale_inverse.y, 0.0, (1.0 - scale_inverse.y) * origin.y,
0.0, 0.0, scale_inverse.z, (1.0 - scale_inverse.z) * origin.z,
0.0, 0.0, 0.0, 1.0,
);
Transform { mat4, mat4_inverse }
}
pub fn look_at(origin: Point3, target: Point3, view_up: Vec3) -> Self {
let back = (origin - target).normalize();
let right = Vec3::cross(view_up, back).normalize();
let up = Vec3::cross(back, right);
#[rustfmt::skip]
let mat4 = Mat4::new(
right.x, up.x, back.x, origin.x,
right.y, up.y, back.y, origin.y,
right.z, up.z, back.z, origin.z,
0.0, 0.0, 0.0, 1.0,
);
Transform {
mat4,
mat4_inverse: mat4.inverse(),
}
}
pub fn perspective(fov: f64, near: f64, far: f64) -> Self {
#[rustfmt::skip]
let persp = Mat4::new(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, far / (far - near), -far * near / (far - near),
0.0, 0.0, 1.0, 0.0,
);
let inv_tan_ang = 1.0 / (fov * 0.5).tan();
Transform::scale(Point3::ZERO, Vec3::new(inv_tan_ang, inv_tan_ang, 1.0))
* Transform::new(persp, persp.inverse())
}
pub fn transform_ray(&self, ray: Ray) -> Ray {
Ray::new(
self.mat4 * ray.origin,
self.mat4 * ray.direction,
ray.last_ior,
)
}
pub fn inverse_transform_ray(&self, ray: Ray) -> Ray {
Ray::new(
self.mat4_inverse * ray.origin,
self.mat4_inverse * ray.direction,
ray.last_ior,
)
}
pub fn transform_bounds(&self, bounds: Bounds3) -> Bounds3 {
let min = self.mat4 * bounds.min;
let max = self.mat4 * bounds.max;
let mut bounds = Bounds3::new(
Point3::new(min.x, min.y, min.z),
Point3::new(max.x, min.y, min.z),
);
bounds = bounds.include_point(Point3::new(min.x, max.y, min.z));
bounds = bounds.include_point(Point3::new(min.x, min.y, max.y));
bounds = bounds.include_point(Point3::new(min.x, max.y, max.z));
bounds = bounds.include_point(Point3::new(max.x, max.y, max.z));
bounds = bounds.include_point(Point3::new(max.x, min.y, max.z));
bounds = bounds.include_point(Point3::new(max.x, max.y, max.z));
bounds
}
}
impl Transformable for Transform {
fn translate(mut self, translation: Vec3) -> Self {
let transform = Transform::translate(translation);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn rotate(mut self, origin: Point3, axis: Vec3, angle: f64) -> Self {
let transform = Transform::rotate(origin, axis, angle);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn rotate_x(mut self, angle: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
let transform = Transform::rotate_x(origin, angle);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn rotate_y(mut self, angle: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
let transform = Transform::rotate_y(origin, angle);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn rotate_z(mut self, angle: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
let transform = Transform::rotate_z(origin, angle);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn scale_x(self, factor: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
self.scale(origin, Vec3::new(factor, 1.0, 1.0))
}
fn scale_y(self, factor: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
self.scale(origin, Vec3::new(1.0, factor, 1.0))
}
fn scale_z(self, factor: f64) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
self.scale(origin, Vec3::new(1.0, 1.0, factor))
}
fn scale_xyz(self, scale: Vec3) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
self.scale(origin, scale)
}
fn scale(mut self, origin: Point3, scale: Vec3) -> Self {
let transform = Transform::scale(origin, scale);
self = Transform::new(
transform.mat4 * self.mat4,
self.mat4_inverse * transform.mat4_inverse,
);
self
}
fn look_at(mut self, target: Point3, view_up: Vec3) -> Self {
let mat4 = self.mat4;
let origin = Point3::new(mat4[0][3], mat4[1][3], mat4[2][3]);
self = Transform::look_at(origin, target, view_up);
self
}
}
impl Mul<Transform> for Transform {
type Output = Transform;
fn mul(self, rhs: Transform) -> Self::Output {
Transform {
mat4: self.mat4 * rhs.mat4,
mat4_inverse: rhs.mat4_inverse * self.mat4_inverse,
}
}
}
pub trait Transformable: Send + Sync {
fn translate(self, translation: Vec3) -> Self
where
Self: Sized;
fn rotate(self, origin: Point3, axis: Vec3, angle: f64) -> Self
where
Self: Sized;
fn rotate_x(self, angle: f64) -> Self
where
Self: Sized;
fn rotate_y(self, angle: f64) -> Self
where
Self: Sized;
fn rotate_z(self, angle: f64) -> Self
where
Self: Sized;
fn scale_x(self, factor: f64) -> Self
where
Self: Sized;
fn scale_y(self, factor: f64) -> Self
where
Self: Sized;
fn scale_z(self, factor: f64) -> Self
where
Self: Sized;
fn scale_xyz(self, scale: Vec3) -> Self
where
Self: Sized;
fn scale(self, origin: Point3, scale: Vec3) -> Self
where
Self: Sized;
fn look_at(self, target: Point3, view_up: Vec3) -> Self
where
Self: Sized;
}
#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
use crate::util::EPSILON_F64;
use super::{Mat4, Point3, Transform, Vec3};
#[test]
fn isometry_test() {
let vec = Vec3::new(4.2, 1.7, -2.22);
let phi = 1.215_f64;
let origin = Point3::new(7.32, 10.5, -2.0);
let rotate_x = Transform::rotate_x(origin, phi);
let rotate_y = Transform::rotate_y(origin, phi);
let rotate_z = Transform::rotate_z(origin, phi);
assert_approx_eq!(
(rotate_x.mat4 * vec).magnitude(),
vec.magnitude(),
EPSILON_F64
);
assert_approx_eq!(
(rotate_y.mat4 * vec).magnitude(),
vec.magnitude(),
EPSILON_F64
);
assert_approx_eq!(
(rotate_z.mat4 * vec).magnitude(),
vec.magnitude(),
EPSILON_F64
);
}
#[test]
fn associativity_test() {
let vec = Vec3::new(-1.5, 24.537, -4.12);
let phi: f64 = 4.217;
let origin = Point3::new(0.3, 1.4, 2.9);
let rotate_x = Transform::rotate_x(origin, phi);
let rotate_y = Transform::rotate_y(origin, phi);
let rotate_z = Transform::rotate_z(origin, phi);
assert_approx_eq!(
(rotate_x.mat4 * rotate_y.mat4 * rotate_z.mat4) * vec,
(rotate_x.mat4 * (rotate_y.mat4 * (rotate_z.mat4 * vec))),
EPSILON_F64
)
}
#[test]
fn inverse_test() {
let origin = Point3::new(6.3, -3.04, 1.0);
let translation = Transform::translate(Vec3::new(23.8, 12.0, -3.2));
let rotation = Transform::rotate(origin, Vec3::new(2.8, 0.0, -4.2), 1.0);
let rotation_x = Transform::rotate_x(origin, 7.4);
let rotation_y = Transform::rotate_y(origin, -2.1);
let rotation_z = Transform::rotate_z(origin, 8.2);
let scale = Transform::scale(origin, Vec3::new(9.81, -2.762, 1.0));
assert_approx_eq!(
translation.mat4 * translation.mat4_inverse,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
translation.mat4_inverse * translation.mat4,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation.mat4 * rotation.mat4_inverse,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation.mat4_inverse * rotation.mat4,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_x.mat4 * rotation_x.mat4_inverse,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_x.mat4_inverse * rotation_x.mat4,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_y.mat4 * rotation_y.mat4_inverse,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_y.mat4_inverse * rotation_y.mat4,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_z.mat4 * rotation_z.mat4_inverse,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(
rotation_z.mat4_inverse * rotation_z.mat4,
Mat4::IDENTITY,
EPSILON_F64
);
assert_approx_eq!(scale.mat4 * scale.mat4_inverse, Mat4::IDENTITY, EPSILON_F64);
assert_approx_eq!(scale.mat4_inverse * scale.mat4, Mat4::IDENTITY, EPSILON_F64);
}
#[test]
fn transpose_test() {
let origin = Point3::new(6.4, 3.5, 5.2);
let translation = Transform::translate(Vec3::new(23.8, 12.0, -3.2));
let rotation = Transform::rotate(origin, Vec3::new(2.8, 0.0, -4.2), 1.0);
let rotation_x = Transform::rotate_x(origin, 7.4);
let rotation_y = Transform::rotate_y(origin, -2.1);
let rotation_z = Transform::rotate_z(origin, 8.2);
let scale = Transform::scale(origin, Vec3::new(9.81, -2.762, 1.0));
assert_approx_eq!(
translation.mat4.transpose().transpose(),
translation.mat4,
EPSILON_F64
);
assert_approx_eq!(
rotation.mat4.transpose().transpose(),
rotation.mat4,
EPSILON_F64
);
assert_approx_eq!(
rotation_x.mat4.transpose().transpose(),
rotation_x.mat4,
EPSILON_F64
);
assert_approx_eq!(
rotation_y.mat4.transpose().transpose(),
rotation_y.mat4,
EPSILON_F64
);
assert_approx_eq!(
rotation_z.mat4.transpose().transpose(),
rotation_z.mat4,
EPSILON_F64
);
assert_approx_eq!(scale.mat4.transpose().transpose(), scale.mat4, EPSILON_F64);
assert_approx_eq!(
translation.mat4.transpose().inverse(),
translation.mat4.inverse().transpose(),
EPSILON_F64
);
}
}