From e0ee0d6dffcff3969ca9f1d5fad54dd4c97408f1 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 14 May 2021 13:30:28 -0700 Subject: [PATCH] Change to using a dedicated affine transform type. This lets certain operations, especially matrix inversion, be quite a bit faster. And we don't need anything beyond affine transformations anyway. --- src/bbox.rs | 6 +- src/camera.rs | 6 +- src/lerp.rs | 28 ++-- src/light/mod.rs | 4 +- src/light/rectangle_light.rs | 10 +- src/light/sphere_light.rs | 8 +- src/math.rs | 2 +- src/parse/psy.rs | 11 +- src/ray.rs | 4 +- src/scene/assembly.rs | 12 +- src/surface/micropoly_batch.rs | 6 +- src/surface/mod.rs | 6 +- src/surface/triangle_mesh.rs | 6 +- src/tracer.rs | 6 +- src/transform_stack.rs | 10 +- sub_crates/math3d/src/lib.rs | 4 +- sub_crates/math3d/src/matrix.rs | 201 ----------------------------- sub_crates/math3d/src/normal.rs | 23 ++-- sub_crates/math3d/src/point.rs | 46 ++----- sub_crates/math3d/src/transform.rs | 178 +++++++++++++++++++++++++ sub_crates/math3d/src/vector.rs | 16 +-- 21 files changed, 275 insertions(+), 318 deletions(-) delete mode 100644 sub_crates/math3d/src/matrix.rs create mode 100644 sub_crates/math3d/src/transform.rs diff --git a/src/bbox.rs b/src/bbox.rs index 12e3212..f4a2ab6 100644 --- a/src/bbox.rs +++ b/src/bbox.rs @@ -7,7 +7,7 @@ use std::{ use crate::{ lerp::{lerp, lerp_slice, Lerp}, - math::{fast_minf32, Matrix4x4, Point, Vector}, + math::{fast_minf32, Point, Transform, Vector}, }; const BBOX_MAXT_ADJUST: f32 = 1.000_000_24; @@ -55,7 +55,7 @@ impl BBox { } // Creates a new BBox transformed into a different space. - pub fn transformed(&self, xform: Matrix4x4) -> BBox { + pub fn transformed(&self, xform: Transform) -> BBox { // BBox corners let vs = [ Point::new(self.min.x(), self.min.y(), self.min.z()), @@ -150,7 +150,7 @@ impl Lerp for BBox { } } -pub fn transform_bbox_slice_from(bbs_in: &[BBox], xforms: &[Matrix4x4], bbs_out: &mut Vec) { +pub fn transform_bbox_slice_from(bbs_in: &[BBox], xforms: &[Transform], bbs_out: &mut Vec) { bbs_out.clear(); // Transform the bounding boxes diff --git a/src/camera.rs b/src/camera.rs index 296bb3d..788e207 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -4,14 +4,14 @@ use kioku::Arena; use crate::{ lerp::lerp_slice, - math::{Matrix4x4, Point, Vector}, + math::{Point, Transform, Vector}, ray::Ray, sampling::square_to_circle, }; #[derive(Copy, Clone, Debug)] pub struct Camera<'a> { - transforms: &'a [Matrix4x4], + transforms: &'a [Transform], fovs: &'a [f32], tfovs: &'a [f32], aperture_radii: &'a [f32], @@ -21,7 +21,7 @@ pub struct Camera<'a> { impl<'a> Camera<'a> { pub fn new( arena: &'a Arena, - transforms: &[Matrix4x4], + transforms: &[Transform], fovs: &[f32], mut aperture_radii: &[f32], mut focus_distances: &[f32], diff --git a/src/lerp.rs b/src/lerp.rs index 6abe117..65a6479 100644 --- a/src/lerp.rs +++ b/src/lerp.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use math3d::{Matrix4x4, Normal, Point, Vector}; +use math3d::{Normal, Point, Transform, Vector}; /// Trait for allowing a type to be linearly interpolated. pub trait Lerp: Copy { @@ -106,8 +106,8 @@ impl Lerp for glam::Vec4 { } } -impl Lerp for Matrix4x4 { - fn lerp(self, other: Matrix4x4, alpha: f32) -> Matrix4x4 { +impl Lerp for Transform { + fn lerp(self, other: Transform, alpha: f32) -> Transform { (self * (1.0 - alpha)) + (other * alpha) } } @@ -215,23 +215,21 @@ mod tests { #[test] fn lerp_matrix() { - let a = Matrix4x4::new_from_values( - 0.0, 2.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, + let a = Transform::new_from_values( + 0.0, 2.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, ); - let b = Matrix4x4::new_from_values( - -1.0, 1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + let b = Transform::new_from_values( + -1.0, 1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, ); - let c1 = Matrix4x4::new_from_values( - -0.25, 1.75, 2.25, 3.25, 4.25, 5.25, 6.25, 7.25, 8.25, 9.25, 10.25, 11.25, 12.25, - 13.25, 14.25, 15.25, + let c1 = Transform::new_from_values( + -0.25, 1.75, 2.25, 3.25, 4.25, 5.25, 6.25, 7.25, 8.25, 9.25, 10.25, 11.25, ); - let c2 = Matrix4x4::new_from_values( - -0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + let c2 = Transform::new_from_values( + -0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, ); - let c3 = Matrix4x4::new_from_values( - -0.75, 1.25, 2.75, 3.75, 4.75, 5.75, 6.75, 7.75, 8.75, 9.75, 10.75, 11.75, 12.75, - 13.75, 14.75, 15.75, + let c3 = Transform::new_from_values( + -0.75, 1.25, 2.75, 3.75, 4.75, 5.75, 6.75, 7.75, 8.75, 9.75, 10.75, 11.75, ); assert_eq!(a.lerp(b, 0.0), a); diff --git a/src/light/mod.rs b/src/light/mod.rs index e38f53a..a45c567 100644 --- a/src/light/mod.rs +++ b/src/light/mod.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; use crate::{ color::SpectralSample, - math::{Matrix4x4, Normal, Point, Vector}, + math::{Normal, Point, Transform, Vector}, surface::Surface, }; @@ -34,7 +34,7 @@ pub trait SurfaceLight: Surface { /// - The pdf of the sample. fn sample_from_point( &self, - space: &Matrix4x4, + space: &Transform, arr: Point, u: f32, v: f32, diff --git a/src/light/rectangle_light.rs b/src/light/rectangle_light.rs index 4da96e1..5460dfc 100644 --- a/src/light/rectangle_light.rs +++ b/src/light/rectangle_light.rs @@ -5,7 +5,7 @@ use crate::{ boundable::Boundable, color::{Color, SpectralSample}, lerp::lerp_slice, - math::{cross, dot, Matrix4x4, Normal, Point, Vector}, + math::{cross, dot, Normal, Point, Transform, Vector}, ray::{RayBatch, RayStack}, sampling::{ spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle, @@ -51,7 +51,7 @@ impl<'a> RectangleLight<'a> { // more efficiently by inlining it there. fn sample_pdf( &self, - space: &Matrix4x4, + space: &Transform, arr: Point, sample_dir: Vector, hit_point: Point, @@ -97,7 +97,7 @@ impl<'a> RectangleLight<'a> { // fn outgoing( // &self, - // space: &Matrix4x4, + // space: &Transform, // dir: Vector, // u: f32, // v: f32, @@ -120,7 +120,7 @@ impl<'a> RectangleLight<'a> { impl<'a> SurfaceLight for RectangleLight<'a> { fn sample_from_point( &self, - space: &Matrix4x4, + space: &Transform, arr: Point, u: f32, v: f32, @@ -261,7 +261,7 @@ impl<'a> Surface for RectangleLight<'a> { ray_stack: &mut RayStack, isects: &mut [SurfaceIntersection], shader: &dyn SurfaceShader, - space: &[Matrix4x4], + space: &[Transform], ) { let _ = shader; // Silence 'unused' warning diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index dc692de..03ea40a 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -7,7 +7,7 @@ use crate::{ boundable::Boundable, color::{Color, SpectralSample}, lerp::lerp_slice, - math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector}, + math::{coordinate_system_from_vector, dot, Normal, Point, Transform, Vector}, ray::{RayBatch, RayStack}, sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere}, shading::surface_closure::SurfaceClosure, @@ -50,7 +50,7 @@ impl<'a> SphereLight<'a> { // more efficiently by inlining it there. fn sample_pdf( &self, - space: &Matrix4x4, + space: &Transform, arr: Point, sample_dir: Vector, sample_u: f32, @@ -84,7 +84,7 @@ impl<'a> SphereLight<'a> { impl<'a> SurfaceLight for SphereLight<'a> { fn sample_from_point( &self, - space: &Matrix4x4, + space: &Transform, arr: Point, u: f32, v: f32, @@ -210,7 +210,7 @@ impl<'a> Surface for SphereLight<'a> { ray_stack: &mut RayStack, isects: &mut [SurfaceIntersection], shader: &dyn SurfaceShader, - space: &[Matrix4x4], + space: &[Transform], ) { let _ = shader; // Silence 'unused' warning diff --git a/src/math.rs b/src/math.rs index 6df51e4..fec2f06 100644 --- a/src/math.rs +++ b/src/math.rs @@ -2,7 +2,7 @@ use std::f32; -pub use math3d::{cross, dot, CrossProduct, DotProduct, Matrix4x4, Normal, Point, Vector}; +pub use math3d::{cross, dot, CrossProduct, DotProduct, Normal, Point, Transform, Vector}; /// Clamps a value between a min and max. pub fn clamp(v: T, lower: T, upper: T) -> T { diff --git a/src/parse/psy.rs b/src/parse/psy.rs index fb7d0e6..4d2f6f5 100644 --- a/src/parse/psy.rs +++ b/src/parse/psy.rs @@ -10,7 +10,7 @@ use crate::{ camera::Camera, color::{rec709_e_to_xyz, Color}, light::WorldLightSource, - math::Matrix4x4, + math::Transform, renderer::Renderer, scene::Scene, scene::World, @@ -553,16 +553,17 @@ fn parse_world<'a>(arena: &'a Arena, tree: &'a DataTree) -> Result, Ps } } -pub fn parse_matrix(contents: &str) -> Result { +pub fn parse_matrix(contents: &str) -> Result { if let IResult::Ok((leftover, ns)) = all_consuming(tuple(( ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, ws_f32, )))(contents) { if leftover.is_empty() { - return Ok(Matrix4x4::new_from_values( - ns.0, ns.4, ns.8, ns.12, ns.1, ns.5, ns.9, ns.13, ns.2, ns.6, ns.10, ns.14, ns.3, - ns.7, ns.11, ns.15, + return Ok(Transform::new_from_values( + // We throw away the last row, since it's not necessarily affine. + // TODO: is there a more correct way to handle this? + ns.0, ns.4, ns.8, ns.12, ns.1, ns.5, ns.9, ns.13, ns.2, ns.6, ns.10, ns.14, )); } } diff --git a/src/ray.rs b/src/ray.rs index 166056a..daf29ab 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -2,7 +2,7 @@ use glam::BVec4A; -use crate::math::{Matrix4x4, Point, Vector}; +use crate::math::{Point, Transform, Vector}; type RayIndexType = u16; type FlagType = u8; @@ -119,7 +119,7 @@ impl RayBatch { /// /// This should be called when entering (and exiting) traversal of a /// new transform space. - pub fn update_local(&mut self, idx: usize, xform: &Matrix4x4) { + pub fn update_local(&mut self, idx: usize, xform: &Transform) { self.hot[idx].orig_local = self.cold[idx].orig * *xform; self.hot[idx].dir_inv_local = Vector { co: (self.cold[idx].dir * *xform).co.recip(), diff --git a/src/scene/assembly.rs b/src/scene/assembly.rs index 3d36c87..e9d8ce9 100644 --- a/src/scene/assembly.rs +++ b/src/scene/assembly.rs @@ -10,7 +10,7 @@ use crate::{ color::SpectralSample, lerp::lerp_slice, light::SurfaceLight, - math::{Matrix4x4, Normal, Point}, + math::{Normal, Point, Transform}, shading::SurfaceShader, surface::{Surface, SurfaceIntersection}, transform_stack::TransformStack, @@ -21,7 +21,7 @@ pub struct Assembly<'a> { // Instance list pub instances: &'a [Instance], pub light_instances: &'a [Instance], - pub xforms: &'a [Matrix4x4], + pub xforms: &'a [Transform], // Surface shader list pub surface_shaders: &'a [&'a dyn SurfaceShader], @@ -60,7 +60,7 @@ impl<'a> Assembly<'a> { let sel_xform = if !xform_stack.top().is_empty() { lerp_slice(xform_stack.top(), time) } else { - Matrix4x4::new() + Transform::new() }; if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel.select( idata.incoming * sel_xform, @@ -90,7 +90,7 @@ impl<'a> Assembly<'a> { if !pxforms.is_empty() { lerp_slice(pxforms, time) } else { - Matrix4x4::new() + Transform::new() } }; @@ -152,7 +152,7 @@ pub struct AssemblyBuilder<'a> { // Instance list instances: Vec, - xforms: Vec, + xforms: Vec, // Shader list surface_shaders: Vec<&'a dyn SurfaceShader>, @@ -224,7 +224,7 @@ impl<'a> AssemblyBuilder<'a> { &mut self, name: &str, surface_shader_name: Option<&str>, - xforms: Option<&[Matrix4x4]>, + xforms: Option<&[Transform]>, ) { // Make sure name exists if !self.name_exists(name) { diff --git a/src/surface/micropoly_batch.rs b/src/surface/micropoly_batch.rs index 967f8f4..ccb2029 100644 --- a/src/surface/micropoly_batch.rs +++ b/src/surface/micropoly_batch.rs @@ -9,7 +9,7 @@ use crate::{ bbox::BBox, boundable::Boundable, lerp::lerp_slice, - math::{cross, dot, Matrix4x4, Normal, Point}, + math::{cross, dot, Normal, Point, Transform}, ray::{RayBatch, RayStack}, shading::SurfaceClosure, }; @@ -150,13 +150,13 @@ impl<'a> MicropolyBatch<'a> { rays: &mut RayBatch, ray_stack: &mut RayStack, isects: &mut [SurfaceIntersection], - space: &[Matrix4x4], + space: &[Transform], ) { // Precalculate transform for non-motion blur cases let static_mat_space = if space.len() == 1 { lerp_slice(space, 0.0).inverse() } else { - Matrix4x4::new() + Transform::new() }; self.accel diff --git a/src/surface/mod.rs b/src/surface/mod.rs index 7122261..a718f4d 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -10,7 +10,7 @@ use std::fmt::Debug; use crate::{ boundable::Boundable, - math::{Matrix4x4, Normal, Point, Vector}, + math::{Normal, Point, Transform, Vector}, ray::{RayBatch, RayStack}, shading::surface_closure::SurfaceClosure, shading::SurfaceShader, @@ -25,7 +25,7 @@ pub trait Surface: Boundable + Debug + Sync { ray_stack: &mut RayStack, isects: &mut [SurfaceIntersection], shader: &dyn SurfaceShader, - space: &[Matrix4x4], + space: &[Transform], ); } @@ -86,7 +86,7 @@ pub struct SurfaceIntersectionData { // a cube centered around `pos` with dimensions of `2 * pos_err`. pub nor: Normal, // Shading normal pub nor_g: Normal, // True geometric normal - pub local_space: Matrix4x4, // Matrix from global space to local space + pub local_space: Transform, // Matrix from global space to local space pub t: f32, // Ray t-value at the intersection point pub sample_pdf: f32, // The PDF of getting this point by explicitly sampling the surface } diff --git a/src/surface/triangle_mesh.rs b/src/surface/triangle_mesh.rs index be1ea8f..6b16ab6 100644 --- a/src/surface/triangle_mesh.rs +++ b/src/surface/triangle_mesh.rs @@ -7,7 +7,7 @@ use crate::{ bbox::BBox, boundable::Boundable, lerp::lerp_slice, - math::{cross, dot, Matrix4x4, Normal, Point}, + math::{cross, dot, Normal, Point, Transform}, ray::{RayBatch, RayStack}, shading::SurfaceShader, }; @@ -128,13 +128,13 @@ impl<'a> Surface for TriangleMesh<'a> { ray_stack: &mut RayStack, isects: &mut [SurfaceIntersection], shader: &dyn SurfaceShader, - space: &[Matrix4x4], + space: &[Transform], ) { // Precalculate transform for non-motion blur cases let static_mat_space = if space.len() == 1 { lerp_slice(space, 0.0).inverse() } else { - Matrix4x4::new() + Transform::new() }; self.accel diff --git a/src/tracer.rs b/src/tracer.rs index 95cf8cb..8e85db6 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -4,7 +4,7 @@ use crate::{ accel::ray_code, color::{rec709_to_xyz, Color}, lerp::lerp_slice, - math::Matrix4x4, + math::Transform, ray::{RayBatch, RayStack}, scene::{Assembly, InstanceType, Object}, shading::{SimpleSurfaceShader, SurfaceShader}, @@ -63,7 +63,7 @@ impl<'a> TracerInner<'a> { // Prep the accel part of the rays. { - let ident = Matrix4x4::new(); + let ident = Transform::new(); for i in 0..rays.len() { rays.update_local(i, &ident); } @@ -140,7 +140,7 @@ impl<'a> TracerInner<'a> { rays.update_local(ray_idx, &lerp_slice(xforms, t)); }); } else { - let ident = Matrix4x4::new(); + let ident = Transform::new(); ray_stack.pop_do_next_task(|ray_idx| { rays.update_local(ray_idx, &ident); }); diff --git a/src/transform_stack.rs b/src/transform_stack.rs index 2612ce1..8219799 100644 --- a/src/transform_stack.rs +++ b/src/transform_stack.rs @@ -3,10 +3,10 @@ use std::{ mem::{transmute, MaybeUninit}, }; -use crate::{algorithm::merge_slices_to, math::Matrix4x4}; +use crate::{algorithm::merge_slices_to, math::Transform}; pub struct TransformStack { - stack: Vec>, + stack: Vec>, stack_indices: Vec, } @@ -30,11 +30,11 @@ impl TransformStack { self.stack_indices.push(0); } - pub fn push(&mut self, xforms: &[Matrix4x4]) { + pub fn push(&mut self, xforms: &[Transform]) { assert!(!xforms.is_empty()); if self.stack.is_empty() { - let xforms: &[MaybeUninit] = unsafe { transmute(xforms) }; + let xforms: &[MaybeUninit] = unsafe { transmute(xforms) }; self.stack.extend(xforms); } else { let sil = self.stack_indices.len(); @@ -73,7 +73,7 @@ impl TransformStack { self.stack_indices.pop(); } - pub fn top(&self) -> &[Matrix4x4] { + pub fn top(&self) -> &[Transform] { let sil = self.stack_indices.len(); let i1 = self.stack_indices[sil - 2]; let i2 = self.stack_indices[sil - 1]; diff --git a/sub_crates/math3d/src/lib.rs b/sub_crates/math3d/src/lib.rs index 6795dee..3c51eed 100644 --- a/sub_crates/math3d/src/lib.rs +++ b/sub_crates/math3d/src/lib.rs @@ -1,11 +1,11 @@ #![allow(dead_code)] -mod matrix; mod normal; mod point; +mod transform; mod vector; -pub use self::{matrix::Matrix4x4, normal::Normal, point::Point, vector::Vector}; +pub use self::{normal::Normal, point::Point, transform::Transform, vector::Vector}; /// Trait for calculating dot products. pub trait DotProduct { diff --git a/sub_crates/math3d/src/matrix.rs b/sub_crates/math3d/src/matrix.rs deleted file mode 100644 index 583c2b1..0000000 --- a/sub_crates/math3d/src/matrix.rs +++ /dev/null @@ -1,201 +0,0 @@ -#![allow(dead_code)] - -use std::ops::{Add, Mul}; - -use approx::relative_eq; -use glam::{Mat4, Vec4}; - -use super::Point; - -/// A 4x4 matrix, used for transforms -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Matrix4x4(pub Mat4); - -impl Matrix4x4 { - /// Creates a new identity matrix - #[inline] - pub fn new() -> Matrix4x4 { - Matrix4x4(Mat4::IDENTITY) - } - - /// Creates a new matrix with the specified values: - /// a b c d - /// e f g h - /// i j k l - /// m n o p - #[inline] - #[allow(clippy::many_single_char_names)] - #[allow(clippy::too_many_arguments)] - pub fn new_from_values( - a: f32, - b: f32, - c: f32, - d: f32, - e: f32, - f: f32, - g: f32, - h: f32, - i: f32, - j: f32, - k: f32, - l: f32, - m: f32, - n: f32, - o: f32, - p: f32, - ) -> Matrix4x4 { - Matrix4x4(Mat4::from_cols( - Vec4::new(a, e, i, m), - Vec4::new(b, f, j, n), - Vec4::new(c, g, k, o), - Vec4::new(d, h, l, p), - )) - } - - #[inline] - pub fn from_location(loc: Point) -> Matrix4x4 { - Matrix4x4(Mat4::from_translation(loc.co.into())) - } - - /// Returns whether the matrices are approximately equal to each other. - /// Each corresponding element in the matrices cannot have a relative - /// error exceeding epsilon. - #[inline] - pub fn aprx_eq(&self, other: Matrix4x4, epsilon: f32) -> bool { - let mut eq = true; - for c in 0..4 { - for r in 0..4 { - let a = self.0.col(c)[r]; - let b = other.0.col(c)[r]; - eq &= relative_eq!(a, b, epsilon = epsilon); - } - } - eq - } - - /// Returns the transpose of the matrix - #[inline] - pub fn transposed(&self) -> Matrix4x4 { - Matrix4x4(self.0.transpose()) - } - - /// Returns the inverse of the Matrix - #[inline] - pub fn inverse(&self) -> Matrix4x4 { - Matrix4x4(self.0.inverse()) - } -} - -impl Default for Matrix4x4 { - fn default() -> Self { - Self::new() - } -} - -/// Multiply two matrices together -impl Mul for Matrix4x4 { - type Output = Self; - - #[inline] - fn mul(self, other: Self) -> Self { - Self(other.0.mul_mat4(&self.0)) - } -} - -/// Multiply a matrix by a f32 -impl Mul for Matrix4x4 { - type Output = Self; - - #[inline] - fn mul(self, other: f32) -> Self { - Self(self.0 * other) - } -} - -/// Add two matrices together -impl Add for Matrix4x4 { - type Output = Self; - - #[inline] - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn equality_test() { - let a = Matrix4x4::new(); - let b = Matrix4x4::new(); - let c = Matrix4x4::new_from_values( - 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, - ); - - assert_eq!(a, b); - assert!(a != c); - } - - #[test] - fn approximate_equality_test() { - let a = Matrix4x4::new(); - let b = Matrix4x4::new_from_values( - 1.000001, 0.0, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0, - 0.0, 1.000001, - ); - let c = Matrix4x4::new_from_values( - 1.000003, 0.0, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0, - 0.0, 1.000003, - ); - let d = Matrix4x4::new_from_values( - -1.000001, 0.0, 0.0, 0.0, 0.0, -1.000001, 0.0, 0.0, 0.0, 0.0, -1.000001, 0.0, 0.0, 0.0, - 0.0, -1.000001, - ); - - assert!(a.aprx_eq(b, 0.000001)); - assert!(!a.aprx_eq(c, 0.000001)); - assert!(!a.aprx_eq(d, 0.000001)); - } - - #[test] - fn multiply_test() { - let a = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 13.0, 7.0, 15.0, 3.0, - ); - let b = Matrix4x4::new_from_values( - 1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0, - ); - let c = Matrix4x4::new_from_values( - 266.0, 141.0, 331.0, 188.5, 292.0, 158.0, 366.0, 213.0, 318.0, 175.0, 401.0, 237.5, - 344.0, 192.0, 436.0, 262.0, - ); - - assert_eq!(a * b, c); - } - - #[test] - fn inverse_test() { - let a = Matrix4x4::new_from_values( - 1.0, 0.33, 0.0, -2.0, 0.0, 1.0, 0.0, 0.0, 2.1, 0.7, 1.3, 0.0, 0.0, 0.0, 0.0, -1.0, - ); - let b = a.inverse(); - let c = Matrix4x4::new(); - - assert!((dbg!(a * b)).aprx_eq(dbg!(c), 0.0000001)); - } - - #[test] - fn transpose_test() { - let a = Matrix4x4::new_from_values( - 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, - ); - let b = Matrix4x4::new_from_values( - 1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0, - ); - let c = a.transposed(); - - assert_eq!(b, c); - } -} diff --git a/sub_crates/math3d/src/normal.rs b/sub_crates/math3d/src/normal.rs index 2b79dd3..c5d55a7 100644 --- a/sub_crates/math3d/src/normal.rs +++ b/sub_crates/math3d/src/normal.rs @@ -7,7 +7,7 @@ use std::{ use glam::Vec3A; -use super::{CrossProduct, DotProduct, Matrix4x4, Vector}; +use super::{CrossProduct, DotProduct, Transform, Vector}; /// A surface normal in 3d homogeneous space. #[derive(Debug, Copy, Clone)] @@ -126,14 +126,13 @@ impl Mul for Normal { } } -impl Mul for Normal { +impl Mul for Normal { type Output = Normal; #[inline] - fn mul(self, other: Matrix4x4) -> Normal { - let mat = other.0.inverse().transpose(); + fn mul(self, other: Transform) -> Normal { Normal { - co: mat.transform_vector3a(self.co), + co: other.0.matrix3.inverse().transpose().mul_vec3a(self.co), } } } @@ -176,9 +175,9 @@ impl CrossProduct for Normal { #[cfg(test)] mod tests { - use super::super::{CrossProduct, DotProduct, Matrix4x4}; + use super::super::{CrossProduct, DotProduct, Transform}; use super::*; - use approx::UlpsEq; + use approx::assert_ulps_eq; #[test] fn add() { @@ -210,12 +209,14 @@ mod tests { #[test] fn mul_matrix_1() { let n = Normal::new(1.0, 2.5, 4.0); - let m = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 13.0, 7.0, 15.0, 3.0, + let m = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); let nm = n * m; - let nm2 = Normal::new(-19.258825, 5.717648, -1.770588); - assert!(nm.co.ulps_eq(&nm2.co, 0.0, 4)); + let nm2 = Normal::new(-4.0625, 1.78125, -0.03125); + for i in 0..3 { + assert_ulps_eq!(nm.co[i], nm2.co[i], max_ulps = 4); + } } #[test] diff --git a/sub_crates/math3d/src/point.rs b/sub_crates/math3d/src/point.rs index b6cbb5e..19deb56 100644 --- a/sub_crates/math3d/src/point.rs +++ b/sub_crates/math3d/src/point.rs @@ -7,7 +7,7 @@ use std::{ use glam::Vec3A; -use super::{Matrix4x4, Vector}; +use super::{Transform, Vector}; /// A position in 3d homogeneous space. #[derive(Debug, Copy, Clone)] @@ -23,15 +23,6 @@ impl Point { } } - // /// Returns the point in standardized coordinates, where the - // /// fourth homogeneous component has been normalized to 1.0. - // #[inline(always)] - // pub fn norm(&self) -> Point { - // Point { - // co: self.co / self.co[3], - // } - // } - #[inline(always)] pub fn min(&self, other: Point) -> Point { let n1 = self; @@ -138,11 +129,11 @@ impl Sub for Point { } } -impl Mul for Point { +impl Mul for Point { type Output = Point; #[inline] - fn mul(self, other: Matrix4x4) -> Point { + fn mul(self, other: Transform) -> Point { Point { co: other.0.transform_point3a(self.co), } @@ -151,18 +142,9 @@ impl Mul for Point { #[cfg(test)] mod tests { - use super::super::{Matrix4x4, Vector}; + use super::super::{Transform, Vector}; use super::*; - #[test] - fn norm() { - let mut p1 = Point::new(1.0, 2.0, 3.0); - let p2 = Point::new(2.0, 4.0, 6.0); - p1.co.set_w(0.5); - - assert_eq!(p2, p1.norm()); - } - #[test] fn add() { let p1 = Point::new(1.0, 2.0, 3.0); @@ -184,8 +166,8 @@ mod tests { #[test] fn mul_matrix_1() { let p = Point::new(1.0, 2.5, 4.0); - let m = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 0.0, 0.0, 0.0, 1.0, + let m = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); let pm = Point::new(15.5, 54.0, 70.0); assert_eq!(p * m, pm); @@ -194,11 +176,10 @@ mod tests { #[test] fn mul_matrix_2() { let p = Point::new(1.0, 2.5, 4.0); - let m = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 2.0, 3.0, 1.0, 5.0, + let m = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); - let mut pm = Point::new(15.5, 54.0, 70.0); - pm.co.set_w(18.5); + let pm = Point::new(15.5, 54.0, 70.0); assert_eq!(p * m, pm); } @@ -206,12 +187,11 @@ mod tests { fn mul_matrix_3() { // Make sure matrix multiplication composes the way one would expect let p = Point::new(1.0, 2.5, 4.0); - let m1 = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 13.0, 7.0, 15.0, 3.0, - ); - let m2 = Matrix4x4::new_from_values( - 4.0, 1.0, 2.0, 3.5, 3.0, 6.0, 5.0, 2.0, 2.0, 2.0, 4.0, 12.0, 5.0, 7.0, 8.0, 11.0, + let m1 = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); + let m2 = + Transform::new_from_values(4.0, 1.0, 2.0, 3.5, 3.0, 6.0, 5.0, 2.0, 2.0, 2.0, 4.0, 12.0); println!("{:?}", m1 * m2); let pmm1 = p * (m1 * m2); diff --git a/sub_crates/math3d/src/transform.rs b/sub_crates/math3d/src/transform.rs new file mode 100644 index 0000000..29cd069 --- /dev/null +++ b/sub_crates/math3d/src/transform.rs @@ -0,0 +1,178 @@ +#![allow(dead_code)] + +use std::ops::{Add, Mul}; + +use approx::relative_eq; +use glam::{Affine3A, Mat3, Mat4, Vec3}; + +use super::Point; + +/// A 4x3 affine transform matrix, used for transforms. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Transform(pub Affine3A); + +impl Transform { + /// Creates a new identity matrix + #[inline] + pub fn new() -> Transform { + Transform(Affine3A::IDENTITY) + } + + /// Creates a new matrix with the specified values: + /// a b c d + /// e f g h + /// i j k l + /// m n o p + #[inline] + #[allow(clippy::many_single_char_names)] + #[allow(clippy::too_many_arguments)] + pub fn new_from_values( + a: f32, + b: f32, + c: f32, + d: f32, + e: f32, + f: f32, + g: f32, + h: f32, + i: f32, + j: f32, + k: f32, + l: f32, + ) -> Transform { + Transform(Affine3A::from_mat3_translation( + Mat3::from_cols(Vec3::new(a, e, i), Vec3::new(b, f, j), Vec3::new(c, g, k)), + Vec3::new(d, h, l), + )) + } + + #[inline] + pub fn from_location(loc: Point) -> Transform { + Transform(Affine3A::from_translation(loc.co.into())) + } + + /// Returns whether the matrices are approximately equal to each other. + /// Each corresponding element in the matrices cannot have a relative + /// error exceeding epsilon. + #[inline] + pub fn aprx_eq(&self, other: Transform, epsilon: f32) -> bool { + let mut eq = true; + for c in 0..3 { + for r in 0..3 { + let a = self.0.matrix3.col(c)[r]; + let b = other.0.matrix3.col(c)[r]; + eq &= relative_eq!(a, b, epsilon = epsilon); + } + } + for i in 0..3 { + let a = self.0.translation[i]; + let b = other.0.translation[i]; + eq &= relative_eq!(a, b, epsilon = epsilon); + } + eq + } + + /// Returns the inverse of the Matrix + #[inline] + pub fn inverse(&self) -> Transform { + Transform(self.0.inverse()) + } +} + +impl Default for Transform { + fn default() -> Self { + Self::new() + } +} + +/// Multiply two matrices together +impl Mul for Transform { + type Output = Self; + + #[inline] + fn mul(self, other: Self) -> Self { + Self(other.0 * self.0) + } +} + +/// Multiply a matrix by a f32 +impl Mul for Transform { + type Output = Self; + + #[inline] + fn mul(self, other: f32) -> Self { + Self(Affine3A::from_mat4(Mat4::from(self.0) * other)) + } +} + +/// Add two matrices together +impl Add for Transform { + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + Self(Affine3A::from_mat4( + Mat4::from(self.0) + Mat4::from(other.0), + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn equality_test() { + let a = Transform::new(); + let b = Transform::new(); + let c = + Transform::new_from_values(1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0); + + assert_eq!(a, b); + assert!(a != c); + } + + #[test] + fn approximate_equality_test() { + let a = Transform::new(); + let b = Transform::new_from_values( + 1.000001, 0.0, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0, 0.0, 1.000001, 0.0, + ); + let c = Transform::new_from_values( + 1.000003, 0.0, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0, 0.0, 1.000003, 0.0, + ); + let d = Transform::new_from_values( + -1.000001, 0.0, 0.0, 0.0, 0.0, -1.000001, 0.0, 0.0, 0.0, 0.0, -1.000001, 0.0, + ); + + assert!(a.aprx_eq(b, 0.000001)); + assert!(!a.aprx_eq(c, 0.000001)); + assert!(!a.aprx_eq(d, 0.000001)); + } + + #[test] + fn multiply_test() { + let a = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, + ); + let b = Transform::new_from_values( + 1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, + ); + let c = Transform::new_from_values( + 97.0, 50.0, 136.0, 162.5, 110.0, 60.0, 156.0, 185.0, 123.0, 70.0, 176.0, 207.5, + ); + + assert_eq!(a * b, c); + } + + #[test] + fn inverse_test() { + let a = Transform::new_from_values( + 1.0, 0.33, 0.0, -2.0, 0.0, 1.0, 0.0, 0.0, 2.1, 0.7, 1.3, 0.0, + ); + let b = a.inverse(); + let c = Transform::new(); + + assert!((dbg!(a * b)).aprx_eq(dbg!(c), 0.0000001)); + } +} diff --git a/sub_crates/math3d/src/vector.rs b/sub_crates/math3d/src/vector.rs index 633f6f3..365e892 100644 --- a/sub_crates/math3d/src/vector.rs +++ b/sub_crates/math3d/src/vector.rs @@ -7,7 +7,7 @@ use std::{ use glam::Vec3A; -use super::{CrossProduct, DotProduct, Matrix4x4, Normal, Point}; +use super::{CrossProduct, DotProduct, Normal, Point, Transform}; /// A direction vector in 3d homogeneous space. #[derive(Debug, Copy, Clone)] @@ -138,11 +138,11 @@ impl Mul for Vector { } } -impl Mul for Vector { +impl Mul for Vector { type Output = Vector; #[inline] - fn mul(self, other: Matrix4x4) -> Vector { + fn mul(self, other: Transform) -> Vector { Vector { co: other.0.transform_vector3a(self.co), } @@ -187,7 +187,7 @@ impl CrossProduct for Vector { #[cfg(test)] mod tests { - use super::super::{CrossProduct, DotProduct, Matrix4x4}; + use super::super::{CrossProduct, DotProduct, Transform}; use super::*; #[test] @@ -220,8 +220,8 @@ mod tests { #[test] fn mul_matrix_1() { let v = Vector::new(1.0, 2.5, 4.0); - let m = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 13.0, 7.0, 15.0, 3.0, + let m = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); assert_eq!(v * m, Vector::new(14.0, 46.0, 58.0)); } @@ -229,8 +229,8 @@ mod tests { #[test] fn mul_matrix_2() { let v = Vector::new(1.0, 2.5, 4.0); - let m = Matrix4x4::new_from_values( - 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, 0.0, 0.0, 0.0, 1.0, + let m = Transform::new_from_values( + 1.0, 2.0, 2.0, 1.5, 3.0, 6.0, 7.0, 8.0, 9.0, 2.0, 11.0, 12.0, ); assert_eq!(v * m, Vector::new(14.0, 46.0, 58.0)); }