Random changes.
Some checks failed
continuous-integration/drone Build is failing

This commit is contained in:
Bill Thiede 2022-06-11 17:46:26 -07:00
parent 270a7ec349
commit e574cdb592
5 changed files with 477 additions and 6 deletions

View File

@ -0,0 +1,232 @@
use std::time::Instant;
use anyhow::Result;
use structopt::StructOpt;
use rtchallenge::prelude::*;
use rtchallenge::{
camera::RenderStrategy,
float::consts::PI,
patterns::{test_pattern, BLACK_PAT, WHITE_PAT},
WHITE,
};
/// End of chapter 14 challenge.
#[derive(StructOpt, Debug)]
#[structopt(name = "eoc14")]
struct Opt {
/// Strategy for casting rays into image.
#[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,
/// Rendered image width in pixels.
#[structopt(short, long, default_value = "2560")]
width: usize,
/// Rendered image height in pixels.
#[structopt(short, long, default_value = "1440")]
height: usize,
}
fn main() -> Result<()> {
let start = Instant::now();
let opt = Opt::from_args();
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(2., 8., -10.);
let to = point(2., 2., -1.);
let up = point(0., 1., 0.);
let camera = CameraBuilder::default()
.hsize(opt.width)
.vsize(opt.height)
.field_of_view(PI / 12.)
.transform(view_transform(from, to, up))
.render_strategy(opt.render_strategy)
.samples_per_pixel(opt.samples)
.build()?;
let floor = plane()
.material(
MaterialBuilder::default()
.color(
checkers_pattern(
ring_pattern([0.8, 0.8, 0.2].into(), [0.8, 0.2, 0.8].into())
.transform(scaling(1. / 8., 1. / 8., 1. / 8.))
.build()?,
ring_pattern([0.2, 0.8, 0.2].into(), [0.8, 0.2, 0.2].into())
.transform(scaling(1. / 4., 1. / 4., 1. / 4.))
.build()?,
)
.transform(translation(0., 1., 0.) * scaling(2., 2., 2.))
.build()?,
)
.specular(0.)
.reflective(0.5)
.build()?,
)
.build()?;
let sphere_size = scaling(0.5, 0.5, 0.5);
let x1y1 = sphere()
.transform(translation(1., 1., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(
gradient_pattern([0., 0., 1.].into(), [1., 1., 0.].into())
.transform(scaling(2., 1., 1.) * translation(-0.5, 0., 0.))
.build()?,
)
.diffuse(0.7)
.specular(0.3)
.reflective(0.5)
.build()?,
)
.build()?;
let x2y1 = sphere()
.transform(translation(2., 1., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(stripe_pattern(WHITE_PAT, BLACK_PAT).build()?)
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let x3y1 = sphere()
.transform(translation(3., 1., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(
stripe_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.2, 1., 1.))
.build()?,
)
.diffuse(0.7)
.specular(0.0)
.build()?,
)
.build()?;
let x1y2 = sphere()
.transform(translation(1., 2., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(test_pattern().build()?)
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let x2y2 = sphere()
.transform(translation(2., 2., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(
ring_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.2, 0.2, 0.2))
.build()?,
)
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let x3y2 = sphere()
.transform(translation(3., 2., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color(
checkers_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.5, 0.5, 0.5))
.build()?,
)
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let x1y2z1 = sphere()
.transform(translation(1., 2., -1.) * sphere_size)
.material(
MaterialBuilder::default()
.color([0.8, 0.2, 0.2])
.diffuse(0.7)
.specular(0.3)
.transparency(0.5)
.build()?,
)
.build()?;
let x2y2z1 = sphere()
.transform(translation(2., 2., -1.) * sphere_size)
.material(
MaterialBuilder::default()
.color([0.8, 0.2, 0.2])
.diffuse(0.7)
.specular(0.3)
.transparency(0.9)
.refractive_index(1.5)
.build()?,
)
.build()?;
let x3y2z1 = cube()
.transform(
translation(3., 2., -1.) * (sphere_size * scaling(0.5, 0.5, 0.5)) * rotation_y(PI / 4.),
)
.material(
MaterialBuilder::default()
.color([0.8, 0.8, 0.2])
.diffuse(0.7)
.specular(0.3)
.reflective(0.5)
.build()?,
)
.build()?;
let x2y4z1 = sphere()
.transform(translation(2., 4., 0.) * sphere_size)
.material(
MaterialBuilder::default()
.color([0.2, 0.2, 0.8])
.diffuse(0.7)
.specular(0.3)
.build()?,
)
.build()?;
let world = WorldBuilder::default()
.lights(vec![light1, light2, light3])
.objects(vec![
floor, x1y1, x2y1, x3y1, x1y2, x2y2, x3y2, x1y2z1, x2y2z1, x2y4z1, x3y2z1,
])
.build()?;
let image = camera.render(&world);
let path = "/tmp/eoc14.png";
println!("saving output to {}", path);
image.write_to_file(path)?;
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
Ok(())
}

View File

@ -0,0 +1,115 @@
use std::time::Instant;
use anyhow::Result;
use structopt::StructOpt;
use rtchallenge::prelude::*;
use rtchallenge::{
camera::RenderStrategy,
float::consts::PI,
patterns::{test_pattern, BLACK_PAT, WHITE_PAT},
WHITE,
};
/// Experiment with transparency.
#[derive(StructOpt, Debug)]
#[structopt(name = "glass")]
struct Opt {
/// Strategy for casting rays into image.
#[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,
/// Rendered image width in pixels.
#[structopt(short, long, default_value = "2560")]
width: usize,
/// Rendered image height in pixels.
#[structopt(short, long, default_value = "1440")]
height: usize,
}
fn main() -> Result<()> {
let start = Instant::now();
let opt = Opt::from_args();
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., 0., -10.);
let to = point(0., 0., 0.);
let up = point(0., 1., 0.);
let camera = CameraBuilder::default()
.hsize(opt.width)
.vsize(opt.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()
.transform(rotation_x(PI / 2.))
.material(
MaterialBuilder::default()
.color(checkers_pattern(BLACK_PAT, WHITE_PAT).build()?)
.reflective(0.5)
.build()?,
)
.build()?;
let ceiling = plane()
.transform(translation(0., 0., -20.) * rotation_x(PI / 2.))
.material(MaterialBuilder::default().color([0.2, 0.2, 0.8]).build()?)
.build()?;
let mut objects = Vec::new();
for x in 0..5 {
for y in 0..5 {
let trans = y as Float / 5.;
let index = 1. + x as Float / 5.;
dbg!(x, y, trans, index);
objects.push(
sphere()
.transform(
translation(x as Float - 2., y as Float - 2., -2.) * scaling(0.5, 0.5, 0.5),
)
.material(
MaterialBuilder::default()
.color([0.5, 0.5, 0.5])
.transparency(trans)
.refractive_index(index)
.build()?,
)
.build()?,
);
}
}
objects.push(floor);
objects.push(ceiling);
let world = WorldBuilder::default()
.lights(vec![light1, light2, light3])
.objects(objects)
.build()?;
let image = camera.render(&world);
let path = "/tmp/glass.png";
println!("saving output to {}", path);
image.write_to_file(path)?;
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
Ok(())
}

View File

@ -22,7 +22,7 @@ use crate::{
Float, BLACK, Float, BLACK,
}; };
const MAX_DEPTH_RECURSION: usize = 5; const MAX_DEPTH_RECURSION: usize = 10;
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)] #[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]

View File

@ -50,5 +50,6 @@ pub mod prelude {
transformations::view_transform, transformations::view_transform,
tuples::{point, vector, Color}, tuples::{point, vector, Color},
world::{World, WorldBuilder}, world::{World, WorldBuilder},
Float,
}; };
} }

