Start building the intersection tests

This commit is contained in:
Savanni D'Gerinel 2024-06-14 09:33:30 -04:00
parent bd899e3a2e
commit 8e4f6b06e6
4 changed files with 193 additions and 0 deletions

View File

View File

@ -2,6 +2,8 @@ mod canvas;
mod color;
mod matrix;
mod point;
mod ray;
mod sphere;
mod tuple;
mod vector;
@ -9,6 +11,8 @@ pub use canvas::Canvas;
pub use color::Color;
pub use matrix::Matrix;
pub use point::Point;
pub use ray::Ray;
pub use sphere::Sphere;
pub use tuple::Tuple;
pub use vector::Vector;

176
ray-tracer/src/types/ray.rs Normal file
View File

@ -0,0 +1,176 @@
use std::cmp::Ordering;
use crate::types::{Point, Sphere, Vector};
#[derive(Clone, Debug, PartialEq)]
pub struct Intersection<'a> {
t: f64,
object: &'a Sphere,
}
pub struct Intersections<'a>(Vec<Intersection<'a>>);
impl <'a> Intersections<'a> {
pub fn len(&'a self) -> usize {
self.0.len()
}
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
self.0.iter().find(|i| i.t >= 0.)
}
}
impl <'a> std::ops::Index<usize> for Intersections<'a> {
type Output = Intersection<'a>;
fn index(&self, idx: usize) -> &Intersection<'a> {
&self.0[idx]
}
}
impl <'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
fn from(mut v: Vec<Intersection<'a>>) -> Self {
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
Self(v)
}
}
pub struct Ray {
origin: Point,
direction: Vector,
}
impl Ray {
pub fn new(origin: Point, direction: Vector) -> Self {
Self { origin, direction }
}
pub fn position(&self, t: f64) -> Point {
self.origin + self.direction * t
}
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
let sphere_to_ray = self.origin - Point::new(0., 0., 0.);
let a = self.direction.dot(&self.direction);
let b = 2. * self.direction.dot(&sphere_to_ray);
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
let discriminant = b * b - 4. * a * c;
if discriminant < 0. {
return vec![].into();
}
let t1 = (-b - discriminant.sqrt()) / (2. * a);
let t2 = (-b + discriminant.sqrt()) / (2. * a);
vec![
Intersection { t: t1, object: &s },
Intersection { t: t2, object: &s },
].into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn computing_point_from_distance() {
let r = Ray::new(Point::new(2., 3., 4.), Vector::new(1., 0., 0.));
assert_eq!(r.position(0.), Point::new(2., 3., 4.));
assert_eq!(r.position(1.), Point::new(3., 3., 4.));
assert_eq!(r.position(-1.), Point::new(1., 3., 4.));
assert_eq!(r.position(2.5), Point::new(4.5, 3., 4.));
}
#[test]
fn ray_intersects_sphere_at_two_points() {
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 4.);
assert_eq!(xs[1].t, 6.);
assert_eq!(*xs[0].object, s);
assert_eq!(*xs[1].object, s);
}
#[test]
fn ray_tangents_sphere_at_one_point() {
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 5.);
assert_eq!(xs[1].t, 5.);
}
#[test]
fn ray_misses_the_sphere() {
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 0);
}
#[test]
fn ray_originates_inside_the_sphere() {
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -1.);
assert_eq!(xs[1].t, 1.);
}
#[test]
fn sphere_is_behind_the_ray() {
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -6.);
assert_eq!(xs[1].t, -4.);
}
#[test]
fn hit_all_intersections_are_positive() {
let s = Sphere::new();
let i1 = Intersection{ t: 1., object: &s };
let i2 = Intersection{ t: 2., object: &s };
let xs = Intersections::from(vec![i1.clone(), i2]);
assert_eq!(xs.hit(), Some(&i1));
}
#[test]
fn hit_some_intersections_are_negative() {
let s = Sphere::new();
let i1 = Intersection{ t: -1., object: &s };
let i2 = Intersection{ t: 1., object: &s };
let xs = Intersections::from(vec![i1, i2.clone()]);
assert_eq!(xs.hit(), Some(&i2));
}
#[test]
fn hit_all_intersections_are_negative() {
let s = Sphere::new();
let i1 = Intersection{ t: -2., object: &s };
let i2 = Intersection{ t: -1., object: &s };
let xs = Intersections::from(vec![i1, i2]);
assert_eq!(xs.hit(), None);
}
#[test]
fn hit_is_always_lowest_nonnegative() {
let s = Sphere::new();
let i1 = Intersection{ t: 5., object: &s };
let i2 = Intersection{ t: 7., object: &s };
let i3 = Intersection{ t: -3., object: &s };
let i4 = Intersection{ t: 2., object: &s };
let xs = Intersections::from(vec![i1, i2, i3, i4.clone()]);
assert_eq!(xs.hit(), Some(&i4));
}
}

View File

@ -0,0 +1,13 @@
use crate::types::Point;
#[derive(Debug, PartialEq)]
pub struct Sphere {
origin: Point,
}
impl Sphere {
pub fn new() -> Self {
Self{ origin: Point::new(0., 0., 0.) }
}
}