Compare commits

...

6 Commits

Author SHA1 Message Date
Matthew Gordon 4202cc8f2e Code reformatting 2025-03-29 15:39:59 -03:00
Matthew Gordon 6e3a5dd1d8 Replace Aggregate with Primitive and stub Primitive::transform()
All the implemntations of transform are just todo!()
2025-03-29 15:39:02 -03:00
Matthew Gordon fa713fc72d Add Affine3 struct 2025-03-29 15:37:06 -03:00
Matthew Gordon 84ad551026 Add sin and cos to Float 2025-03-29 15:36:29 -03:00
Matthew Gordon 44bc147421 Add missing Copy marker to Vec4 2025-03-29 15:26:13 -03:00
Matthew Gordon 908da3d995 Add Vec4::xyz() 2025-03-29 15:13:40 -03:00
12 changed files with 227 additions and 66 deletions

View File

@ -20,7 +20,7 @@ use vanrijn::materials::LambertianMaterial;
use vanrijn::math::Vec3;
use vanrijn::mesh::load_obj;
use vanrijn::partial_render_scene;
use vanrijn::raycasting::{Aggregate, BoundingVolumeHierarchy, Plane, Primitive, Sphere};
use vanrijn::raycasting::{BoundingVolumeHierarchy, Plane, Primitive, Sphere};
use vanrijn::scene::Scene;
use vanrijn::util::TileIterator;
@ -131,7 +131,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
)?;
println!("Building BVH...");
let model_bvh: Box<dyn Aggregate> =
let model_bvh: Box<dyn Primitive> =
Box::new(BoundingVolumeHierarchy::build(model_object.as_mut_slice()));
println!("Constructing Scene...");
@ -183,7 +183,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
//specular_strength: 1.0,
}),
)),
]) as Box<dyn Aggregate>,
]) as Box<dyn Primitive>,
model_bvh,
],
};

160
src/math/affine3.rs Normal file
View File

