From c48790f32fc7fa000e1c9bbb464d16fa97e4cedb Mon Sep 17 00:00:00 2001 From: hal8174 Date: Sun, 24 Aug 2025 21:31:13 +0200 Subject: [PATCH] Use full 4x4 matrix for affine transformation --- ray-tracing-core/src/affine_transform.rs | 219 ++++++++++++++++++----- ray-tracing-pbrt-scene/src/lib.rs | 2 + 2 files changed, 172 insertions(+), 49 deletions(-) diff --git a/ray-tracing-core/src/affine_transform.rs b/ray-tracing-core/src/affine_transform.rs index c437dc4..9998282 100644 --- a/ray-tracing-core/src/affine_transform.rs +++ b/ray-tracing-core/src/affine_transform.rs @@ -2,14 +2,40 @@ use std::ops::{Mul, MulAssign}; use crate::prelude::*; -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] pub struct AffineTransform { - mat: [[Float; 4]; 3], - inv: [[Float; 3]; 3], + mat: [[Float; 4]; 4], + inv: [[Float; 4]; 4], +} + +impl std::fmt::Debug for AffineTransform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct MatDebug([[Float; 4]; 4]); + struct RowDebug([Float; 4]); + + impl std::fmt::Debug for RowDebug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{:.5?}", self.0)) + } + } + + impl std::fmt::Debug for MatDebug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.0.into_iter().map(RowDebug)) + .finish() + } + } + + f.debug_struct("AffineTransform") + .field("mat", &MatDebug(self.mat)) + .field("inv", &MatDebug(self.inv)) + .finish() + } } impl AffineTransform { - pub fn new(mat: [[Float; 4]; 3]) -> Option { + pub fn new(mat: [[Float; 4]; 4]) -> Option { let inv = Self::invert(mat)?; Some(Self { mat, inv }) } @@ -20,8 +46,14 @@ impl AffineTransform { [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + inv: [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], ], - inv: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], } } @@ -31,8 +63,14 @@ impl AffineTransform { [1.0, 0.0, 0.0, pos.x()], [0.0, 1.0, 0.0, pos.y()], [0.0, 0.0, 1.0, pos.z()], + [0.0, 0.0, 0.0, 1.0], + ], + inv: [ + [1.0, 0.0, 0.0, -pos.x()], + [0.0, 1.0, 0.0, -pos.y()], + [0.0, 0.0, 1.0, -pos.z()], + [0.0, 0.0, 0.0, 1.0], ], - inv: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], } } @@ -42,11 +80,13 @@ impl AffineTransform { [sx, 0.0, 0.0, 0.0], [0.0, sy, 0.0, 0.0], [0.0, 0.0, sz, 0.0], + [0.0, 0.0, 0.0, 1.0], ], inv: [ - [1.0 / sx, 0.0, 0.0], - [0.0, 1.0 / sy, 0.0], - [0.0, 0.0, 1.0 / sz], + [1.0 / sx, 0.0, 0.0, 0.0], + [0.0, 1.0 / sy, 0.0, 0.0], + [0.0, 0.0, 1.0 / sz, 0.0], + [0.0, 0.0, 0.0, 1.0], ], } } @@ -71,13 +111,15 @@ impl AffineTransform { dir.z() * dir.z() * (1.0 - angle.cos()) + angle.cos(), 0.0, ], + [0.0, 0.0, 0.0, 1.0], ]; Self { mat, inv: [ - [mat[0][0], mat[1][0], mat[2][0]], - [mat[0][1], mat[1][1], mat[2][1]], - [mat[0][2], mat[1][2], mat[2][2]], + [mat[0][0], mat[1][0], mat[2][0], 0.0], + [mat[0][1], mat[1][1], mat[2][1], 0.0], + [mat[0][2], mat[1][2], mat[2][2], 0.0], + [0.0, 0.0, 0.0, 1.0], ], } } @@ -91,33 +133,46 @@ impl AffineTransform { [right.x(), new_up.x(), dir.x(), pos.x()], [right.y(), new_up.y(), dir.y(), pos.y()], [right.z(), new_up.z(), dir.z(), pos.z()], + [0.0, 0.0, 0.0, 1.0], ]; Self::new(mat) } - fn invert(mat: [[Float; 4]; 3]) -> Option<[[Float; 3]; 3]> { + fn invert(mat: [[Float; 4]; 4]) -> Option<[[Float; 4]; 4]> { // using cramers rule + let cofactor_indices = |p| match p { + 0 => (1, 2, 3), + 1 => (0, 2, 3), + 2 => (0, 1, 3), + 3 => (0, 1, 2), + _ => unreachable!(), + }; let adj = |y, x| { - let cofactor_indices = |p| match p { - 0 => (1, 2), - 1 => (0, 2), - 2 => (0, 1), - _ => unreachable!(), - }; - let (y1, y2) = cofactor_indices(y); - let (x1, x2) = cofactor_indices(x); + let (y0, y1, y2) = cofactor_indices(y); + let (x0, x1, x2) = cofactor_indices(x); (if (x + y) % 2 == 0 { 1.0 } else { -1.0 }) - * (mat[y1][x1] * mat[y2][x2] - mat[y1][x2] * mat[y2][x1]) + * (mat[y0][x0] * mat[y1][x1] * mat[y2][x2] + + mat[y0][x1] * mat[y1][x2] * mat[y2][x0] + + mat[y0][x2] * mat[y1][x0] * mat[y2][x1] + - mat[y0][x2] * mat[y1][x1] * mat[y2][x0] + - mat[y0][x1] * mat[y1][x0] * mat[y2][x2] + - mat[y0][x0] * mat[y1][x2] * mat[y2][x1]) }; - let det = mat[0][0] * mat[1][1] * mat[2][2] - + mat[0][1] * mat[1][2] * mat[2][0] - + mat[0][2] * mat[1][0] * mat[2][1] - - mat[0][2] * mat[1][1] * mat[2][0] - - mat[0][1] * mat[1][0] * mat[2][2] - - mat[0][0] * mat[1][2] * mat[2][1]; + let mut det = 0.0; + for i in 0..=3 { + let (x0, x1, x2) = cofactor_indices(i); + det = if i % 2 == 0 { -1.0 } else { 1.0 } + * mat[3][i] + * (mat[0][x0] * mat[1][x1] * mat[2][x2] + + mat[0][x1] * mat[1][x2] * mat[2][x0] + + mat[0][x2] * mat[1][x0] * mat[2][x1] + - mat[0][x2] * mat[1][x1] * mat[2][x0] + - mat[0][x1] * mat[1][x0] * mat[2][x2] + - mat[0][x0] * mat[1][x2] * mat[2][x1]); + } if det != 0.0 { let det_frac = 1.0 / det; @@ -127,16 +182,25 @@ impl AffineTransform { adj(0, 0) * det_frac, adj(1, 0) * det_frac, adj(2, 0) * det_frac, + adj(3, 0) * det_frac, ], [ adj(0, 1) * det_frac, adj(1, 1) * det_frac, adj(2, 1) * det_frac, + adj(3, 1) * det_frac, ], [ adj(0, 2) * det_frac, adj(1, 2) * det_frac, adj(2, 2) * det_frac, + adj(3, 2) * det_frac, + ], + [ + adj(0, 3) * det_frac, + adj(1, 3) * det_frac, + adj(2, 3) * det_frac, + adj(3, 3) * det_frac, ], ]) } else { @@ -194,90 +258,147 @@ impl Mul for AffineTransform { self.mat[0][0] * rhs.mat[0][0] + self.mat[0][1] * rhs.mat[1][0] + self.mat[0][2] * rhs.mat[2][0] - + self.mat[0][3], + + self.mat[0][3] * rhs.mat[3][0], self.mat[0][0] * rhs.mat[0][1] + self.mat[0][1] * rhs.mat[1][1] + self.mat[0][2] * rhs.mat[2][1] - + self.mat[0][3], + + self.mat[0][3] * rhs.mat[3][1], self.mat[0][0] * rhs.mat[0][2] + self.mat[0][1] * rhs.mat[1][2] + self.mat[0][2] * rhs.mat[2][2] - + self.mat[0][3], + + self.mat[0][3] * rhs.mat[3][2], self.mat[0][0] * rhs.mat[0][3] + self.mat[0][1] * rhs.mat[1][3] + self.mat[0][2] * rhs.mat[2][3] - + self.mat[0][3], + + self.mat[0][3] * rhs.mat[3][3], ], [ self.mat[1][0] * rhs.mat[0][0] + self.mat[1][1] * rhs.mat[1][0] + self.mat[1][2] * rhs.mat[2][0] - + self.mat[1][3], + + self.mat[1][3] * rhs.mat[3][0], self.mat[1][0] * rhs.mat[0][1] + self.mat[1][1] * rhs.mat[1][1] + self.mat[1][2] * rhs.mat[2][1] - + self.mat[1][3], + + self.mat[1][3] * rhs.mat[3][1], self.mat[1][0] * rhs.mat[0][2] + self.mat[1][1] * rhs.mat[1][2] + self.mat[1][2] * rhs.mat[2][2] - + self.mat[1][3], + + self.mat[1][3] * rhs.mat[3][2], self.mat[1][0] * rhs.mat[0][3] + self.mat[1][1] * rhs.mat[1][3] + self.mat[1][2] * rhs.mat[2][3] - + self.mat[1][3], + + self.mat[1][3] * rhs.mat[3][3], ], [ self.mat[2][0] * rhs.mat[0][0] + self.mat[2][1] * rhs.mat[1][0] + self.mat[2][2] * rhs.mat[2][0] - + self.mat[2][3], + + self.mat[2][3] * rhs.mat[3][0], self.mat[2][0] * rhs.mat[0][1] + self.mat[2][1] * rhs.mat[1][1] + self.mat[2][2] * rhs.mat[2][1] - + self.mat[2][3], + + self.mat[2][3] * rhs.mat[3][1], self.mat[2][0] * rhs.mat[0][2] + self.mat[2][1] * rhs.mat[1][2] + self.mat[2][2] * rhs.mat[2][2] - + self.mat[2][3], + + self.mat[2][3] * rhs.mat[3][3], self.mat[2][0] * rhs.mat[0][3] + self.mat[2][1] * rhs.mat[1][3] + self.mat[2][2] * rhs.mat[2][3] - + self.mat[2][3], + + self.mat[2][3] * rhs.mat[3][3], + ], + [ + self.mat[3][0] * rhs.mat[0][0] + + self.mat[3][1] * rhs.mat[1][0] + + self.mat[3][2] * rhs.mat[2][0] + + self.mat[3][3] * rhs.mat[3][0], + self.mat[3][0] * rhs.mat[0][1] + + self.mat[3][1] * rhs.mat[1][1] + + self.mat[3][2] * rhs.mat[2][1] + + self.mat[3][3] * rhs.mat[3][1], + self.mat[3][0] * rhs.mat[0][2] + + self.mat[3][1] * rhs.mat[1][2] + + self.mat[3][2] * rhs.mat[2][2] + + self.mat[3][3] * rhs.mat[3][2], + self.mat[3][0] * rhs.mat[0][3] + + self.mat[3][1] * rhs.mat[1][3] + + self.mat[3][2] * rhs.mat[2][3] + + self.mat[3][3] * rhs.mat[3][3], ], ], inv: [ [ rhs.inv[0][0] * self.inv[0][0] + rhs.inv[0][1] * self.inv[1][0] - + rhs.inv[0][2] * self.inv[2][0], + + rhs.inv[0][2] * self.inv[2][0] + + rhs.inv[0][3] * self.inv[3][0], rhs.inv[0][0] * self.inv[0][1] + rhs.inv[0][1] * self.inv[1][1] - + rhs.inv[0][2] * self.inv[2][1], + + rhs.inv[0][2] * self.inv[2][1] + + rhs.inv[0][3] * self.inv[3][1], rhs.inv[0][0] * self.inv[0][2] + rhs.inv[0][1] * self.inv[1][2] - + rhs.inv[0][2] * self.inv[2][2], + + rhs.inv[0][2] * self.inv[2][2] + + rhs.inv[0][3] * self.inv[3][2], + rhs.inv[0][0] * self.inv[0][3] + + rhs.inv[0][1] * self.inv[1][3] + + rhs.inv[0][2] * self.inv[2][3] + + rhs.inv[0][3] * self.inv[3][3], ], [ rhs.inv[1][0] * self.inv[0][0] + rhs.inv[1][1] * self.inv[1][0] - + rhs.inv[1][2] * self.inv[2][0], + + rhs.inv[1][2] * self.inv[2][0] + + rhs.inv[1][3] * self.inv[3][0], rhs.inv[1][0] * self.inv[0][1] + rhs.inv[1][1] * self.inv[1][1] - + rhs.inv[1][2] * self.inv[2][1], + + rhs.inv[1][2] * self.inv[2][1] + + rhs.inv[1][3] * self.inv[3][1], rhs.inv[1][0] * self.inv[0][2] + rhs.inv[1][1] * self.inv[1][2] - + rhs.inv[1][2] * self.inv[2][2], + + rhs.inv[1][2] * self.inv[2][2] + + rhs.inv[1][3] * self.inv[3][2], + rhs.inv[1][0] * self.inv[0][3] + + rhs.inv[1][1] * self.inv[1][3] + + rhs.inv[1][2] * self.inv[2][3] + + rhs.inv[1][3] * self.inv[3][3], ], [ rhs.inv[2][0] * self.inv[0][0] + rhs.inv[2][1] * self.inv[1][0] - + rhs.inv[2][2] * self.inv[2][0], + + rhs.inv[2][2] * self.inv[2][0] + + rhs.inv[2][3] * self.inv[3][0], rhs.inv[2][0] * self.inv[0][1] + rhs.inv[2][1] * self.inv[1][1] - + rhs.inv[2][2] * self.inv[2][1], + + rhs.inv[2][2] * self.inv[2][1] + + rhs.inv[2][3] * self.inv[3][1], rhs.inv[2][0] * self.inv[0][2] + rhs.inv[2][1] * self.inv[1][2] - + rhs.inv[2][2] * self.inv[2][2], + + rhs.inv[2][2] * self.inv[2][2] + + rhs.inv[2][3] * self.inv[3][3], + rhs.inv[2][0] * self.inv[0][3] + + rhs.inv[2][1] * self.inv[1][3] + + rhs.inv[2][2] * self.inv[2][3] + + rhs.inv[2][3] * self.inv[3][3], + ], + [ + rhs.inv[3][0] * self.inv[0][0] + + rhs.inv[3][1] * self.inv[1][0] + + rhs.inv[3][2] * self.inv[2][0] + + rhs.inv[3][3] * self.inv[3][0], + rhs.inv[3][0] * self.inv[0][1] + + rhs.inv[3][1] * self.inv[1][1] + + rhs.inv[3][2] * self.inv[2][1] + + rhs.inv[3][3] * self.inv[3][1], + rhs.inv[3][0] * self.inv[0][2] + + rhs.inv[3][1] * self.inv[1][2] + + rhs.inv[3][2] * self.inv[2][2] + + rhs.inv[3][3] * self.inv[3][2], + rhs.inv[3][0] * self.inv[0][3] + + rhs.inv[3][1] * self.inv[1][3] + + rhs.inv[3][2] * self.inv[2][3] + + rhs.inv[3][3] * self.inv[3][3], ], ], } diff --git a/ray-tracing-pbrt-scene/src/lib.rs b/ray-tracing-pbrt-scene/src/lib.rs index 6d858c9..8629f2a 100644 --- a/ray-tracing-pbrt-scene/src/lib.rs +++ b/ray-tracing-pbrt-scene/src/lib.rs @@ -487,6 +487,7 @@ fn parse_transform(input: &mut Tokenizer) -> Result { [v[0], v[4], v[8], v[12]], [v[1], v[5], v[9], v[13]], [v[2], v[6], v[10], v[14]], + [v[3], v[7], v[11], v[15]], ]) .ok_or(miette!("Unable to invert transformation")) } @@ -757,6 +758,7 @@ fn inner_parse_pbrt(path: impl AsRef + std::fmt::Debug) -> Result { *context.ctm.last_mut().unwrap() = affine_transform } Statement::Shape(shape_type, shape_alpha) => { + dbg!(&context); pbrt.scene.shapes.push(Shape { ctm: context.get_ctm(), material: Arc::clone(