View File

@ -5,7 +5,7 @@ use derive_builder::Builder;
use crate::{ use crate::{
intersections::Intersections, intersections::Intersections,
materials::{Material, MaterialBuilder}, materials::{Material, MaterialBuilder},
matrices::Matrix4x4, matrices::{identity, Matrix4x4},
rays::Ray, rays::Ray,
tuples::Tuple, tuples::Tuple,
}; };
@ -25,6 +25,8 @@ pub enum Geometry {
Plane, Plane,
/// AABB cube at origin from -1,1 in each direction. /// AABB cube at origin from -1,1 in each direction.
Cube, Cube,
/// Takes shape from children held within.
Group(Vec<Shape>),
} }
impl Default for Geometry { impl Default for Geometry {
@ -41,6 +43,7 @@ impl PartialEq for Geometry {
(Sphere, Sphere) => true, (Sphere, Sphere) => true,
(Plane, Plane) => true, (Plane, Plane) => true,
(Cube, Cube) => true, (Cube, Cube) => true,
(Group(v1), Group(v2)) => v1 == v2,
_ => false, _ => false,
} }
} }
@ -50,10 +53,9 @@ impl PartialEq for Geometry {
/// 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(Builder, Debug, Clone, PartialEq)] #[derive(Builder, Debug, Clone, PartialEq)]
#[builder(default, pattern = "owned")] #[builder(default, pattern = "owned", build_fn(skip))]
pub struct Shape { pub struct Shape {
transform: Matrix4x4, transform: Matrix4x4,
#[builder(private, default = "self.default_inverse_transform()?")]
inverse_transform: Matrix4x4, inverse_transform: Matrix4x4,
pub material: Material, pub material: Material,
geometry: Geometry, geometry: Geometry,
@ -78,6 +80,11 @@ pub fn cube() -> ShapeBuilder {
ShapeBuilder::cube() ShapeBuilder::cube()
} }
/// Short hand for creating a ShapeBuilder with a group geometry.
pub fn group() -> ShapeBuilder {
ShapeBuilder::group()
}
/// Helper for producing a sphere with a glassy material. /// Helper for producing a sphere with a glassy material.
pub fn glass_sphere() -> ShapeBuilder { pub fn glass_sphere() -> ShapeBuilder {
ShapeBuilder::sphere().material( ShapeBuilder::sphere().material(
@ -107,9 +114,38 @@ impl ShapeBuilder {
pub fn cube() -> ShapeBuilder { pub fn cube() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Cube) ShapeBuilder::default().geometry(Geometry::Cube)
} }
/// Short hand for creating a ShapeBuilder with a group geometry.
pub fn group() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Group(Vec::new()))
}
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> { /// Add child shapes, only valid for Group::Geometry.
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse()) pub fn add_child(mut self, child: Shape) -> ShapeBuilder {
if let Some(Geometry::Group(ref mut children)) = &mut self.geometry {
children.push(child);
}
self
}
pub fn build(&self) -> Result<Shape, ShapeBuilderError> {
let mut s = Shape::default();
if let Some(transform) = &self.transform {
s.set_transform(transform.clone());
}
if let Some(material) = &self.material {
s.material = material.clone();
};
if let Some(geometry) = &self.geometry {
s.geometry = geometry.clone();
};
let transform = s.transform().clone();
if let Geometry::Group(ref mut children) = s.geometry {
children
.into_iter()
.for_each(|c| c.set_transform(transform * c.transform()));
}
Ok(s)
} }
} }
@ -158,6 +194,14 @@ impl Shape {
geometry: Geometry::Cube, geometry: Geometry::Cube,
} }
} }
pub fn group() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Group(Vec::new()),
}
}
/// Find the normal at the point on the sphere. /// Find the normal at the point on the sphere.
pub fn normal_at(&self, world_point: Tuple) -> Tuple { pub fn normal_at(&self, world_point: Tuple) -> Tuple {
let object_point = self.inverse_transform * world_point; let object_point = self.inverse_transform * world_point;
@ -166,6 +210,7 @@ impl Shape {
Geometry::Plane => Tuple::vector(0., 1., 0.), Geometry::Plane => Tuple::vector(0., 1., 0.),
Geometry::TestShape(_) => object_point, Geometry::TestShape(_) => object_point,
Geometry::Cube => cube::local_normal_at(object_point), Geometry::Cube => cube::local_normal_at(object_point),
Geometry::Group(_) => todo!("normal_at"),
}; };
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.;
@ -197,6 +242,7 @@ pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
Geometry::Plane => plane::intersect(shape, &local_ray), Geometry::Plane => plane::intersect(shape, &local_ray),
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray), Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
Geometry::Cube => cube::intersect(shape, &local_ray), Geometry::Cube => cube::intersect(shape, &local_ray),
Geometry::Group(_) => group::intersect(shape, &ray),
} }
} }
@ -380,6 +426,83 @@ mod cube {
} }
} }
mod group {
use crate::{intersections::Intersections, rays::Ray, shapes::Shape};
use super::Geometry;
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
if let Geometry::Group(children) = &shape.geometry {
let mut intersections: Vec<_> = children
.iter()
.map(|c| super::intersect(c, ray))
.flatten()
.collect();
intersections.sort_by(|a, b| {
a.t.partial_cmp(&b.t)
.expect("an intersection has a t value that is NaN")
});
return Intersections::new(intersections);
}
unreachable!();
}
#[cfg(test)]
mod tests {
use crate::{
matrices::{scaling, translation},
rays::Ray,
shapes::{intersect, Shape, ShapeBuilder},
tuples::{point, vector},
};
#[test]
fn intersecting_empty_group() {
let g = Shape::group();
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 0);
}
#[test]
fn intersecting_nonempty_group() -> Result<(), Box<dyn std::error::Error>> {
let s1 = Shape::sphere();
let s2 = ShapeBuilder::sphere()
.transform(translation(0., 0., -3.))
.build()?;
let s3 = ShapeBuilder::sphere()
.transform(translation(5., 0., 0.))
.build()?;
let g = ShapeBuilder::group()
.add_child(s1.clone())
.add_child(s2.clone())
.add_child(s3.clone())
.build()?;
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 4);
assert_eq!(xs[0].object, &s2);
assert_eq!(xs[1].object, &s2);
assert_eq!(xs[2].object, &s1);
assert_eq!(xs[3].object, &s1);
Ok(())
}
#[test]
fn intersecting_transformed_group() -> Result<(), Box<dyn std::error::Error>> {
let g = ShapeBuilder::group()
.transform(scaling(2., 2., 2.))
.add_child(
ShapeBuilder::sphere()
.transform(translation(5., 0., 0.))
.build()?,
)
.build()?;
let r = Ray::new(point(10., 0., -10.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 2);
Ok(())
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
mod shape_builder { mod shape_builder {