Skip to content

Commit

Permalink
Use transforms in spheres
Browse files Browse the repository at this point in the history
  • Loading branch information
banga committed Apr 19, 2023
1 parent 70df53f commit c2f5bc6
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 56 deletions.
17 changes: 10 additions & 7 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
TODO

[ ] Transformations
[ ] Area lights
[ ] Textures
[ ] Film
[ ] Thin lens camera
[ ] Samplers
[ ] Spectrum
- [ ] Transformations
- [x] Matrix
- [x] Spheres
- [ ] Triangles
- [ ] Area lights
- [ ] Textures
- [ ] Film
- [ ] Thin lens camera
- [ ] Samplers
- [ ] Spectrum
1 change: 1 addition & 0 deletions src/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
material::Material,
};

#[derive(Debug, PartialEq)]
pub struct ShapeIntersection {
pub location: Point,
// TODO: Get the normal lazily when needed
Expand Down
11 changes: 8 additions & 3 deletions src/ray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ impl Ray {
max_distance: f64::INFINITY,
}
}
pub fn update_max_distance(&mut self, distance: f64) -> Option<Point> {

pub fn at(&self, distance: f64) -> Point {
self.origin + self.direction * distance
}

pub fn update_max_distance(&mut self, distance: f64) -> bool {
if distance > EPSILON && distance < self.max_distance {
self.max_distance = distance;
Some(self.origin + self.direction * distance)
true
} else {
None
false
}
}
}
65 changes: 36 additions & 29 deletions src/shape.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use crate::{
bounds::Bounds,
constants::EPSILON,
geometry::{point::Point, traits::DotProduct, vector::Vector},
geometry::{normal::Normal, point::Point, traits::DotProduct, vector::Vector},
intersection::ShapeIntersection,
ray::Ray,
transformation::{Transformable, Transformation},
};

