Compare commits

..

No commits in common. "62ad8275076aca64a165e803dae0fbf838e2aed5" and "5600d6c561b137385fca38ce9c629fa9c81634e5" have entirely different histories.

15 changed files with 27 additions and 895 deletions

87
rtchallenge/Cargo.lock generated
View File

@ -130,7 +130,7 @@ dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim 0.8.0",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
@ -259,41 +259,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "darling"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deflate"
version = "0.8.6"
@ -304,37 +269,6 @@ dependencies = [
"byteorder",
]
[[package]]
name = "derive_builder"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "either"
version = "1.6.1"
@ -374,12 +308,6 @@ dependencies = [
"backtrace",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.3"
@ -421,12 +349,6 @@ dependencies = [
"libc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "itertools"
version = "0.9.0"
@ -740,7 +662,6 @@ dependencies = [
"anyhow",
"core_affinity",
"criterion",
"derive_builder",
"enum-utils",
"num_cpus",
"png",
@ -852,12 +773,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "structopt"
version = "0.3.22"

View File

@ -15,7 +15,6 @@ float-as-double = []
anyhow = "1.0.41"
core_affinity = "0.5.10"
criterion = "0.3.4"
derive_builder = "0.10.2"
enum-utils = "0.1.2"
num_cpus = "1.13.0"
png = "0.16.8"

View File

@ -1,119 +0,0 @@
use std::time::Instant;
use anyhow::Result;
use structopt::StructOpt;
use rtchallenge::{
camera::{Camera, RenderStrategy},
float::consts::PI,
lights::PointLightBuilder,
materials::MaterialBuilder,
matrices::Matrix4x4,
shapes::{Geometry, ShapeBuilder},
transformations::view_transform,
tuples::Tuple,
world::WorldBuilder,
Float, WHITE,
};
/// Experimenting with balls.
#[derive(StructOpt, Debug)]
#[structopt(name = "balls")]
struct Opt {
#[structopt(long, default_value = "rayon")]
render_strategy: RenderStrategy,
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
/// times randomly across the pixel.
#[structopt(short, long, default_value = "0")]
samples: usize,
}
fn main() -> Result<()> {
let start = Instant::now();
let opt = Opt::from_args();
let width = 2560;
let height = 1440;
let light1 = PointLightBuilder::default()
.position(Tuple::point(-5., 5., -5.))
.intensity(WHITE)
.build()?;
let light2 = PointLightBuilder::default()
.position(Tuple::point(5., 5., -5.))
.intensity([0.2, 0.2, 0.6])
.build()?;
let light3 = PointLightBuilder::default()
.position(Tuple::point(0., 2., -5.))
.intensity([0.2, 0.2, 0.1])
.build()?;
let ball_size = 0.5;
let num_per_axis = 3;
let center_to_center = 4. * ball_size;
let from = Tuple::point(
-5. * center_to_center,
5. * center_to_center,
-4. * center_to_center,
);
let to = Tuple::point(
num_per_axis as Float * center_to_center / 2.,
0., //num_per_axis as Float * center_to_center / 2.,
num_per_axis as Float * center_to_center / 2.,
);
let up = Tuple::point(0., 1., 0.);
let mut camera = Camera::new(width, height, PI / 6.);
camera.set_transform(view_transform(from, to, up));
camera.render_strategy = opt.render_strategy;
camera.samples_per_pixel = opt.samples;
let floor = ShapeBuilder::default()
.geometry(Geometry::Plane)
.transform(Matrix4x4::translation(0., -ball_size, 0.))
.material(
MaterialBuilder::default()
.color([0.2, 0.2, 0.2])
.specular(0.)
.build()?,
)
.build()?;
let mut objects = vec![floor];
for z in 0..num_per_axis {
for y in 0..num_per_axis {
for x in 0..num_per_axis {
objects.push(
ShapeBuilder::default()
.transform(
Matrix4x4::translation(
x as Float * center_to_center,
y as Float * center_to_center,
z as Float * center_to_center,
) * Matrix4x4::scaling(ball_size, ball_size, ball_size),
)
.material(
MaterialBuilder::default()
.color([0.1, 1., 0.5])
.ambient(y as Float / 4.)
.diffuse(x as Float / 4.)
.specular(z as Float / 4.)
.build()?,
)
.build()?,
);
}
}
}
let world = WorldBuilder::default()
.lights(vec![light1, light2, light3])
.objects(objects)
.build()?;
let image = camera.render(&world);
let path = "/tmp/balls.png";
println!("saving output to {}", path);
image.write_to_file(path)?;
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
Ok(())
}

View File

@ -1,117 +0,0 @@
use std::time::Instant;
use anyhow::Result;
use structopt::StructOpt;
use rtchallenge::prelude::*;
use rtchallenge::{camera::RenderStrategy, float::consts::PI, WHITE};
/// End of chapter 9 challenge.
#[derive(StructOpt, Debug)]
#[structopt(name = "eoc9")]
struct Opt {
#[structopt(long, default_value = "rayon")]
render_strategy: RenderStrategy,
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
/// times randomly across the pixel.
#[structopt(short, long, default_value = "0")]
samples: usize,
}
fn main() -> Result<()> {
let start = Instant::now();
let opt = Opt::from_args();
let width = 2560;
let height = 1440;
let light1 = PointLightBuilder::default()
.position(point(-5., 5., -5.))
.intensity(WHITE)
.build()?;
let light2 = PointLightBuilder::default()
.position(point(5., 5., -5.))
.intensity([0.2, 0.2, 0.6])
.build()?;
let light3 = PointLightBuilder::default()
.position(point(0., 2., -5.))
.intensity([0.2, 0.2, 0.1])
.build()?;
let from = point(0., 1.5, -5.);
let to = point(0., 1., 0.);
let up = point(0., 1., 0.);
let camera = CameraBuilder::default()
.hsize(width)
.vsize(height)
.field_of_view(PI / 4.)
.transform(view_transform(from, to, up))
.render_strategy(opt.render_strategy)
.samples_per_pixel(opt.samples)
.build()?;
let floor = plane()
.material(
MaterialBuilder::default()
.color([1., 0.2, 0.2])
.specular(0.)
.build()?,
)
.build()?;
let ceiling = plane()
.transform(translation(0., 6., 0.) * rotation_x(PI))
.material(
MaterialBuilder::default()
.color([0.6, 0.6, 0.8])
.specular(0.2)
.build()?,
)
.build()?;
let middle = sphere()
.transform(translation(-0.5, 0.5, 0.5))
.material(
MaterialBuilder::default()
.color([0.1, 1., 0.5])
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let right = sphere()
.transform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5))
.material(
MaterialBuilder::default()
.color([1., 1., 1.])
.diffuse(0.7)
.specular(0.0)
.build()?,
)
.build()?;
let left = sphere()
.transform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33))
.material(
MaterialBuilder::default()
.color([1., 0.8, 0.1])
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let world = WorldBuilder::default()
.lights(vec![light1, light2, light3])
.objects(vec![floor, ceiling, middle, right, left])
.build()?;
let image = camera.render(&world);
let path = "/tmp/eoc9.png";
println!("saving output to {}", path);
image.write_to_file(path)?;
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
Ok(())
}

View File

@ -1,109 +0,0 @@
use std::time::Instant;
use anyhow::Result;
use structopt::StructOpt;
use rtchallenge::{
camera::{Camera, RenderStrategy},
float::consts::PI,
lights::PointLight,
materials::Material,
matrices::Matrix4x4,
shapes::Shape,
transformations::view_transform,
tuples::{Color, Tuple},
world::World,
WHITE,
};
/// End of chapter 9 challenge.
#[derive(StructOpt, Debug)]
#[structopt(name = "eoc9")]
struct Opt {
#[structopt(long, default_value = "rayon")]
render_strategy: RenderStrategy,
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
/// times randomly across the pixel.
#[structopt(short, long, default_value = "0")]
samples: usize,
}
fn main() -> Result<()> {
let start = Instant::now();
let opt = Opt::from_args();
let width = 2560;
let height = 1440;
let light_position = Tuple::point(-5., 5., -5.);
let light_color = WHITE;
let light1 = PointLight::new(light_position, light_color);
let light_position = Tuple::point(5., 5., -5.);
let light_color = Color::new(0.2, 0.2, 0.6);
let light2 = PointLight::new(light_position, light_color);
let light_position = Tuple::point(0., 2., -5.);
let light_color = Color::new(0.2, 0.2, 0.1);
let light3 = PointLight::new(light_position, light_color);
let mut camera = Camera::new(width, height, PI / 4.);
let from = Tuple::point(0., 1.5, -5.);
let to = Tuple::point(0., 1., 0.);
let up = Tuple::point(0., 1., 0.);
camera.set_transform(view_transform(from, to, up));
camera.render_strategy = opt.render_strategy;
camera.samples_per_pixel = opt.samples;
let mut floor = Shape::plane();
floor.material = Material {
color: Color::new(1., 0.2, 0.2),
specular: 0.,
..Material::default()
};
let mut ceiling = Shape::plane();
ceiling.set_transform(Matrix4x4::translation(0., 6., 0.) * Matrix4x4::rotation_x(PI));
ceiling.material = Material {
color: Color::new(0.6, 0.6, 0.8),
specular: 0.2,
..Material::default()
};
let mut middle = Shape::sphere();
middle.set_transform(Matrix4x4::translation(-0.5, 0.5, 0.5));
middle.material = Material {
color: Color::new(0.1, 1., 0.5),
diffuse: 0.7,
specular: 0.3,
..Material::default()
};
let mut right = Shape::sphere();
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
right.material = Material {
color: Color::new(1., 1., 1.),
diffuse: 0.7,
specular: 0.0,
..Material::default()
};
let mut left = Shape::sphere();
left.set_transform(
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
);
left.material = Material {
color: Color::new(1., 0.8, 0.1),
diffuse: 0.7,
specular: 0.3,
..Material::default()
};
let mut world = World::default();
world.lights = vec![light1, light2, light3];
world.objects = vec![floor, ceiling, middle, right, left];
let image = camera.render(&world);
let path = "/tmp/eoc9.png";
println!("saving output to {}", path);
image.write_to_file(path)?;
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
Ok(())
}

View File

@ -7,7 +7,6 @@ use std::{
thread,
};
use derive_builder::Builder;
use rand::Rng;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize;
@ -29,13 +28,6 @@ pub enum RenderStrategy {
Rayon,
WorkerPool,
}
impl Default for RenderStrategy {
fn default() -> RenderStrategy {
RenderStrategy::Rayon
}
}
impl FromStr for RenderStrategy {
type Err = serde_json::error::Error;
fn from_str(s: &str) -> Result<RenderStrategy, serde_json::error::Error> {
@ -43,68 +35,21 @@ impl FromStr for RenderStrategy {
}
}
#[derive(Builder, Clone, Debug, Default)]
#[builder(setter(skip), build_fn(skip))]
#[derive(Clone)]
pub struct Camera {
#[builder(setter(skip = "false"))]
hsize: usize,
#[builder(setter(skip = "false"))]
vsize: usize,
#[builder(setter(skip = "false"))]
field_of_view: Float,
#[builder(setter(skip = "false"))]
transform: Matrix4x4,
inverse_transform: Matrix4x4,
pixel_size: Float,
half_width: Float,
half_height: Float,
#[builder(setter(skip = "false"))]
pub render_strategy: RenderStrategy,
/// 0 renders from the center of the pixel, 1 or higher is random sampling of the pixel.
#[builder(setter(skip = "false"))]
pub samples_per_pixel: usize,
}
impl CameraBuilder {
pub fn build(&self) -> Result<Camera, CameraBuilderError> {
let hsize = match self.hsize {
Some(ref value) => Clone::clone(value),
None => {
return Err(Into::into(::derive_builder::UninitializedFieldError::from(
"hsize",
)))
}
};
let vsize = match self.vsize {
Some(ref value) => Clone::clone(value),
None => {
return Err(Into::into(::derive_builder::UninitializedFieldError::from(
"vsize",
)))
}
};
let field_of_view = match self.field_of_view {
Some(ref value) => Clone::clone(value),
None => {
return Err(Into::into(::derive_builder::UninitializedFieldError::from(
"field_of_view",
)))
}
};
let mut c = Camera::new(hsize, vsize, field_of_view);
if let Some(transform) = self.transform {
c.set_transform(transform);
}
if let Some(render_strategy) = self.render_strategy {
c.render_strategy = render_strategy;
}
if let Some(samples_per_pixel) = self.samples_per_pixel {
c.samples_per_pixel = samples_per_pixel;
}
Ok(c)
}
}
enum Request {
Line { width: usize, y: usize },
}

View File

@ -7,16 +7,11 @@ use crate::{
Float, EPSILON,
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Intersection<'i> {
pub t: Float,
pub object: &'i Shape,
}
impl<'i> PartialEq for Intersection<'i> {
fn eq(&self, rhs: &Intersection) -> bool {
((self.t - rhs.t).abs() < EPSILON) && (self.object == rhs.object)
}
}
impl<'i> Intersection<'i> {
/// Create new `Intersection` at the given `t` that hits the given `object`.

View File

@ -37,16 +37,3 @@ pub mod float {
}
pub use float::Float;
pub mod prelude {
pub use crate::{
camera::{Camera, CameraBuilder},
lights::{PointLight, PointLightBuilder},
materials::{Material, MaterialBuilder},
matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation},
shapes::{plane, sphere, test_shape},
transformations::view_transform,
tuples::{point, vector, Color},
world::{World, WorldBuilder},
};
}

View File

@ -1,12 +1,8 @@
use derive_builder::Builder;
use crate::tuples::{Color, Tuple};
#[derive(Builder, Clone, Debug, Default, PartialEq)]
#[builder(default)]
#[derive(Clone, Debug, PartialEq)]
pub struct PointLight {
pub position: Tuple,
#[builder(setter(into))]
pub intensity: Color,
}
@ -28,13 +24,10 @@ impl PointLight {
/// assert_eq!(light.position, position);
/// assert_eq!(light.intensity, intensity);
/// ```
pub fn new<C>(position: Tuple, intensity: C) -> PointLight
where
C: Into<Color>,
{
pub fn new(position: Tuple, intensity: Color) -> PointLight {
PointLight {
position,
intensity: intensity.into(),
intensity,
}
}
}

View File

@ -1,16 +1,11 @@
use derive_builder::Builder;
use crate::{
lights::PointLight,
tuples::Color,
tuples::{dot, reflect, Tuple},
Float, BLACK, WHITE,
};
#[derive(Builder, Debug, PartialEq, Clone)]
#[builder(default)]
#[derive(Debug, PartialEq, Clone)]
pub struct Material {
#[builder(setter(into))]
pub color: Color,
pub ambient: Float,
pub diffuse: Float,

View File

@ -3,87 +3,6 @@ use std::ops::{Index, IndexMut, Mul, Sub};
use crate::{tuples::Tuple, Float, EPSILON};
/// Short hand for creating a Matrix4x4 set to the identity matrix.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{identity, Matrix4x4};
///
/// assert_eq!(identity(), Matrix4x4::identity());
/// ```
pub fn identity() -> Matrix4x4 {
Matrix4x4::identity()
}
/// Short hand for creating a Matrix4x4 for rotating around the X-axis.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{rotation_x, Matrix4x4};
///
/// assert_eq!(rotation_x(10.), Matrix4x4::rotation_x(10.));
/// ```
pub fn rotation_x(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_x(radians)
}
/// Short hand for creating a Matrix4x4 for rotating around the Y-axis.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{rotation_y, Matrix4x4};
///
/// assert_eq!(rotation_y(10.), Matrix4x4::rotation_y(10.));
/// ```
pub fn rotation_y(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_y(radians)
}
/// Short hand for creating a Matrix4x4 for rotating around the Z-axis.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{rotation_z, Matrix4x4};
///
/// assert_eq!(rotation_z(10.), Matrix4x4::rotation_z(10.));
/// ```
pub fn rotation_z(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_z(radians)
}
/// Short hand for creating a Matrix4x4 that scales in the given x,y,z axis.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{scaling, Matrix4x4};
///
/// assert_eq!(scaling(1., 2., 3.), Matrix4x4::scaling(1., 2., 3.));
/// ```
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::scaling(x, y, z)
}
/// Short hand for creating a Matrix4x4 that shears across the given axis pairs.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{shearing, Matrix4x4};
///
/// assert_eq!(
/// shearing(1., 2., 3., 4., 5., 6.),
/// Matrix4x4::shearing(1., 2., 3., 4., 5., 6.)
/// );
/// ```
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
Matrix4x4::shearing(xy, xz, yx, yz, zx, zy)
}
/// Short hand for creating a Matrix4x4 that translations along the given x,y,z axis.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{translation, Matrix4x4};
///
/// assert_eq!(translation(1., 2., 3.), Matrix4x4::translation(1., 2., 3.));
/// ```
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::translation(x, y, z)
}
#[derive(Debug)]
pub struct Matrix2x2 {
m: [[Float; 2]; 2],

View File

@ -1,7 +1,7 @@
use crate::{matrices::Matrix4x4, tuples::Tuple, Float};
/// Rays have an origin and a direction. This datatype is the 'ray' in 'raytracer'.
#[derive(Debug, Default, Clone, PartialEq)]
#[derive(Debug)]
pub struct Ray {
pub origin: Tuple,
pub direction: Tuple,

View File

@ -1,189 +1,32 @@
use std::sync::{Arc, Mutex};
use derive_builder::Builder;
use crate::{
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
tuples::Tuple,
intersections::{Intersection, Intersections},
materials::Material,
matrices::Matrix4x4,
rays::Ray,
tuples::{dot, Tuple},
EPSILON,
};
#[derive(Default, PartialEq, Debug, Clone)]
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>>),
#[derive(Debug, PartialEq, Clone)]
enum Geometry {
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
Sphere,
/// Flat surface that extends infinitely in the XZ axes.
Plane,
}
impl Default for Geometry {
fn default() -> Geometry {
Geometry::Sphere
}
}
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
/// 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(Builder, Debug, Clone, PartialEq)]
#[builder(default, pattern = "owned")]
#[derive(Debug, PartialEq, Clone)]
pub struct Shape {
transform: Matrix4x4,
#[builder(private, default = "self.default_inverse_transform()?")]
inverse_transform: Matrix4x4,
pub material: Material,
geometry: Geometry,
}
/// Short hand for creating a ShapeBuilder with a plane geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{plane, Shape};
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(plane().build()?, Shape::plane());
/// # Ok(())
/// # }
/// ```
pub fn plane() -> ShapeBuilder {
ShapeBuilder::plane()
}
/// Short hand for creating a ShapeBuilder with a sphere geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{sphere, Shape};
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(sphere().build()?, Shape::sphere());
/// # Ok(())
/// # }
/// ```
pub fn sphere() -> ShapeBuilder {
ShapeBuilder::sphere()
}
/// Short hand for creating a ShapeBuilder with a test shape geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{test_shape, Shape};
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(test_shape().build()?, Shape::test_shape());
/// # Ok(())
/// # }
/// ```
pub fn test_shape() -> ShapeBuilder {
ShapeBuilder::test_shape()
}
impl ShapeBuilder {
/// Short hand for creating a ShapeBuilder with a plane geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{plane, Shape};
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(plane().build()?, Shape::plane());
/// # Ok(())
/// # }
/// ```
pub fn plane() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Plane)
}
/// Short hand for creating a ShapeBuilder with a sphere geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{sphere, Shape};
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(sphere().build()?, Shape::sphere());
/// # Ok(())
/// # }
/// ```
pub fn sphere() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Sphere)
}
/// Short hand for creating a ShapeBuilder with a test shape geometry.
///
/// # Examples
/// ```
/// use rtchallenge::shapes::{test_shape, Shape};
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
/// assert_eq!(test_shape().build()?, Shape::test_shape());
/// # Ok(())
/// # }
/// ```
pub fn test_shape() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
TestData::default(),
))))
}
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> {
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse())
}
}
impl Default for Shape {
fn default() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::default(),
}
}
}
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
/// ```
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
@ -232,22 +75,6 @@ impl Shape {
/// 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
/// let s = Shape::sphere();
/// let n = s.normal_at(Tuple::point(1., 0., 0.));
@ -324,7 +151,6 @@ impl Shape {
let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
Geometry::Plane => Tuple::vector(0., 1., 0.),
Geometry::TestShape(_) => object_point,
};
let mut world_normal = self.inverse_transform.transpose() * object_normal;
world_normal.w = 0.;
@ -336,7 +162,6 @@ impl Shape {
let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
Geometry::Plane => Tuple::vector(0., 1., 0.),
Geometry::TestShape(_) => todo!("test shape normal"),
};
let mut world_normal = self.transform.inverse().transpose() * object_normal;
world_normal.w = 0.;
@ -351,12 +176,9 @@ impl Shape {
self.transform = t;
self.inverse_transform = t.inverse();
}
pub fn geometry(&self) -> &Geometry {
&self.geometry
}
}
/// Intersect a ray with a shapes.
/// Intersect a ray with a sphere.
///
/// # Examples
/// ```
@ -364,42 +186,10 @@ impl Shape {
/// intersections::{Intersection, Intersections},
/// matrices::Matrix4x4,
/// rays::Ray,
/// shapes::{intersect, Geometry, Shape},
/// shapes::{intersect, Shape},
/// 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.
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let s = Shape::sphere();
@ -475,27 +265,9 @@ impl Shape {
/// assert_eq!(xs[0].object, &p);
/// ```
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let local_ray = ray.transform(shape.inverse_transform);
match shape.geometry {
Geometry::Sphere => sphere::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()
Geometry::Sphere => sphere::intersect(shape, ray),
Geometry::Plane => plane::intersect(shape, ray),
}
}
@ -507,6 +279,7 @@ mod sphere {
tuples::{dot, Tuple},
};
pub fn intersect<'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);

View File

@ -2,33 +2,7 @@ use std::ops::{Add, Div, Mul, Neg, Sub};
use crate::{Float, EPSILON};
/// Short hand for creating a Tuple that represents a point, w=1.
///
/// # Examples
/// ```
/// use rtchallenge::tuples::{point, Tuple};
///
/// assert_eq!(point(1., 2., 3.), Tuple::point(1., 2., 3.));
/// assert_eq!(point(1., 2., 3.), Tuple::new(1., 2., 3., 1.));
/// ```
pub fn point(x: Float, y: Float, z: Float) -> Tuple {
Tuple::point(x, y, z)
}
/// Short hand for creating a Tuple that represents a vector, w=0.
///
/// # Examples
/// ```
/// use rtchallenge::tuples::{vector, Tuple};
///
/// assert_eq!(vector(1., 2., 3.), Tuple::vector(1., 2., 3.));
/// assert_eq!(vector(1., 2., 3.), Tuple::new(1., 2., 3., 0.));
/// ```
pub fn vector(x: Float, y: Float, z: Float) -> Tuple {
Tuple::vector(x, y, z)
}
#[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub struct Tuple {
pub x: Float,
pub y: Float,
@ -187,29 +161,17 @@ pub fn cross(a: Tuple, b: Tuple) -> Tuple {
)
}
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
pub struct Color {
pub red: Float,
pub green: Float,
pub blue: Float,
}
impl Color {
pub const fn new(red: Float, green: Float, blue: Float) -> Color {
Color { red, green, blue }
}
}
impl From<[Float; 3]> for Color {
fn from(rgb: [Float; 3]) -> Self {
Color {
red: rgb[0],
green: rgb[1],
blue: rgb[2],
}
}
}
impl PartialEq for Color {
fn eq(&self, rhs: &Color) -> bool {
((self.red - rhs.red).abs() < EPSILON)
@ -217,7 +179,6 @@ impl PartialEq for Color {
&& ((self.blue - rhs.blue).abs() < EPSILON)
}
}
impl Add for Color {
type Output = Self;
fn add(self, other: Self) -> Self {
@ -261,7 +222,6 @@ impl Mul<Color> for Float {
}
}
}
impl Mul<Color> for Color {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
@ -283,7 +243,6 @@ impl Neg for Color {
}
}
}
impl Sub for Color {
type Output = Self;
fn sub(self, other: Self) -> Self {

View File

@ -1,5 +1,3 @@
use derive_builder::Builder;
use crate::{
intersections::{prepare_computations, Intersections, PrecomputedData},
lights::PointLight,
@ -8,7 +6,7 @@ use crate::{
rays::Ray,
shapes::{intersect, Shape},
tuples::{Color, Tuple},
BLACK, WHITE,
Float, BLACK, WHITE,
};
/// World holds all drawable objects and the light(s) that illuminate them.
@ -22,8 +20,7 @@ use crate::{
/// assert_eq!(w.lights.len(), 0);
/// ```
#[derive(Builder, Clone, Debug, Default)]
#[builder(default)]
#[derive(Clone, Debug, Default)]
pub struct World {
pub lights: Vec<PointLight>,
pub objects: Vec<Shape>,
@ -148,7 +145,7 @@ impl World {
shadowed,
)
});
c
c / self.lights.len() as Float
}
/// Compute color for given ray fired at the world.
///