shapes: create generic Shape object with Sphere implementation.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-07-19 19:51:12 -07:00
parent 7741766635
commit c0e422a7eb
8 changed files with 117 additions and 93 deletions

View File

@@ -2,7 +2,7 @@ use std::ops::Index;
use crate::{
rays::Ray,
spheres::Sphere,
shapes::Shape,
tuples::{dot, Tuple},
Float, EPSILON,
};
@@ -10,7 +10,7 @@ use crate::{
#[derive(Debug, Clone, PartialEq)]
pub struct Intersection<'i> {
pub t: Float,
pub object: &'i Sphere,
pub object: &'i Shape,
}
impl<'i> Intersection<'i> {
@@ -18,15 +18,15 @@ impl<'i> Intersection<'i> {
///
/// # Examples
/// ```
/// use rtchallenge::{intersections::Intersection, spheres::Sphere};
/// use rtchallenge::{intersections::Intersection, shapes::Shape};
///
/// // An intersection ecapsulates t and object.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i = Intersection::new(3.5, &s);
/// assert_eq!(i.t, 3.5);
/// assert_eq!(i.object, &s);
/// ```
pub fn new(t: Float, object: &Sphere) -> Intersection {
pub fn new(t: Float, object: &Shape) -> Intersection {
Intersection { t, object }
}
}
@@ -38,11 +38,11 @@ impl<'i> Intersection<'i> {
/// use rtchallenge::{
/// intersections::{Intersection, Intersections},
/// rays::Ray,
/// spheres::{intersect, Sphere},
/// shapes::{intersect, Shape},
/// tuples::Tuple,
/// };
///
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i1 = Intersection::new(1., &s);
/// let i2 = Intersection::new(2., &s);
/// let xs = Intersections::new(vec![i1, i2]);
@@ -73,12 +73,12 @@ impl<'i> Intersections<'i> {
/// use rtchallenge::{
/// intersections::{Intersection, Intersections},
/// rays::Ray,
/// spheres::{intersect, Sphere},
/// shapes::{intersect, Shape},
/// tuples::Tuple,
/// };
///
/// // The hit, when all intersections have positive t.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i1 = Intersection::new(1., &s);
/// let i2 = Intersection::new(2., &s);
/// let xs = Intersections::new(vec![i2, i1.clone()]);
@@ -86,7 +86,7 @@ impl<'i> Intersections<'i> {
/// assert_eq!(i, Some(&i1));
///
/// // The hit, when some intersections have negative t.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i1 = Intersection::new(-1., &s);
/// let i2 = Intersection::new(1., &s);
/// let xs = Intersections::new(vec![i2.clone(), i1]);
@@ -94,7 +94,7 @@ impl<'i> Intersections<'i> {
/// assert_eq!(i, Some(&i2));
///
/// // The hit, when all intersections have negative t.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i1 = Intersection::new(-2., &s);
/// let i2 = Intersection::new(-1., &s);
/// let xs = Intersections::new(vec![i2, i1]);
@@ -102,7 +102,7 @@ impl<'i> Intersections<'i> {
/// assert_eq!(i, None);
///
/// // The hit is always the lowest nonnegative intersection.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let i1 = Intersection::new(5., &s);
/// let i2 = Intersection::new(7., &s);
/// let i3 = Intersection::new(-3., &s);
@@ -138,7 +138,7 @@ impl<'i> Index<usize> for Intersections<'i> {
#[derive(Debug)]
pub struct PrecomputedData<'i> {
pub t: Float,
pub object: &'i Sphere,
pub object: &'i Shape,
pub point: Tuple,
pub over_point: Tuple,
pub eyev: Tuple,
@@ -154,14 +154,14 @@ pub struct PrecomputedData<'i> {
/// intersections::{prepare_computations, Intersection, Intersections},
/// rays::Ray,
/// matrices::Matrix4x4,
/// spheres::{intersect, Sphere},
/// shapes::{intersect, Shape},
/// tuples::Tuple,
/// EPSILON
/// };
///
/// // Precomputing the state of an intersection.
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let shape = Sphere::default();
/// let shape = Shape::sphere();
/// let i = Intersection::new(4., &shape);
/// let comps = prepare_computations(&i, &r);
/// assert_eq!(comps.t, i.t);
@@ -172,14 +172,14 @@ pub struct PrecomputedData<'i> {
///
/// // The hit, when an intersection occurs on the outside.
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let shape = Sphere::default();
/// let shape = Shape::sphere();
/// let i = Intersection::new(4., &shape);
/// let comps = prepare_computations(&i, &r);
/// assert_eq!(comps.inside, false);
///
/// // The hit, when an intersection occurs on the inside.
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
/// let shape = Sphere::default();
/// let shape = Shape::sphere();
/// let i = Intersection::new(1., &shape);
/// let comps = prepare_computations(&i, &r);
/// assert_eq!(comps.point, Tuple::point(0., 0., 1.));
@@ -190,7 +190,7 @@ pub struct PrecomputedData<'i> {
///
/// // The hit should offset the point.
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let mut shape = Sphere::default();
/// let mut shape = Shape::sphere();
/// shape .set_transform(Matrix4x4::translation(0.,0.,1.));
/// let i = Intersection::new(5., &shape);
/// let comps = prepare_computations(&i, &r);

View File

@@ -5,7 +5,7 @@ pub mod lights;
pub mod materials;
pub mod matrices;
pub mod rays;
pub mod spheres;
pub mod shapes;
pub mod transformations;
pub mod tuples;
pub mod world;

View File

@@ -7,71 +7,80 @@ use crate::{
};
#[derive(Debug, PartialEq, Clone)]
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
pub struct Sphere {
enum Geometry {
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
Sphere,
}
/// Shape represents visible objects. A signal instance of Shape can generically represent one of
/// many different shapes based on the value of it's geometry field. Users chose the shape by
/// calling the appropriate constructor, i.e. [Shape::sphere].
#[derive(Debug, PartialEq, Clone)]
pub struct Shape {
transform: Matrix4x4,
inverse_transform: Matrix4x4,
pub material: Material,
geometry: Geometry,
}
impl Default for Sphere {
impl Shape {
/// # Examples
/// ```
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, spheres::Sphere};
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
///
/// // A sphere's default transform is the identity matrix.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// assert_eq!(s.transform(), Matrix4x4::identity());
///
/// // It can be changed by directly setting the transform member.
/// let mut s = Sphere::default();
/// let mut s = Shape::sphere();
/// let t = Matrix4x4::translation(2., 3., 4.);
/// s.set_transform ( t.clone());
/// s.set_transform(t.clone());
/// assert_eq!(s.transform(), t);
///
/// // Default Sphere has the default material.
/// assert_eq!(s.material, Material::default());
/// // It can be overridden.
/// let mut s = Sphere::default();
/// let mut s = Shape::sphere();
/// let mut m = Material::default();
/// m.ambient = 1.;
/// s.material = m.clone();
/// assert_eq!(s.material, m);
/// ```
fn default() -> Sphere {
Sphere {
pub fn sphere() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Sphere,
}
}
}
impl Sphere {
/// Find the normal at the point on the sphere.
///
/// # Examples
/// ```
/// use rtchallenge::{matrices::Matrix4x4, spheres::Sphere, tuples::Tuple,Float};
/// use rtchallenge::{
/// float::consts::PI, materials::Material, matrices::Matrix4x4, shapes::Shape, tuples::Tuple,
/// Float,
/// };
///
/// // Normal on X-axis
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(1., 0., 0.));
/// assert_eq!(n, Tuple::vector(1., 0., 0.));
///
/// // Normal on Y-axis
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(0., 1., 0.));
/// assert_eq!(n, Tuple::vector(0., 1., 0.));
///
/// // Normal on Z-axis
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(0., 0., 1.));
/// assert_eq!(n, Tuple::vector(0., 0., 1.));
///
/// // Normal on a sphere at a nonaxial point.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(
/// (3. as Float).sqrt() / 3.,
/// (3. as Float).sqrt() / 3.,
@@ -79,10 +88,14 @@ impl Sphere {
/// ));
/// assert_eq!(
/// n,
/// Tuple::vector((3. as Float).sqrt() / 3., (3. as Float).sqrt() / 3., (3. as Float).sqrt() / 3.,)
/// Tuple::vector(
/// (3. as Float).sqrt() / 3.,
/// (3. as Float).sqrt() / 3.,
/// (3. as Float).sqrt() / 3.,
/// )
/// );
/// // Normals returned are normalized.
/// let s = Sphere::default();
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(
/// (3. as Float).sqrt() / 3.,
/// (3. as Float).sqrt() / 3.,
@@ -91,23 +104,27 @@ impl Sphere {
/// assert_eq!(n, n.normalize());
///
/// // Compute the normal on a translated sphere.
/// let mut s = Sphere::default();
/// s.set_transform ( Matrix4x4::translation(0., 1., 0.));
/// let mut s = Shape::sphere();
/// s.set_transform(Matrix4x4::translation(0., 1., 0.));
/// let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
/// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
/// // Compute the normal on a transformed sphere.
/// use rtchallenge::float::consts::PI;
///
/// let mut s = Sphere::default();
/// s.set_transform ( Matrix4x4::scaling(1.,0.5,1.) * Matrix4x4::rotation_z(PI/5.));
/// let n = s.normal_at(Tuple::point(0., (2. as Float).sqrt()/2., -(2. as Float).sqrt()/2.));
/// // Compute the normal on a transformed sphere.
/// let mut s = Shape::sphere();
/// s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
/// let n = s.normal_at(Tuple::point(
/// 0.,
/// (2. as Float).sqrt() / 2.,
/// -(2. as Float).sqrt() / 2.,
/// ));
/// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
/// ```
#[cfg(not(feature = "disable-inverse-cache"))]
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
let object_point = self.inverse_transform * world_point;
let object_normal = object_point - Tuple::point(0., 0., 0.);
let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
};
let mut world_normal = self.inverse_transform.transpose() * object_normal;
world_normal.w = 0.;
world_normal.normalize()
@@ -115,7 +132,9 @@ impl Sphere {
#[cfg(feature = "disable-inverse-cache")]
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
let object_point = self.transform.inverse() * world_point;
let object_normal = object_point - Tuple::point(0., 0., 0.);
let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
};
let mut world_normal = self.transform.inverse().transpose() * object_normal;
world_normal.w = 0.;
world_normal.normalize()
@@ -124,6 +143,7 @@ impl Sphere {
pub fn transform(&self) -> Matrix4x4 {
self.transform
}
pub fn set_transform(&mut self, t: Matrix4x4) {
self.transform = t;
self.inverse_transform = t.inverse();
@@ -138,13 +158,13 @@ impl Sphere {
/// intersections::{Intersection, Intersections},
/// matrices::Matrix4x4,
/// rays::Ray,
/// spheres::{intersect, Sphere},
/// shapes::{intersect, Shape},
/// 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 s = Shape::sphere();
/// let xs = intersect(&s, &r);
/// assert_eq!(
/// xs,
@@ -153,13 +173,13 @@ impl Sphere {
///
/// // 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 s = Shape::sphere();
/// 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 s = Shape::sphere();
/// let xs = intersect(&s, &r);
/// assert_eq!(
/// xs,
@@ -168,7 +188,7 @@ impl Sphere {
///
/// // 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 s = Shape::sphere();
/// let xs = intersect(&s, &r);
/// assert_eq!(
/// xs,
@@ -177,7 +197,7 @@ impl Sphere {
///
/// // 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();
/// let mut s = Shape::sphere();
/// s.set_transform(Matrix4x4::scaling(2., 2., 2.));
/// let xs = intersect(&s, &r);
/// assert_eq!(xs.len(), 2, "xs {:?}", xs);
@@ -186,17 +206,19 @@ impl Sphere {
///
/// // 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();
/// let mut s = Shape::sphere();
/// s.set_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> {
intersect_rtc(sphere, ray)
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
match shape.geometry {
Geometry::Sphere => intersect_rtc(shape, ray),
}
}
fn intersect_rtc<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
let ray = ray.transform(sphere.inverse_transform);
fn intersect_rtc<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let ray = ray.transform(shape.inverse_transform);
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);
@@ -206,12 +228,12 @@ fn intersect_rtc<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
return Intersections::default();
}
Intersections::new(vec![
Intersection::new((-b - discriminant.sqrt()) / (2. * a), &sphere),
Intersection::new((-b + discriminant.sqrt()) / (2. * a), &sphere),
Intersection::new((-b - discriminant.sqrt()) / (2. * a), &shape),
Intersection::new((-b + discriminant.sqrt()) / (2. * a), &shape),
])
}
fn intersect_rtiow<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
let ray = ray.transform(sphere.inverse_transform);
fn intersect_rtiow<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let ray = ray.transform(shape.inverse_transform);
let oc = ray.origin - Tuple::point(0., 0., 0.);
let a = dot(ray.direction, ray.direction);
let b = dot(oc, ray.direction);
@@ -221,7 +243,7 @@ fn intersect_rtiow<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
return Intersections::default();
}
Intersections::new(vec![
Intersection::new((-b - discriminant.sqrt()) / a, &sphere),
Intersection::new((-b + discriminant.sqrt()) / a, &sphere),
Intersection::new((-b - discriminant.sqrt()) / a, &shape),
Intersection::new((-b + discriminant.sqrt()) / a, &shape),
])
}

View File

@@ -4,7 +4,7 @@ use crate::{
materials::{lighting, Material},
matrices::Matrix4x4,
rays::Ray,
spheres::{intersect, Sphere},
shapes::{intersect, Shape},
tuples::{Color, Tuple},
Float, BLACK, WHITE,
};
@@ -23,7 +23,7 @@ use crate::{
#[derive(Clone, Debug, Default)]
pub struct World {
pub lights: Vec<PointLight>,
pub objects: Vec<Sphere>,
pub objects: Vec<Shape>,
}
impl World {
@@ -39,14 +39,14 @@ impl World {
/// ```
pub fn test_world() -> World {
let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE);
let mut s1 = Sphere::default();
let mut s1 = Shape::sphere();
s1.material = Material {
color: Color::new(0.8, 1., 0.6),
diffuse: 0.7,
specular: 0.2,
..Material::default()
};
let mut s2 = Sphere::default();
let mut s2 = Shape::sphere();
s2.set_transform(Matrix4x4::scaling(0.5, 0.5, 0.5));
World {
lights: vec![light],
@@ -92,7 +92,7 @@ impl World {
/// lights::PointLight,
/// matrices::Matrix4x4,
/// rays::Ray,
/// spheres::Sphere,
/// shapes::Shape,
/// tuples::{Color, Tuple},
/// world::World,
/// WHITE,
@@ -120,8 +120,8 @@ impl World {
/// // Shading with an intersection in shadow.
/// let mut w = World::default();
/// w.lights = vec![PointLight::new(Tuple::point(0., 0., -10.), WHITE)];
/// let s1 = Sphere::default();
/// let mut s2 = Sphere::default();
/// let s1 = Shape::sphere();
/// let mut s2 = Shape::sphere();
/// s2.set_transform(Matrix4x4::translation(0., 0., 10.));
/// w.objects = vec![s1, s2.clone()];
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
@@ -155,6 +155,7 @@ impl World {
/// intersections::{prepare_computations, Intersection},
/// lights::PointLight,
/// rays::Ray,
/// shapes::Shape,
/// tuples::{Color, Tuple},
/// world::World,
/// BLACK,
@@ -176,7 +177,7 @@ impl World {
/// let w = {
/// let mut w = World::test_world();
/// let mut outer = &mut w.objects[0];
/// outer.material.ambient = 1.;
/// let m = outer.material.ambient = 1.;
/// let inner = &mut w.objects[1];
/// inner.material.ambient = 1.;
/// w
@@ -202,6 +203,7 @@ impl World {
/// ```
/// use rtchallenge::{
/// tuples::{ Tuple},
/// shapes::Shape,
/// world::World,
/// };
///