@ -0,0 +1,160 @@
use super::{Float, Mat3, Mat4, Vec3, Vec4};
use std::ops::{Mul, MulAssign};
#[derive(PartialEq, Debug)]
pub struct Affine3<T: Float> {
matrix: Mat4<T>,
}
impl<T: Float> Affine3<T> {
pub fn translation(delta: Vec3<T>) -> Self {
#[rustfmt::skip]
let matrix = Mat4::new(T::one(), T::zero(), T::zero(), delta.x(),
T::zero(), T::one() , T::zero(), delta.y(),
T::zero(), T::zero(), T::one(), delta.z(),
T::zero(), T::zero(), T::zero(), T::one());
Self { matrix }
}
pub fn rotation(axis: Vec3<T>, angle: T) -> Self {
let x = axis.x();
let y = axis.y();
let z = axis.z();
let cos = angle.cos();
let ncos = T::one() - cos;
let sin = angle.sin();
#[rustfmt::skip]
let matrix = Mat4::new(x*x*ncos+cos, y*x*ncos-z*sin, z*x*ncos+y*sin, T::zero(),
x*y*ncos+z*sin, y*y*ncos+cos, z*y*ncos-x*sin, T::zero(),
x*z*ncos-y*sin, y*z*ncos+x*sin, z*z*ncos+cos, T::zero(),
T::zero(), T::zero(), T::zero(), T::one());
Self { matrix }
}
pub fn scale(s: T) -> Self {
#[rustfmt::skip]
let matrix = Mat4::new(s, T::zero(), T::zero(), T::zero(),
T::zero(), s , T::zero(), T::zero(),
T::zero(), T::zero(), s, T::zero(),
T::zero(), T::zero(), T::zero(), T::one());
Self { matrix }
}
pub fn get_element(&self, row: usize, column: usize) -> T {
self.matrix.get_element(row, column)
}
pub fn get_row(&self, row: usize) -> Vec4<T> {
self.matrix.get_row(row)
}
pub fn get_column(&self, column: usize) -> Vec4<T> {
self.matrix.get_column(column)
}
pub fn linear_map(&self) -> Mat3<T> {
Mat3::new(
self.matrix.get_element(0, 0),
self.matrix.get_element(0, 1),
self.matrix.get_element(0, 2),
self.matrix.get_element(1, 0),
self.matrix.get_element(1, 1),
self.matrix.get_element(1, 2),
self.matrix.get_element(2, 0),
self.matrix.get_element(2, 1),
self.matrix.get_element(2, 2),
)
}
pub fn inverse(&self) -> Affine3<T> {
// linear map should always be invertable.
let inner = self.linear_map().try_inverse().unwrap();
let translation = inner * self.matrix.get_column(3).xyz();
#[rustfmt::skip]
let matrix = Mat4::new(
inner.get_element(0,0), inner.get_element(0,1), inner.get_element(0,2), translation.x(),
inner.get_element(1,0), inner.get_element(1,1), inner.get_element(1,2), translation.y(),
inner.get_element(2,0), inner.get_element(2,1), inner.get_element(2,2), translation.z(),
T::zero(), T::zero(), T::zero(), T::one());
Self { matrix }
}
}
impl<T: Float> Mul<Affine3<T>> for Affine3<T> {
type Output = Self;
fn mul(self, rhs: Affine3<T>) -> Affine3<T> {
let matrix = self.matrix * rhs.matrix;
Affine3 { matrix }
}
}
impl<T: Float> Mul<Mat4<T>> for Affine3<T> {
type Output = Mat4<T>;
fn mul(self, rhs: Mat4<T>) -> Mat4<T> {
self.matrix * rhs
}
}
impl<T: Float> Mul<Affine3<T>> for Mat4<T> {
type Output = Mat4<T>;
fn mul(self, rhs: Affine3<T>) -> Mat4<T> {
self * rhs.matrix
}
}
impl<T: Float> MulAssign<Affine3<T>> for Affine3<T> {
fn mul_assign(&mut self, rhs: Affine3<T>) {
self.matrix *= rhs.matrix
}
}
impl<T: Float> Mul<Vec4<T>> for Affine3<T> {
type Output = Vec4<T>;
fn mul(self, rhs: Vec4<T>) -> Vec4<T> {
self.matrix * rhs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn translate_translates_vector() {
let p = Vec4::new(1.0, 2.0, 3.0, 1.0);
let v = Vec3::new(4.0, 5.0, 6.0);
let target = Affine3::translation(v);
let diff = (target * p).xyz() - (p.xyz() + v);
assert!(diff.norm() < 0.0000000001);
}
#[test]
fn rotate_rotates_vector() {
let x = Vec4::new(1.0, 0.0, 0.0, 1.0);
let y = Vec4::new(0.0, 1.0, 0.0, 1.0);
let z = Vec4::new(0.0, 0.0, 1.0, 1.0);
let target = Affine3::rotation(z.xyz(), std::f64::consts::PI/2.0) * y;
let diff = -x.xyz() - target.xyz();
assert!(diff.norm() < 0.0000000001);
}
#[test]
fn linear_map_is_inner_matrix() {
#[rustfmt::skip]
let target = Affine3{
matrix: Mat4::new(1.0, 2.0, 3.0, 4.0,
5.0, 6.0, 7.0, 8.0,
9.0, 10.0, 11.0, 12.0,
0.0, 0.0, 0.0, 1.0)};
let linear_map = target.linear_map();
for i in 0..2 {
for j in 0..2 {
assert!(linear_map.get_element(i, j) == target.get_element(i, j));
}
}
}
}

View File

@ -18,3 +18,6 @@ pub use mat3::*;
mod mat4;
pub use mat4::*;
mod affine3;
pub use affine3::*;

View File

@ -37,6 +37,8 @@ pub trait Float:
{
fn abs(self) -> Self;
fn sqrt(self) -> Self;
fn sin(self) -> Self;
fn cos(self) -> Self;
}
impl HasZero for f64 {
@ -55,7 +57,16 @@ impl Float for f64 {
fn abs(self) -> Self {
self.abs()
}
fn sqrt(self) -> Self {
self.sqrt()
}
fn sin(self) -> Self {
self.sin()
}
fn cos(self) -> Self {
self.cos()
}
}

View File

@ -1,10 +1,10 @@
use super::Float;
use super::{Float, Vec3};
use itertools::izip;
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Clone, Copy)]
pub struct Vec4<T: Float> {
pub coords: [T; 4],
}
@ -32,6 +32,12 @@ impl<T: Float> Vec4<T> {
self.coords[3]
}
pub fn xyz(&self) -> Vec3<T> {
let mut coords = [T::zero(); 3];
coords.copy_from_slice(&self.coords[0..3]);
Vec3 { coords }
}
pub fn dot(&self, rhs: &Vec4<T>) -> T {
self.coords
.iter()
@ -138,6 +144,14 @@ mod tests {
assert!(target.w() == 4.0);
}
#[test]
fn xyz_returns_expected_value() {
let target = Vec4::new(1.0, 2.0, 3.0, 4.0).xyz();
assert!(target.x() == 1.0);
assert!(target.y() == 2.0);
assert!(target.z() == 3.0);
}
#[test]
fn dot_product_returns_correct_result() {
let a = Vec4::new(1.0, 2.0, 3.0, 4.0);

View File

@ -1,8 +1,6 @@
use crate::math::Vec3;
use crate::math::{Affine3, Vec3};
use super::{
Aggregate, BoundingBox, HasBoundingBox, Intersect, IntersectP, IntersectionInfo, Primitive, Ray,
};
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectP, IntersectionInfo, Primitive, Ray};
use std::cmp::Ordering;
use std::sync::Arc;
@ -125,7 +123,11 @@ impl HasBoundingBox for BoundingVolumeHierarchy {
}
}
impl Aggregate for BoundingVolumeHierarchy {}
impl Primitive for BoundingVolumeHierarchy {
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive> {
todo!()
}
}
#[cfg(test)]
mod test {}

