shapes: implement TestShape.
This commit is contained in:
parent
952ed8bf81
commit
e041fd1f6a
@ -1,20 +1,41 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
|
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
|
||||||
tuples::Tuple,
|
tuples::Tuple,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Default, PartialEq, Debug, Clone)]
|
||||||
enum Geometry {
|
pub struct TestData {
|
||||||
|
pub saved_ray: Option<Ray>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Geometry {
|
||||||
|
/// Shape with predictable normals useful for debugging.
|
||||||
|
TestShape(Arc<Mutex<TestData>>),
|
||||||
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
|
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
|
||||||
Sphere,
|
Sphere,
|
||||||
/// Flat surface that extends infinitely in the XZ axes.
|
/// Flat surface that extends infinitely in the XZ axes.
|
||||||
Plane,
|
Plane,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Geometry {
|
||||||
|
fn eq(&self, rhs: &Geometry) -> bool {
|
||||||
|
use Geometry::*;
|
||||||
|
match (self, rhs) {
|
||||||
|
(TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
|
||||||
|
(Sphere, Sphere) => true,
|
||||||
|
(Plane, Plane) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Shape represents visible objects. A signal instance of Shape can generically represent one of
|
/// 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
|
/// 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].
|
/// calling the appropriate constructor, i.e. [Shape::sphere].
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Shape {
|
pub struct Shape {
|
||||||
transform: Matrix4x4,
|
transform: Matrix4x4,
|
||||||
inverse_transform: Matrix4x4,
|
inverse_transform: Matrix4x4,
|
||||||
@ -23,6 +44,33 @@ pub struct Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
|
/// Create a test shape useful for debugging.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
|
||||||
|
///
|
||||||
|
/// let mut s = Shape::test_shape();
|
||||||
|
/// // The default transform.
|
||||||
|
/// assert_eq!(s.transform(), Matrix4x4::identity());
|
||||||
|
/// // The default material.
|
||||||
|
/// assert_eq!(s.material, Material::default());
|
||||||
|
/// // Assigning a material.
|
||||||
|
/// let mut m = Material {
|
||||||
|
/// ambient: 1.,
|
||||||
|
/// ..Material::default()
|
||||||
|
/// };
|
||||||
|
/// s.material = m.clone();
|
||||||
|
/// assert_eq!(s.material, m);
|
||||||
|
/// ```
|
||||||
|
pub fn test_shape() -> Shape {
|
||||||
|
Shape {
|
||||||
|
transform: Matrix4x4::identity(),
|
||||||
|
inverse_transform: Matrix4x4::identity(),
|
||||||
|
material: Material::default(),
|
||||||
|
geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
|
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
|
||||||
@ -71,6 +119,22 @@ impl Shape {
|
|||||||
/// Float,
|
/// Float,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
|
/// // Computing the normal on a translated shape.
|
||||||
|
/// let mut s = Shape::test_shape();
|
||||||
|
/// 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));
|
||||||
|
///
|
||||||
|
/// // Computing the normal on a transform shape.
|
||||||
|
/// let mut s = Shape::test_shape();
|
||||||
|
/// 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));
|
||||||
|
///
|
||||||
/// // Normal on X-axis
|
/// // Normal on X-axis
|
||||||
/// let s = Shape::sphere();
|
/// let s = Shape::sphere();
|
||||||
/// let n = s.normal_at(Tuple::point(1., 0., 0.));
|
/// let n = s.normal_at(Tuple::point(1., 0., 0.));
|
||||||
@ -147,6 +211,7 @@ impl Shape {
|
|||||||
let object_normal = match self.geometry {
|
let object_normal = match self.geometry {
|
||||||
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
||||||
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
||||||
|
Geometry::TestShape(_) => object_point,
|
||||||
};
|
};
|
||||||
let mut world_normal = self.inverse_transform.transpose() * object_normal;
|
let mut world_normal = self.inverse_transform.transpose() * object_normal;
|
||||||
world_normal.w = 0.;
|
world_normal.w = 0.;
|
||||||
@ -158,6 +223,7 @@ impl Shape {
|
|||||||
let object_normal = match self.geometry {
|
let object_normal = match self.geometry {
|
||||||
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
||||||
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
||||||
|
Geometry::TestShape(_) => todo!("test shape normal"),
|
||||||
};
|
};
|
||||||
let mut world_normal = self.transform.inverse().transpose() * object_normal;
|
let mut world_normal = self.transform.inverse().transpose() * object_normal;
|
||||||
world_normal.w = 0.;
|
world_normal.w = 0.;
|
||||||
@ -172,9 +238,12 @@ impl Shape {
|
|||||||
self.transform = t;
|
self.transform = t;
|
||||||
self.inverse_transform = t.inverse();
|
self.inverse_transform = t.inverse();
|
||||||
}
|
}
|
||||||
|
pub fn geometry(&self) -> &Geometry {
|
||||||
|
&self.geometry
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intersect a ray with a sphere.
|
/// Intersect a ray with a shapes.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@ -182,10 +251,42 @@ impl Shape {
|
|||||||
/// intersections::{Intersection, Intersections},
|
/// intersections::{Intersection, Intersections},
|
||||||
/// matrices::Matrix4x4,
|
/// matrices::Matrix4x4,
|
||||||
/// rays::Ray,
|
/// rays::Ray,
|
||||||
/// shapes::{intersect, Shape},
|
/// shapes::{intersect, Geometry, Shape},
|
||||||
/// tuples::Tuple,
|
/// tuples::Tuple,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
|
/// // Intersecting a scaled shape with a ray.
|
||||||
|
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
/// let mut s = Shape::test_shape();
|
||||||
|
/// s.set_transform(Matrix4x4::scaling(2., 2., 2.));
|
||||||
|
/// let xs = intersect(&s, &r);
|
||||||
|
/// if let Geometry::TestShape(data) = s.geometry() {
|
||||||
|
/// if let Some(ray) = &data.lock().unwrap().saved_ray {
|
||||||
|
/// assert_eq!(ray.origin, Tuple::point(0., 0., -2.5));
|
||||||
|
/// assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5));
|
||||||
|
/// } else {
|
||||||
|
/// panic!("ray wasn't set");
|
||||||
|
/// };
|
||||||
|
/// } else {
|
||||||
|
/// panic!("test_shape returned a non-TestShape geometry")
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Intersecting a translated shape with a ray.
|
||||||
|
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
/// let mut s = Shape::test_shape();
|
||||||
|
/// s.set_transform(Matrix4x4::translation(5., 0., 0.));
|
||||||
|
/// let xs = intersect(&s, &r);
|
||||||
|
/// if let Geometry::TestShape(data) = s.geometry() {
|
||||||
|
/// if let Some(ray) = &data.lock().unwrap().saved_ray {
|
||||||
|
/// assert_eq!(ray.origin, Tuple::point(-5., 0., -5.));
|
||||||
|
/// assert_eq!(ray.direction, Tuple::vector(0., 0., 1.));
|
||||||
|
/// } else {
|
||||||
|
/// panic!("ray wasn't set");
|
||||||
|
/// };
|
||||||
|
/// } else {
|
||||||
|
/// panic!("test_shape returned a non-TestShape geometry")
|
||||||
|
/// };
|
||||||
|
///
|
||||||
/// // A ray intersects a sphere in two points.
|
/// // A ray intersects a sphere in two points.
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
/// let s = Shape::sphere();
|
/// let s = Shape::sphere();
|
||||||
@ -265,6 +366,23 @@ pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
|||||||
match shape.geometry {
|
match shape.geometry {
|
||||||
Geometry::Sphere => sphere::intersect(shape, &local_ray),
|
Geometry::Sphere => sphere::intersect(shape, &local_ray),
|
||||||
Geometry::Plane => plane::intersect(shape, &local_ray),
|
Geometry::Plane => plane::intersect(shape, &local_ray),
|
||||||
|
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_shape {
|
||||||
|
use crate::{
|
||||||
|
intersections::Intersections,
|
||||||
|
rays::Ray,
|
||||||
|
shapes::{Geometry, Shape},
|
||||||
|
};
|
||||||
|
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||||
|
if let Geometry::TestShape(s) = &shape.geometry {
|
||||||
|
s.lock()
|
||||||
|
.expect("couldn't grab mutex for TestData")
|
||||||
|
.saved_ray = Some(ray.clone());
|
||||||
|
}
|
||||||
|
Intersections::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user