Compare commits
13 Commits
12c2382327
...
4bb6a72e4b
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bb6a72e4b | |||
| cb1b3ec801 | |||
| ad7b10322f | |||
| 6e73bab37f | |||
| 87bf924094 | |||
| 9f22d820e7 | |||
| 324a26212a | |||
| c9b42d94b3 | |||
| 0ce1e8f7af | |||
| 8b451a2395 | |||
| da98744288 | |||
| f44d671573 | |||
| 191760fa13 |
@ -24,20 +24,18 @@ fn main() -> Result<()> {
|
||||
let w = 200;
|
||||
let h = w;
|
||||
let mut c = Canvas::new(w, h);
|
||||
let t = Matrix4x4::translate(0., 0.4, 0.);
|
||||
let t = Matrix4x4::translation(0., 0.4, 0.);
|
||||
let p = Tuple::point(0., 0., 0.);
|
||||
let rot_hour = Matrix4x4::rotation_z(-PI / 6.);
|
||||
|
||||
let mut p = t * p;
|
||||
let w = w as f32;
|
||||
let h = h as f32;
|
||||
let h_w = w / 2.0;
|
||||
let h_h = h / 2.0;
|
||||
// The 'world' exists between -0.5 - 0.5 in X-Y plane.
|
||||
// To convert to screen space, we translate by 0.5, scale to canvas size,
|
||||
// and invert the Y-axis.
|
||||
let world_to_screen =
|
||||
Matrix4x4::scaling(w as f32, -h as f32, 1.0) * Matrix4x4::translate(0.5, -0.5, 0.);
|
||||
Matrix4x4::scaling(w as f32, -h as f32, 1.0) * Matrix4x4::translation(0.5, -0.5, 0.);
|
||||
for _ in 0..12 {
|
||||
let canvas_pixel = world_to_screen * p;
|
||||
draw_dot(&mut c, canvas_pixel.x as usize, canvas_pixel.y as usize);
|
||||
|
||||
41
rtchallenge/examples/eoc5.rs
Normal file
41
rtchallenge/examples/eoc5.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use core::f32;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use rtchallenge::{
|
||||
canvas::Canvas,
|
||||
rays::Ray,
|
||||
spheres::{intersect, Sphere},
|
||||
tuples::{Color, Tuple},
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let w = 200;
|
||||
let h = w;
|
||||
let mut c = Canvas::new(w, h);
|
||||
let ray_origin = Tuple::point(0., 0., -5.);
|
||||
let wall_z = 10.;
|
||||
let wall_size = 7.;
|
||||
let pixel_size = wall_size / w as f32;
|
||||
let half = wall_size / 2.;
|
||||
let color = Color::new(1., 0., 0.);
|
||||
let shape = Sphere::default();
|
||||
|
||||
for y in 0..h {
|
||||
let world_y = half - pixel_size * y as f32;
|
||||
for x in 0..w {
|
||||
let world_x = -half + pixel_size * x as f32;
|
||||
let position = Tuple::point(world_x, world_y, wall_z);
|
||||
let r = Ray::new(ray_origin, (position - ray_origin).normalize());
|
||||
let xs = intersect(&shape, &r);
|
||||
if xs.hit().is_some() {
|
||||
c.set(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = "/tmp/eoc5.png";
|
||||
println!("saving output to {}", path);
|
||||
c.write_to_file(path)?;
|
||||
Ok(())
|
||||
}
|
||||
122
rtchallenge/src/intersections.rs
Normal file
122
rtchallenge/src/intersections.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::ops::Index;
|
||||
|
||||
use crate::spheres::Sphere;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Intersection<'i> {
|
||||
pub t: f32,
|
||||
pub object: &'i Sphere,
|
||||
}
|
||||
|
||||
impl<'i> Intersection<'i> {
|
||||
/// Create new `Intersection` at the given `t` that hits the given `object`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{intersections::Intersection, spheres::Sphere};
|
||||
///
|
||||
/// // An intersection ecapsulates t and object.
|
||||
/// let s = Sphere::default();
|
||||
/// let i = Intersection::new(3.5, &s);
|
||||
/// assert_eq!(i.t, 3.5);
|
||||
/// assert_eq!(i.object, &s);
|
||||
/// ```
|
||||
pub fn new(t: f32, object: &Sphere) -> Intersection {
|
||||
Intersection { t, object }
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggregates `Intersection`s.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// spheres::{intersect, Sphere},
|
||||
/// tuples::Tuple,
|
||||
/// };
|
||||
///
|
||||
/// let s = Sphere::default();
|
||||
/// let i1 = Intersection::new(1., &s);
|
||||
/// let i2 = Intersection::new(2., &s);
|
||||
/// let xs = Intersections::new(vec![i1, i2]);
|
||||
/// assert_eq!(xs.len(), 2);
|
||||
/// assert_eq!(xs[0].t, 1.);
|
||||
/// assert_eq!(xs[1].t, 2.);
|
||||
///
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(xs.len(), 2);
|
||||
/// assert_eq!(xs[0].object, &s);
|
||||
/// assert_eq!(xs[1].object, &s);
|
||||
/// ```
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Intersections<'i>(Vec<Intersection<'i>>);
|
||||
|
||||
impl<'i> Intersections<'i> {
|
||||
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
|
||||
Intersections(xs)
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
/// Finds nearest hit for this collection of intersections.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// spheres::{intersect, Sphere},
|
||||
/// tuples::Tuple,
|
||||
/// };
|
||||
///
|
||||
/// // The hit, when all intersections have positive t.
|
||||
/// let s = Sphere::default();
|
||||
/// let i1 = Intersection::new(1., &s);
|
||||
/// let i2 = Intersection::new(2., &s);
|
||||
/// let xs = Intersections::new(vec![i2, i1.clone()]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i1));
|
||||
///
|
||||
/// // The hit, when some intersections have negative t.
|
||||
/// let s = Sphere::default();
|
||||
/// let i1 = Intersection::new(-1., &s);
|
||||
/// let i2 = Intersection::new(1., &s);
|
||||
/// let xs = Intersections::new(vec![i2.clone(), i1]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i2));
|
||||
///
|
||||
/// // The hit, when all intersections have negative t.
|
||||
/// let s = Sphere::default();
|
||||
/// let i1 = Intersection::new(-2., &s);
|
||||
/// let i2 = Intersection::new(-1., &s);
|
||||
/// let xs = Intersections::new(vec![i2, i1]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, None);
|
||||
///
|
||||
/// // The hit is always the lowest nonnegative intersection.
|
||||
/// let s = Sphere::default();
|
||||
/// let i1 = Intersection::new(5., &s);
|
||||
/// let i2 = Intersection::new(7., &s);
|
||||
/// let i3 = Intersection::new(-3., &s);
|
||||
/// let i4 = Intersection::new(2., &s);
|
||||
/// let xs = Intersections::new(vec![i1, i2, i3, i4.clone()]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i4));
|
||||
/// ```
|
||||
pub fn hit(&self) -> Option<&Intersection> {
|
||||
self.0.iter().filter(|i| i.t > 0.).min_by(|i1, i2| {
|
||||
i1.t.partial_cmp(&i2.t)
|
||||
.expect("an intersection has a t value that is NaN")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i> Index<usize> for Intersections<'i> {
|
||||
type Output = Intersection<'i>;
|
||||
fn index(&self, idx: usize) -> &Self::Output {
|
||||
&self.0[idx]
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
pub mod canvas;
|
||||
pub mod intersections;
|
||||
pub mod matrices;
|
||||
pub mod rays;
|
||||
pub mod spheres;
|
||||
pub mod tuples;
|
||||
|
||||
/// Value considered close enough for PartialEq implementations.
|
||||
|
||||
@ -175,7 +175,7 @@ impl PartialEq for Matrix3x3 {
|
||||
/// let p = Tuple::point(1., 0., 1.);
|
||||
/// let a = Matrix4x4::rotation_x(PI / 2.);
|
||||
/// let b = Matrix4x4::scaling(5., 5., 5.);
|
||||
/// let c = Matrix4x4::translate(10., 5., 7.);
|
||||
/// let c = Matrix4x4::translation(10., 5., 7.);
|
||||
/// // Apply rotation first.
|
||||
/// let p2 = a * p;
|
||||
/// assert_eq!(p2, Tuple::point(1., -1., 0.));
|
||||
@ -190,7 +190,7 @@ impl PartialEq for Matrix3x3 {
|
||||
/// let p = Tuple::point(1., 0., 1.);
|
||||
/// let a = Matrix4x4::rotation_x(PI / 2.);
|
||||
/// let b = Matrix4x4::scaling(5., 5., 5.);
|
||||
/// let c = Matrix4x4::translate(10., 5., 7.);
|
||||
/// let c = Matrix4x4::translation(10., 5., 7.);
|
||||
/// let t = c * b * a;
|
||||
/// assert_eq!(t * p, Tuple::point(15., 0., 7.));
|
||||
/// ```
|
||||
@ -251,7 +251,7 @@ impl Matrix4x4 {
|
||||
/// ```
|
||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// let transform = Matrix4x4::translate(5., -3., 2.);
|
||||
/// let transform = Matrix4x4::translation(5., -3., 2.);
|
||||
/// let p = Tuple::point(-3., 4., 5.);
|
||||
/// assert_eq!(transform * p, Tuple::point(2., 1., 7.));
|
||||
///
|
||||
@ -261,7 +261,7 @@ impl Matrix4x4 {
|
||||
/// let v = Tuple::vector(-3., 4., 5.);
|
||||
/// assert_eq!(transform * v, v);
|
||||
/// ```
|
||||
pub fn translate(x: f32, y: f32, z: f32) -> Matrix4x4 {
|
||||
pub fn translation(x: f32, y: f32, z: f32) -> Matrix4x4 {
|
||||
Matrix4x4::new(
|
||||
[1., 0., 0., x],
|
||||
[0., 1., 0., y],
|
||||
|
||||
71
rtchallenge/src/rays.rs
Normal file
71
rtchallenge/src/rays.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use crate::{matrices::Matrix4x4, tuples::Tuple};
|
||||
|
||||
/// Rays have an origin and a direction. This datatype is the 'ray' in 'raytracer'.
|
||||
pub struct Ray {
|
||||
pub origin: Tuple,
|
||||
pub direction: Tuple,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
/// Create a ray with the given origin point and direction vector.
|
||||
/// Will panic if origin not a point or direction not a vector.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{rays::Ray, tuples::Tuple};
|
||||
///
|
||||
/// let origin = Tuple::point(1., 2., 3.);
|
||||
/// let direction = Tuple::vector(4., 5., 6.);
|
||||
/// let r = Ray::new(origin, direction);
|
||||
/// assert_eq!(r.origin, origin);
|
||||
/// assert_eq!(r.direction, direction);
|
||||
/// ```
|
||||
pub fn new(origin: Tuple, direction: Tuple) -> Ray {
|
||||
assert!(origin.is_point(), "Ray origin must be a point");
|
||||
assert!(direction.is_vector(), "Ray direction must be a vector");
|
||||
Ray { origin, direction }
|
||||
}
|
||||
|
||||
/// Compute a point from the given distance along the `Ray`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{rays::Ray, tuples::Tuple};
|
||||
///
|
||||
/// let r = Ray::new(Tuple::point(2., 3., 4.), Tuple::vector(1., 0., 0.));
|
||||
/// assert_eq!(r.position(0.), Tuple::point(2., 3., 4.));
|
||||
/// assert_eq!(r.position(1.), Tuple::point(3., 3., 4.));
|
||||
/// assert_eq!(r.position(-1.), Tuple::point(1., 3., 4.));
|
||||
/// assert_eq!(r.position(2.5), Tuple::point(4.5, 3., 4.));
|
||||
/// ```
|
||||
pub fn position(&self, t: f32) -> Tuple {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
|
||||
/// Apply Matrix4x4 transforms to Ray.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{matrices::Matrix4x4, rays::Ray, tuples::Tuple};
|
||||
///
|
||||
/// // Translating a ray
|
||||
/// let r = Ray::new(Tuple::point(1., 2., 3.), Tuple::vector(0., 1., 0.));
|
||||
/// let m = Matrix4x4::translation(3., 4., 5.);
|
||||
/// let r2 = r.transform(m);
|
||||
/// assert_eq!(r2.origin, Tuple::point(4., 6., 8.));
|
||||
/// assert_eq!(r2.direction, Tuple::vector(0., 1., 0.));
|
||||
///
|
||||
/// // Scaling a ray
|
||||
/// let r = Ray::new(Tuple::point(1., 2., 3.), Tuple::vector(0., 1., 0.));
|
||||
/// let m = Matrix4x4::scaling(2., 3., 4.);
|
||||
/// let r2 = r.transform(m);
|
||||
/// assert_eq!(r2.origin, Tuple::point(2., 6., 12.));
|
||||
/// assert_eq!(r2.direction, Tuple::vector(0., 3., 0.));
|
||||
/// ```
|
||||
pub fn transform(&self, m: Matrix4x4) -> Ray {
|
||||
Ray {
|
||||
origin: m * self.origin,
|
||||
direction: m * self.direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
113
rtchallenge/src/spheres.rs
Normal file
113
rtchallenge/src/spheres.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::{
|
||||
intersections::{Intersection, Intersections},
|
||||
matrices::Matrix4x4,
|
||||
rays::Ray,
|
||||
tuples::{dot, Tuple},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
|
||||
pub struct Sphere {
|
||||
// TODO(wathiede): cache inverse to speed up intersect.
|
||||
pub transform: Matrix4x4,
|
||||
}
|
||||
|
||||
impl Default for Sphere {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{matrices::Matrix4x4, spheres::Sphere};
|
||||
///
|
||||
/// // A sphere's default transform is the identity matrix.
|
||||
/// let s = Sphere::default();
|
||||
/// assert_eq!(s.transform, Matrix4x4::identity());
|
||||
///
|
||||
/// // It can be changed by directly setting the transform member.
|
||||
/// let mut s = Sphere::default();
|
||||
/// let t = Matrix4x4::translation(2., 3., 4.);
|
||||
/// s.transform = t.clone();
|
||||
/// assert_eq!(s.transform, t);
|
||||
/// ```
|
||||
fn default() -> Sphere {
|
||||
Sphere {
|
||||
transform: Matrix4x4::identity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersect a ray with a sphere.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{Intersection, Intersections},
|
||||
/// matrices::Matrix4x4,
|
||||
/// rays::Ray,
|
||||
/// spheres::{intersect, Sphere},
|
||||
/// tuples::Tuple,
|
||||
/// };
|
||||
///
|
||||
/// // A ray intersects a sphere in two points.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = Sphere::default();
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(
|
||||
/// xs,
|
||||
/// Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)])
|
||||
/// );
|
||||
///
|
||||
/// // A ray intersects a sphere at a tangent.
|
||||
/// let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = Sphere::default();
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(xs, Intersections::default());
|
||||
///
|
||||
/// // A ray originates inside a sphere.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = Sphere::default();
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(
|
||||
/// xs,
|
||||
/// Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)])
|
||||
/// );
|
||||
///
|
||||
/// // A sphere is behind a ray.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = Sphere::default();
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(
|
||||
/// xs,
|
||||
/// Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)])
|
||||
/// );
|
||||
///
|
||||
/// // Intersect a scaled sphere with a ray.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut s = Sphere::default();
|
||||
/// s.transform = Matrix4x4::scaling(2., 2., 2.);
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(xs.len(), 2, "xs {:?}", xs);
|
||||
/// assert_eq!(xs[0].t, 3., "xs {:?}", xs);
|
||||
/// assert_eq!(xs[1].t, 7., "xs {:?}", xs);
|
||||
///
|
||||
/// // Intersect a translated sphere with a ray.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut s = Sphere::default();
|
||||
/// s.transform = Matrix4x4::translation(5., 0., 0.);
|
||||
/// let xs = intersect(&s, &r);
|
||||
/// assert_eq!(xs.len(), 0);
|
||||
/// ```
|
||||
pub fn intersect<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
|
||||
let ray = ray.transform(sphere.transform.inverse());
|
||||
let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.);
|
||||
let a = dot(ray.direction, ray.direction);
|
||||
let b = 2. * dot(ray.direction, sphere_to_ray);
|
||||
let c = dot(sphere_to_ray, sphere_to_ray) - 1.;
|
||||
let discriminant = b * b - 4. * a * c;
|
||||
if discriminant < 0. {
|
||||
Intersections::default()
|
||||
} else {
|
||||
Intersections::new(vec![
|
||||
Intersection::new((-b - discriminant.sqrt()) / (2. * a), &sphere),
|
||||
Intersection::new((-b + discriminant.sqrt()) / (2. * a), &sphere),
|
||||
])
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user