View File

@ -1,4 +1,4 @@
use crate::math::Vec3;
use crate::math::{Affine3,Vec3};
use super::materials::Material;
@ -121,23 +121,12 @@ pub trait HasBoundingBox: Send + Sync {
fn bounding_box(&self) -> BoundingBox;
}
/// Any geometric object which can have an affine transformation applied to it
///
/// Used for moving, rotating or scaling primitives
/*pub trait Transform {
/// Create a new object by applying the transformation to this object.
fn transform(&self, transformation: &Affine3<f64>) -> Self;
}*/
/// A basic geometric primitive such as a sphere or a triangle
pub trait Primitive: Intersect + HasBoundingBox {
// / Create a new object by applying the transformation to this object.
//fn transform(&self, transformation: &Affine3) -> dyn Primitive;
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive>;
}
/// Either a primitive or a collection of primitives
pub trait Aggregate: Intersect + HasBoundingBox {}
#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;

View File

@ -79,21 +79,9 @@ impl HasBoundingBox for Plane {
let p0 = self.normal * self.distance_from_origin;
let f = |v: Vec3<f64>| {
Vec3::new(
if v.x() == 0.0 {
0.0
} else {
f64::INFINITY
},
if v.y() == 0.0 {
0.0
} else {
f64::INFINITY
},
if v.z() == 0.0 {
0.0
} else {
f64::INFINITY
},
if v.x() == 0.0 { 0.0 } else { f64::INFINITY },
if v.y() == 0.0 { 0.0 } else { f64::INFINITY },
if v.z() == 0.0 { 0.0 } else { f64::INFINITY },
)
};
let tangent = f(self.tangent);
@ -106,7 +94,11 @@ impl HasBoundingBox for Plane {
}
}
impl Primitive for Plane {}
impl Primitive for Plane {
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
todo!()
}
}
#[cfg(test)]
mod tests {

View File

@ -99,7 +99,11 @@ impl HasBoundingBox for Sphere {
}
}
impl Primitive for Sphere {}
impl Primitive for Sphere {
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
todo!()
}
}
#[cfg(test)]
mod tests {

View File

@ -103,7 +103,11 @@ impl HasBoundingBox for Triangle {
}
}
impl Primitive for Triangle {}
impl Primitive for Triangle {
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
todo!()
}
}
fn indices_with_index_of_largest_element_last(v: &Vec3<f64>) -> [usize; 3] {
#[allow(clippy::collapsible_else_if)]

View File

@ -1,4 +1,5 @@
use super::{Aggregate, BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
use crate::math::Affine3;
impl HasBoundingBox for Vec<Box<dyn Primitive>> {
fn bounding_box(&self) -> BoundingBox {
@ -21,27 +22,8 @@ impl Intersect for Vec<Box<dyn Primitive>> {
}
}
impl Aggregate for Vec<Box<dyn Primitive>> {}
impl HasBoundingBox for Vec<Box<dyn Aggregate>> {
fn bounding_box(&self) -> BoundingBox {
self.iter().fold(BoundingBox::empty(), |acc, elem| {
acc.union(&elem.bounding_box())
})
impl Primitive for Vec<Box<dyn Primitive>> {
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive> {
todo!()
}
}
impl Intersect for Vec<Box<dyn Aggregate>> {
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
self.iter()
.flat_map(|aggregate| aggregate.intersect(ray))
.min_by(
|a, b| match PartialOrd::partial_cmp(&a.distance, &b.distance) {
None => std::cmp::Ordering::Less,
Some(ordering) => ordering,
},
)
}
}
impl Aggregate for Vec<Box<dyn Aggregate>> {}

View File

@ -1,8 +1,8 @@
use crate::math::Vec3;
use crate::raycasting::Aggregate;
use crate::raycasting::Primitive;
pub struct Scene {
pub camera_location: Vec3<f64>,
pub objects: Vec<Box<dyn Aggregate>>,
pub objects: Vec<Box<dyn Primitive>>,
}