#[derive(Debug, PartialEq)]
pub enum Shape {
Sphere {
origin: Point,
object_to_world: Transformation,
world_to_object: Transformation,
radius: f64,
radius_squared: f64,
inv_radius: f64,
Expand All @@ -27,10 +29,11 @@ pub enum Shape {
impl Shape {
pub fn new_sphere(origin: Point, radius: f64) -> Shape {
Shape::Sphere {
origin,
radius,
radius_squared: radius * radius,
inv_radius: 1.0 / radius,
object_to_world: Transformation::translate(origin.x(), origin.y(), origin.z()),
world_to_object: Transformation::translate(-origin.x(), -origin.y(), -origin.z()),
}
}
pub fn new_triangle(v0: Point, v1: Point, v2: Point) -> Shape {
Expand Down Expand Up @@ -76,15 +79,18 @@ impl Shape {
pub fn intersect(&self, ray: &mut Ray) -> Option<ShapeIntersection> {
match self {
Shape::Sphere {
origin,
object_to_world,
world_to_object,
radius_squared,
inv_radius,
..
} => {
let oc = ray.origin - *origin;
let a = ray.direction.magnitude_squared();
let b = 2.0 * oc.dot(&ray.direction);
let c = oc.magnitude_squared() - radius_squared;
let mut obj_ray = world_to_object.transform(ray);

let oc = Vector(obj_ray.origin.x(), obj_ray.origin.y(), obj_ray.origin.z());
let a = obj_ray.direction.magnitude_squared();
let b = 2.0 * oc.dot(&obj_ray.direction);
let c = oc.magnitude_squared() - *radius_squared;
let discriminant = b * b - 4.0 * a * c;

if discriminant < 0.0 {
Expand All @@ -94,19 +100,23 @@ impl Shape {
let discriminant_sqrt = discriminant.sqrt();
let inv_2_a = 1.0 / (2.0 * a);
let mut distance = (-b - discriminant_sqrt) * inv_2_a;
if let Some(location) = ray.update_max_distance(distance) {
return Some(ShapeIntersection {
if obj_ray.update_max_distance(distance) {
let location = obj_ray.at(distance);
ray.update_max_distance(distance);
return Some(object_to_world.transform(&ShapeIntersection {
location,
normal: ((location - *origin) * *inv_radius).into(),
});
normal: Normal(location.x(), location.y(), location.z()) * *inv_radius,
}));
}

distance = (-b + discriminant_sqrt) * inv_2_a;
if let Some(location) = ray.update_max_distance(distance) {
return Some(ShapeIntersection {
if obj_ray.update_max_distance(distance) {
let location = obj_ray.at(distance);
ray.update_max_distance(distance);
return Some(object_to_world.transform(&ShapeIntersection {
location,
normal: ((location - *origin) * *inv_radius).into(),
});
normal: Normal(location.x(), location.y(), location.z()) * *inv_radius,
}));
}

None
Expand Down Expand Up @@ -141,7 +151,8 @@ impl Shape {
}

let distance = T.cross(e1).dot(e2) * inv_denominator;
if let Some(location) = ray.update_max_distance(distance) {
if ray.update_max_distance(distance) {
let location = ray.at(distance);
Some(ShapeIntersection {
location,
normal: (*n0 + *n01 * u + *n02 * v).normalized().into(),
Expand All @@ -154,18 +165,14 @@ impl Shape {
}
pub fn bounds(&self) -> Bounds {
match self {
Shape::Sphere { origin, radius, .. } => Bounds::new(
Point(
origin.x() - radius,
origin.y() - radius,
origin.z() - radius,
),
Point(
origin.x() + radius,
origin.y() + radius,
origin.z() + radius,
),
),
Shape::Sphere {
object_to_world,
radius,
..
} => object_to_world.transform(&Bounds::new(
Point(-radius, -radius, -radius),
Point(*radius, *radius, *radius),
)),
Shape::Triangle { v0, e1, e2, .. } => {
let v1 = *v0 + *e1;
let v2 = *v0 + *e2;
Expand Down
45 changes: 37 additions & 8 deletions src/transformation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use crate::{
bounds::Bounds,
constants::EPSILON,
geometry::{normal::Normal, point::Point, vector::Vector},
intersection::ShapeIntersection,
ray::Ray,
};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Matrix {
pub m: [[f64; 4]; 4],
}
Expand Down Expand Up @@ -243,27 +244,35 @@ impl Display for Matrix {
}
}

#[derive(Debug, PartialEq, Clone)]
pub struct Transformation {
pub matrix: Matrix,
pub inverse: Matrix,
}

impl Transformation {
pub fn translate(delta: &Vector) -> Self {
pub fn inverse(&self) -> Self {
Transformation {
matrix: self.inverse.clone(),
inverse: self.matrix.clone(),
}
}

pub fn translate(dx: f64, dy: f64, dz: f64) -> Self {
Transformation {
matrix: Matrix {
m: [
[1.0, 0.0, 0.0, delta.x()],
[0.0, 1.0, 0.0, delta.y()],
[0.0, 0.0, 1.0, delta.z()],
[1.0, 0.0, 0.0, dx],
[0.0, 1.0, 0.0, dy],
[0.0, 0.0, 1.0, dz],
[0.0, 0.0, 0.0, 1.0],
],
},
inverse: Matrix {
m: [
[1.0, 0.0, 0.0, -delta.x()],
[0.0, 1.0, 0.0, -delta.y()],
[0.0, 0.0, 1.0, -delta.z()],
[1.0, 0.0, 0.0, -dx],
[0.0, 1.0, 0.0, -dy],
[0.0, 0.0, 1.0, -dz],
[0.0, 0.0, 0.0, 1.0],
],
},
Expand Down Expand Up @@ -340,6 +349,17 @@ impl Transformation {
}
}

impl Mul for &Transformation {
type Output = Transformation;

fn mul(self, rhs: Self) -> Transformation {
Transformation {
matrix: &self.matrix * &rhs.matrix,
inverse: &rhs.inverse * &self.inverse,
}
}
}

pub trait Transformable<T> {
fn transform(&self, t: &T) -> T;
}
Expand Down Expand Up @@ -406,3 +426,12 @@ impl Transformable<Bounds> for Transformation {
.sum()
}
}

impl Transformable<ShapeIntersection> for Transformation {
fn transform(&self, intersection: &ShapeIntersection) -> ShapeIntersection {
ShapeIntersection {
location: self.transform(&intersection.location),
normal: self.transform(&intersection.normal),
}
}
}
43 changes: 42 additions & 1 deletion tests/test_shape.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
mod sphere {
use craytracer::{bounds::Bounds, geometry::point::Point, shape::Shape};
use craytracer::{
bounds::Bounds,
geometry::{normal::Normal, point::Point, vector::Vector},
intersection::ShapeIntersection,
ray::Ray,
shape::Shape,
};
use pretty_assertions::assert_eq;

#[test]
fn intersect() {
let s = Shape::new_sphere(Point(1.0, 2.0, 3.0), 2.0);

let ray = &mut Ray::new(Point(1.0, 2.0, 0.0), Vector::Z);
let intersection = s.intersect(ray);
assert_eq!(
intersection,
Some(ShapeIntersection {
location: Point(1.0, 2.0, 1.0),
normal: Normal(0.0, 0.0, -1.0),
})
);

let ray = &mut Ray::new(Point(-3.0, 2.0, 3.0), Vector::X);
let intersection = s.intersect(ray);
assert_eq!(
intersection,
Some(ShapeIntersection {
location: Point(-1.0, 2.0, 3.0),
normal: Normal(-1.0, 0.0, 0.0),
})
);

let ray = &mut Ray::new(Point(1.0, -2.0, 3.0), Vector::Y);
let intersection = s.intersect(ray);
assert_eq!(
intersection,
Some(ShapeIntersection {
location: Point(1.0, 0.0, 3.0),
normal: Normal(0.0, -1.0, 0.0),
})
);
}

#[test]
fn bounds() {
assert_eq!(
Expand Down
16 changes: 8 additions & 8 deletions tests/test_transformation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod matrix {
use pretty_assertions::assert_eq;

#[test]
pub fn test_mul() {
pub fn mul() {
let m1 = Matrix::new([
// Dürer's magic square
[16, 3, 2, 13],
Expand Down Expand Up @@ -35,7 +35,7 @@ pub mod matrix {
}

#[test]
pub fn test_inverse() {
pub fn inverse() {
let m = Matrix::new([
//
[1, 3, 5, 4],
Expand Down Expand Up @@ -79,8 +79,8 @@ pub mod transformation {
use pretty_assertions::assert_eq;

#[test]
pub fn test_translation() {
let t = Transformation::translate(&Vector(5.0, -3.0, 2.0));
pub fn translation() {
let t = Transformation::translate(5.0, -3.0, 2.0);

assert_eq!(t.transform(&Point(-3.0, 4.0, 5.0)), Point(2.0, 1.0, 7.0));
assert_eq!(t.transform(&Vector(-3.0, 4.0, 5.0)), Vector(-3.0, 4.0, 5.0));
Expand All @@ -96,7 +96,7 @@ pub mod transformation {
}

#[test]
pub fn test_scale() {
pub fn scale() {
let t = Transformation::scale(2.0, -3.0, 0.5);

assert_eq!(t.transform(&Point(-3.0, 4.0, 5.0)), Point(-6.0, -12.0, 2.5));
Expand All @@ -119,7 +119,7 @@ pub mod transformation {
}

#[test]
pub fn test_rotate_x() {
pub fn rotate_x() {
let t = Transformation::rotate_x(90.0);

assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(2.0, -3.0, 1.0),);
Expand All @@ -132,7 +132,7 @@ pub mod transformation {
}

#[test]
pub fn test_rotate_y() {
pub fn rotate_y() {
let t = Transformation::rotate_y(90.0);

assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(3.0, 1.0, -2.0),);
Expand All @@ -145,7 +145,7 @@ pub mod transformation {
}

#[test]
pub fn test_rotate_z() {
pub fn rotate_z() {
let t = Transformation::rotate_z(90.0);

assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(-1.0, 2.0, 3.0),);
Expand Down

0 comments on commit c2f5bc6

Please sign in to comment.