diff --git a/src/playing_around/cubes.rs b/src/playing_around/cubes.rs index 214a75a..25eecdc 100644 --- a/src/playing_around/cubes.rs +++ b/src/playing_around/cubes.rs @@ -20,13 +20,13 @@ use crate::{ }; pub fn run(width: usize, height: usize) -> Canvas { - let skybox = Object::new( + let skybox = Object::primitive( Shape::Cube, Material::with_pattern(Pattern::Const(Color::new(0.2, 0.35, 0.78))), Matrix::scaling_uniform(10.), ); - let floor = Object::new( + let floor = Object::primitive( Shape::Plane, Material { pattern: Pattern::checkers( @@ -53,31 +53,31 @@ pub fn run(width: usize, height: usize) -> Canvas { let leg_scaling = Matrix::identity().scale(0.08, 1., 0.08).transformed(); - let leg1 = Object::new( + let leg1 = Object::primitive( Shape::Cube, wood_material.clone(), leg_scaling.clone().translate(1., 0.5, 1.).transformed(), ); - let leg2 = Object::new( + let leg2 = Object::primitive( Shape::Cube, wood_material.clone(), leg_scaling.clone().translate(-1., 0.5, 1.).transformed(), ); - let leg3 = Object::new( + let leg3 = Object::primitive( Shape::Cube, wood_material.clone(), leg_scaling.clone().translate(1., 0.5, -1.).transformed(), ); - let leg4 = Object::new( + let leg4 = Object::primitive( Shape::Cube, wood_material.clone(), leg_scaling.clone().translate(-1., 0.5, -1.).transformed(), ); - let table_top = Object::new( + let table_top = Object::primitive( Shape::Cube, Material { reflectivity: 0.05, @@ -91,7 +91,7 @@ pub fn run(width: usize, height: usize) -> Canvas { let walls_width = 5.; - let walls = Object::new( + let walls = Object::primitive( Shape::Cube, Material::with_pattern(Pattern::stripe( Color::new(0.42, 0.55, 0.42), @@ -113,7 +113,7 @@ pub fn run(width: usize, height: usize) -> Canvas { let mirror_scale = Matrix::scaling(2.5, 1.5, 0.01); let mirror_translate = Matrix::translation(0., 2.25, -walls_width).transformed(); - let mirror_frame1 = Object::new( + let mirror_frame1 = Object::primitive( Shape::Cube, wood_material.clone(), frame_scaling_ver @@ -123,7 +123,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror_frame2 = Object::new( + let mirror_frame2 = Object::primitive( Shape::Cube, wood_material.clone(), frame_scaling_ver @@ -133,7 +133,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror_frame3 = Object::new( + let mirror_frame3 = Object::primitive( Shape::Cube, wood_material.clone(), frame_scaling_hor @@ -144,7 +144,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror_frame4 = Object::new( + let mirror_frame4 = Object::primitive( Shape::Cube, wood_material, frame_scaling_hor @@ -155,7 +155,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror = Object::new( + let mirror = Object::primitive( Shape::Cube, Material::mirror(), Matrix::identity() @@ -165,7 +165,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let glass_cube = Object::new( + let glass_cube = Object::primitive( Shape::Cube, Material::glass(), Matrix::identity() @@ -174,7 +174,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let tinted_cube = Object::new( + let tinted_cube = Object::primitive( Shape::Cube, Material { pattern: Pattern::Const(Color::new(0.4, 0.2, 0.3)), @@ -190,7 +190,7 @@ pub fn run(width: usize, height: usize) -> Canvas { let c1 = Color::new(0.4, 0.2, 0.3); let c2 = Color::new(0.2, 0.3, 0.4); - let pattern_cube = Object::new( + let pattern_cube = Object::primitive( Shape::Cube, Material::with_pattern(Pattern::checkers( c1, @@ -204,7 +204,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror_cube = Object::new( + let mirror_cube = Object::primitive( Shape::Cube, Material::mirror(), Matrix::identity() diff --git a/src/playing_around/cylinders.rs b/src/playing_around/cylinders.rs index 39f996b..7d34d6d 100644 --- a/src/playing_around/cylinders.rs +++ b/src/playing_around/cylinders.rs @@ -18,7 +18,7 @@ use crate::{ }; pub fn run(width: usize, height: usize) -> Canvas { - let skybox = Object::new( + let skybox = Object::primitive( Shape::Cube, Material::with_pattern(Pattern::Const(Color::new(0.2, 0.35, 0.78))), Matrix::scaling_uniform(10.), @@ -29,14 +29,14 @@ pub fn run(width: usize, height: usize) -> Canvas { .translate(-0.5, 0., 0.) .transformed(); - let arrow_body = Object::new( + let arrow_body = Object::primitive( Shape::unit_cylinder(), arrow_material.clone(), Matrix::scaling(0.1, 1., 0.1) .transform_chain(&arrow_rotation) .transformed(), ); - let arrow_head = Object::new( + let arrow_head = Object::primitive( Shape::unit_cone(), arrow_material, Matrix::scaling(0.2, 0.5, 0.2) @@ -45,7 +45,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let ice_cream_cone = Object::new( + let ice_cream_cone = Object::primitive( Shape::cone(1., 0.5, false), Material::with_pattern(Pattern::Const(Color::new(0.67, 0.57, 0.38))), Matrix::scaling(0.5, 1., 0.5) @@ -53,7 +53,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let vanilla_scoop = Object::new( + let vanilla_scoop = Object::primitive( Shape::Sphere, Material::with_pattern(Pattern::Const(Color::new(0.95, 0.89, 0.67))), Matrix::scaling(0.5, 0.5, 0.5) @@ -61,7 +61,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let choc_scoop = Object::new( + let choc_scoop = Object::primitive( Shape::Sphere, Material::with_pattern(Pattern::Const(Color::new(0.48, 0.24, 0.))), Matrix::scaling(0.5, 0.5, 0.5) diff --git a/src/playing_around/groups.rs b/src/playing_around/groups.rs index 430ce0b..a3c2d67 100644 --- a/src/playing_around/groups.rs +++ b/src/playing_around/groups.rs @@ -11,14 +11,14 @@ use crate::{ canvas::Canvas, color::Color, light::PointLightSource, - object::{cylinder::Cylinder, group::ObjectGroup, shape::Shape, Object}, + object::{cylinder::Cylinder, group::ObjectGroup, shape::Shape, Object, PrimitiveObject}, world::World, }, }; fn hexagon() -> Object { - let corner_sphere = Object::sphere(Point::new(0., 0., -1.), 0.25); - let cylinder = Object::with_transformation( + let corner_sphere = Object::from(PrimitiveObject::sphere(Point::new(0., 0., -1.), 0.25)); + let cylinder = Object::primitive_with_transformation( Shape::Cylinder(Cylinder { y_min: 0., y_max: 1., @@ -32,7 +32,7 @@ fn hexagon() -> Object { ); let mut hexagon_group = ObjectGroup::new(vec![cylinder.clone(), corner_sphere.clone()]); hexagon_group.add_bounding_box_as_obj(); - let hexagon_part = Object::with_shape(hexagon_group.into_shape()); + let hexagon_part = Object::from_group(hexagon_group); let mut hexagon = ObjectGroup::new(vec![hexagon_part.clone()]); @@ -40,7 +40,7 @@ fn hexagon() -> Object { hexagon.transform(&Matrix::rotation_y(consts::FRAC_PI_3)); hexagon.add_child(hexagon_part.clone()); } - Object::with_shape(hexagon.into_shape()) + Object::from_group(hexagon) } pub fn run(width: usize, height: usize) -> Canvas { diff --git a/src/playing_around/light_and_shading.rs b/src/playing_around/light_and_shading.rs index 5ef0041..f3f9905 100644 --- a/src/playing_around/light_and_shading.rs +++ b/src/playing_around/light_and_shading.rs @@ -6,7 +6,7 @@ use crate::{ intersection::IntersectionCollection, light::{color_of_illuminated_point, PointLightSource}, material::Material, - object::Object, + object::{Object, PrimitiveObject}, pattern::Pattern, ray::Ray, }, @@ -35,7 +35,7 @@ pub fn run() -> Canvas { ..Default::default() }; - let mut sphere_obj = Object::sphere(Point::new(0., 0., 0.), radius); + let mut sphere_obj = Object::from(PrimitiveObject::sphere(Point::new(0., 0., 0.), radius)); sphere_obj.set_material(material); let light = PointLightSource::new( diff --git a/src/playing_around/making_patterns.rs b/src/playing_around/making_patterns.rs index c160eb1..b4df692 100644 --- a/src/playing_around/making_patterns.rs +++ b/src/playing_around/making_patterns.rs @@ -5,7 +5,7 @@ use crate::{ color::Color, light::PointLightSource, material::Material, - object::{shape::Shape, Object}, + object::{shape::Shape, Object, PrimitiveObject}, pattern::Pattern, world::World, }, @@ -14,16 +14,17 @@ use crate::{ use super::making_scene; pub fn run(width: usize, height: usize) -> Canvas { - let floor = Object::with_shape_material( + let floor = PrimitiveObject::with_shape_material( Shape::Plane, Material::with_pattern(Pattern::ring( Color::new(0.15, 0.6, 0.7), Color::new(0.5, 0.1, 0.4), Some(Matrix::scaling_uniform(0.25)), )), - ); + ) + .into(); - let sphere = Object::new( + let sphere = Object::primitive( Shape::Sphere, Material::with_pattern(Pattern::checkers( Color::white(), diff --git a/src/playing_around/making_scene.rs b/src/playing_around/making_scene.rs index f88331f..e150890 100644 --- a/src/playing_around/making_scene.rs +++ b/src/playing_around/making_scene.rs @@ -8,56 +8,69 @@ use crate::{ vector::Vector, }, render::{ - camera::Camera, canvas::Canvas, color::Color, light::PointLightSource, material::Material, - object::shape::Shape, object::Object, pattern::Pattern, world::World, + camera::Camera, + canvas::Canvas, + color::Color, + light::PointLightSource, + material::Material, + object::{shape::Shape, Object}, + pattern::Pattern, + world::World, }, }; pub fn scene_objects() -> Vec { - let mut middle_sphere = - Object::with_transformation(Shape::Sphere, Matrix::translation(-0.5, 1., 0.5)); - - middle_sphere.set_material(Material { - pattern: Pattern::Const(Color::new(0.1, 1., 0.5)), - diffuse: 0.7, - specular: 0.3, - ..Default::default() - }); + let middle_sphere = Object::primitive( + Shape::Sphere, + Material { + pattern: Pattern::Const(Color::new(0.1, 1., 0.5)), + diffuse: 0.7, + specular: 0.3, + ..Default::default() + }, + Matrix::translation(-0.5, 1., 0.5), + ); - let mut right_sphere = Object::new( + let right_sphere = Object::primitive( Shape::Sphere, - middle_sphere.material().clone(), + Material { + pattern: Pattern::Const(Color::new(0.5, 1., 0.1)), + ..middle_sphere.material_unwrapped().clone() + }, Matrix::scaling_uniform(0.5) .translate(1.5, 0.5, -0.5) .transformed(), ); - right_sphere.material_mut().pattern = Pattern::Const(Color::new(0.5, 1., 0.1)); - let mut left_sphere = Object::with_transformation( + let left_sphere = Object::primitive( Shape::Sphere, + Material { + pattern: Pattern::Const(Color::new(1., 0.8, 0.1)), + diffuse: 0.7, + specular: 0.3, + ..Default::default() + }, Matrix::scaling_uniform(0.33) .translate(-1.5, 0.33, -0.75) .transformed(), ); - - left_sphere.set_material(Material { - pattern: Pattern::Const(Color::new(1., 0.8, 0.1)), - diffuse: 0.7, - specular: 0.3, - ..Default::default() - }); - vec![middle_sphere, right_sphere, left_sphere] } pub fn scene_walls() -> Vec { - let mut floor = Object::with_transformation(Shape::Sphere, Matrix::scaling(10., 0.01, 10.)); - floor.material_mut().specular = 0.; - floor.material_mut().pattern = Pattern::Const(Color::new(1., 0.9, 0.9)); + let floor = Object::primitive( + Shape::Sphere, + Material { + specular: 0., + pattern: Pattern::Const(Color::new(1., 0.9, 0.9)), + ..Default::default() + }, + Matrix::scaling(10., 0.01, 10.), + ); - let left_wall = Object::new( + let left_wall = Object::primitive( Shape::Sphere, - floor.material().clone(), + floor.material_unwrapped().clone(), Matrix::scaling(10., 0.01, 10.) .rotate_x(FRAC_PI_2) .rotate_y(-FRAC_PI_4) @@ -65,9 +78,9 @@ pub fn scene_walls() -> Vec { .transformed(), ); - let right_wall = Object::new( + let right_wall = Object::primitive( Shape::Sphere, - floor.material().clone(), + floor.material_unwrapped().clone(), Matrix::scaling(10., 0.01, 10.) .rotate_x(FRAC_PI_2) .rotate_y(FRAC_PI_4) diff --git a/src/playing_around/planes.rs b/src/playing_around/planes.rs index 65beb54..e16ccfb 100644 --- a/src/playing_around/planes.rs +++ b/src/playing_around/planes.rs @@ -14,8 +14,9 @@ use super::making_scene; pub fn run(width: usize, height: usize) -> Canvas { let mut objects = making_scene::scene_objects(); let lights = making_scene::scene_lights(); - let plane = Object::with_transformation(Shape::Plane, Matrix::translation(0., 0.5, 0.)); - let plane2 = Object::with_transformation( + let plane = + Object::primitive_with_transformation(Shape::Plane, Matrix::translation(0., 0.5, 0.)); + let plane2 = Object::primitive_with_transformation( Shape::Plane, Matrix::rotation_x(consts::FRAC_PI_2) .translate(0., 0., 0.) diff --git a/src/playing_around/rays_and_spheres.rs b/src/playing_around/rays_and_spheres.rs index b64a614..e08d746 100644 --- a/src/playing_around/rays_and_spheres.rs +++ b/src/playing_around/rays_and_spheres.rs @@ -8,7 +8,10 @@ use crate::{ vector::Vector, }, render::{ - canvas::Canvas, color::Color, intersection::IntersectionCollection, object::Object, + canvas::Canvas, + color::Color, + intersection::IntersectionCollection, + object::{Object, PrimitiveObject}, ray::Ray, }, }; @@ -22,7 +25,10 @@ pub fn run() -> Canvas { let radius = SPHERE_RADIUS as f64; let canvas_center = CANVAS_SIZE as f64 / 2.0; - let mut sphere_obj = Object::sphere(Point::new(canvas_center, canvas_center, 0.), radius); + let mut sphere_obj = Object::from(PrimitiveObject::sphere( + Point::new(canvas_center, canvas_center, 0.), + radius, + )); sphere_obj.transform( Matrix::identity() diff --git a/src/playing_around/reflections.rs b/src/playing_around/reflections.rs index 05d4d90..74822e5 100644 --- a/src/playing_around/reflections.rs +++ b/src/playing_around/reflections.rs @@ -23,10 +23,16 @@ pub fn get_walls() -> Vec { let dist = 12.; - let mut floor = Object::new(Shape::Plane, material.clone(), Matrix::identity()); - floor.material_mut().reflectivity = 0.4; + let floor = Object::primitive( + Shape::Plane, + Material { + reflectivity: 0.4, + ..material.clone() + }, + Matrix::identity(), + ); - let left_wall = Object::new( + let left_wall = Object::primitive( Shape::Plane, material.clone(), Matrix::rotation_x(FRAC_PI_2) @@ -35,7 +41,7 @@ pub fn get_walls() -> Vec { .transformed(), ); - let right_wall = Object::new( + let right_wall = Object::primitive( Shape::Plane, material, Matrix::rotation_x(FRAC_PI_2) @@ -70,7 +76,7 @@ pub fn run(width: usize, height: usize) -> Canvas { let mirror_dist = 3.; - let mirror_wall = Object::new( + let mirror_wall = Object::primitive( Shape::Plane, mirror.clone(), Matrix::rotation_x(FRAC_PI_2) @@ -79,7 +85,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mirror_wall2 = Object::new( + let mirror_wall2 = Object::primitive( Shape::Plane, mirror, Matrix::rotation_x(FRAC_PI_2) diff --git a/src/playing_around/refractions.rs b/src/playing_around/refractions.rs index a202df3..8727914 100644 --- a/src/playing_around/refractions.rs +++ b/src/playing_around/refractions.rs @@ -31,9 +31,9 @@ pub fn run(width: usize, height: usize) -> Canvas { ), )); - let floor = Object::new(Shape::Plane, material.clone(), Matrix::identity()); + let floor = Object::primitive(Shape::Plane, material.clone(), Matrix::identity()); - let wall = Object::new( + let wall = Object::primitive( Shape::Plane, material, Matrix::rotation_x(FRAC_PI_2) @@ -41,7 +41,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let small_sphere = Object::new( + let small_sphere = Object::primitive( Shape::Sphere, Material { pattern: Pattern::Const(glass_color), @@ -52,13 +52,13 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let mid_sphere = Object::new( + let mid_sphere = Object::primitive( Shape::Sphere, Material::glass(), Matrix::translation(0., 1., -1.5).transformed(), ); - let mid_sphere_air_pocket = Object::new( + let mid_sphere_air_pocket = Object::primitive( Shape::Sphere, Material::air(), Matrix::scaling_uniform(0.6) diff --git a/src/playing_around/shadows.rs b/src/playing_around/shadows.rs index 9c882ef..6b3890f 100644 --- a/src/playing_around/shadows.rs +++ b/src/playing_around/shadows.rs @@ -7,13 +7,18 @@ use crate::{ tuple::Tuple, }, render::{ - camera::Camera, canvas::Canvas, color::Color, light::PointLightSource, material::Material, - object::shape::Shape, object::Object, world::World, + camera::Camera, + canvas::Canvas, + color::Color, + light::PointLightSource, + material::Material, + object::{shape::Shape, Object, PrimitiveObject}, + world::World, }, }; pub fn run(width: usize, height: usize) -> Canvas { - let wall = Object::new( + let wall = Object::primitive( Shape::Sphere, Material::matte_with_color(Color::new(0.4, 0.7, 0.9)), Matrix::scaling(50., 50., 0.1) @@ -28,16 +33,16 @@ pub fn run(width: usize, height: usize) -> Canvas { let x = 1.5; let z = -8.; - let mut sphere1 = Object::sphere(Point::new(x, 0., z), 1.); + let mut sphere1 = PrimitiveObject::sphere(Point::new(x, 0., z), 1.); sphere1.set_material(gray.clone()); - let mut sphere2 = Object::sphere(Point::new(x, 1., z), 0.7); + let mut sphere2 = PrimitiveObject::sphere(Point::new(x, 1., z), 0.7); sphere2.set_material(gray.clone()); - let mut sphere3 = Object::sphere(Point::new(x, 1.8, z), 0.4); + let mut sphere3 = PrimitiveObject::sphere(Point::new(x, 1.8, z), 0.4); sphere3.set_material(gray); - let carrot = Object::new( + let carrot = Object::primitive( Shape::Sphere, orange, Matrix::scaling(0.4, 0.1, 0.1) @@ -45,7 +50,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let flat = Object::new( + let flat = Object::primitive( Shape::Sphere, black.clone(), Matrix::scaling(0.4, 0.05, 0.4) @@ -53,7 +58,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let cylinder = Object::new( + let cylinder = Object::primitive( Shape::Sphere, black.clone(), Matrix::scaling(0.25, 0.55, 0.25) @@ -61,7 +66,7 @@ pub fn run(width: usize, height: usize) -> Canvas { .transformed(), ); - let top = Object::new( + let top = Object::primitive( Shape::Sphere, black, Matrix::scaling(0.15, 0.085, 0.15) @@ -72,7 +77,16 @@ pub fn run(width: usize, height: usize) -> Canvas { let light_source = PointLightSource::new(Point::new(2. * x, 1., 4.), Color::white()); let world = World::new( - vec![wall, sphere1, sphere2, sphere3, carrot, flat, cylinder, top], + vec![ + wall, + sphere1.into(), + sphere2.into(), + sphere3.into(), + carrot, + flat, + cylinder, + top, + ], vec![light_source], None, ); diff --git a/src/render/intersection.rs b/src/render/intersection.rs index af7eb77..64c8d0f 100644 --- a/src/render/intersection.rs +++ b/src/render/intersection.rs @@ -184,7 +184,11 @@ impl<'a> IntersecComputations<'a> { for inter in intersections { if inter == hit && !containers.is_empty() { - refractive_from = containers.last().unwrap().material().refractive_index; + refractive_from = containers + .last() + .unwrap() + .material_unwrapped() + .refractive_index; } if let Some(idx) = containers @@ -198,7 +202,11 @@ impl<'a> IntersecComputations<'a> { if inter == hit { if !containers.is_empty() { - refractive_to = containers.last().unwrap().material().refractive_index; + refractive_to = containers + .last() + .unwrap() + .material_unwrapped() + .refractive_index; } break; } @@ -211,7 +219,7 @@ impl<'a> IntersecComputations<'a> { intersection, ray, AIR_REFRACTIVE_INDEX, - intersection.object().material().refractive_index, + intersection.object().material_unwrapped().refractive_index, ) } @@ -224,7 +232,7 @@ impl<'a> IntersecComputations<'a> { } pub fn material(&self) -> &Material { - self.object().material() + self.object().material_unwrapped() } pub fn world_point(&self) -> Point { @@ -286,7 +294,7 @@ impl<'a> IntersectionCollection<'a> { pub fn from_ray_and_mult_objects(ray: Ray, objects: &'a [Object]) -> Self { let mut collector = IntersectionCollector::new(); for object in objects { - object.intersect_with_collector(&ray, &mut collector); + object.intersect(&ray, &mut collector); } Self::new_with_sorted_vec(ray, collector.collect_sorted()) @@ -352,7 +360,7 @@ mod tests { #[test] fn intersect_sphere() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let intersections = IntersectionCollection::from_ray_and_obj(ray, &obj); assert_eq!(intersections.count(), 2); @@ -365,7 +373,7 @@ mod tests { #[test] fn ray_intersects_sphere_at_tangent() { let ray = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let intersections = IntersectionCollection::from_ray_and_obj(ray, &obj); assert_eq!(intersections.count(), 2); @@ -378,7 +386,7 @@ mod tests { #[test] fn ray_misses_sphere() { let ray = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); assert_eq!( IntersectionCollection::from_ray_and_obj(ray, &obj).count(), @@ -388,7 +396,7 @@ mod tests { #[test] fn intersect_ray_originates_inside_sphere() { let ray = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let intersections = IntersectionCollection::from_ray_and_obj(ray, &obj); assert_eq!(intersections.count(), 2); @@ -401,7 +409,7 @@ mod tests { #[test] fn intersect_ray_behind_sphere() { let ray = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let intersections = IntersectionCollection::from_ray_and_obj(ray, &obj); assert_eq!(intersections.count(), 2); @@ -415,7 +423,7 @@ mod tests { #[test] fn intersection_hit_all_times_positive() { let sphere = Shape::Sphere; - let obj = Object::with_shape(sphere); + let obj = Object::primitive_with_shape(sphere); let ray = Ray::new(Point::zero(), Vector::zero()); let intersections = IntersectionCollection::from_times_and_obj(ray, vec![1., 2.], &obj); @@ -427,7 +435,7 @@ mod tests { #[test] fn intersection_hit_with_negative_time() { let sphere = Shape::Sphere; - let obj = Object::with_shape(sphere); + let obj = Object::primitive_with_shape(sphere); let ray = Ray::new(Point::zero(), Vector::zero()); let intersections = IntersectionCollection::from_times_and_obj(ray, vec![1., -1.], &obj); @@ -439,7 +447,7 @@ mod tests { #[test] fn intersection_hit_all_times_negative() { let sphere = Shape::Sphere; - let obj = Object::with_shape(sphere); + let obj = Object::primitive_with_shape(sphere); let ray = Ray::new(Point::zero(), Vector::zero()); let intersections = IntersectionCollection::from_times_and_obj(ray, vec![-2., -1.], &obj); @@ -450,7 +458,7 @@ mod tests { #[test] fn intersection_hit_always_smallest_nonnegative() { let sphere = Shape::Sphere; - let obj = Object::with_shape(sphere); + let obj = Object::primitive_with_shape(sphere); let ray = Ray::new(Point::zero(), Vector::zero()); let intersections = @@ -463,7 +471,7 @@ mod tests { #[test] fn intersec_comps_outside_obj() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let inter_vec = IntersectionCollection::from_ray_and_obj(ray.clone(), &obj); let comps = inter_vec.hit().unwrap().computations(&ray); @@ -472,7 +480,7 @@ mod tests { #[test] fn intersec_comps_inside_obj() { let ray = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); - let obj = Object::with_shape(Shape::Sphere); + let obj = Object::primitive_with_shape(Shape::Sphere); let inter_vec = IntersectionCollection::from_ray_and_obj(ray.clone(), &obj); let comps = inter_vec.hit().unwrap().computations(&ray); @@ -488,7 +496,8 @@ mod tests { #[test] fn hit_should_offset_point() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_transformation(Shape::Sphere, Matrix::translation(0., 0., 10.)); + let obj = + Object::primitive_with_transformation(Shape::Sphere, Matrix::translation(0., 0., 10.)); let inter = Intersection::new(5., &obj); let comps = inter.computations(&ray); @@ -499,7 +508,7 @@ mod tests { #[test] fn intersect_plane_with_parallel_ray() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let ray = Ray::new(Point::new(0., 10., 0.), Vector::new(0., 0., 1.)); let intersections = IntersectionCollection::from_ray_and_obj(ray, &plane); @@ -508,7 +517,7 @@ mod tests { #[test] fn intersect_plane_with_coplanar_ray() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let ray = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); let intersections = IntersectionCollection::from_ray_and_obj(ray, &plane); @@ -517,7 +526,7 @@ mod tests { #[test] fn precomputing_refletion_vecctor() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let half_sqrt = consts::FRAC_1_SQRT_2; let r = Ray::new( Point::new(0., 1., -1.), @@ -531,12 +540,12 @@ mod tests { #[test] fn finding_reflective_exiting_entering_various_intersections() { - let sphere_a = Object::new( + let sphere_a = Object::primitive( Shape::Sphere, Material::glass(), Matrix::scaling(2., 2., 2.), ); - let sphere_b = Object::new( + let sphere_b = Object::primitive( Shape::Sphere, Material { refractive_index: 2., @@ -544,7 +553,7 @@ mod tests { }, Matrix::translation(0., 0., -0.25), ); - let sphere_c = Object::new( + let sphere_c = Object::primitive( Shape::Sphere, Material { refractive_index: 2.5, @@ -587,7 +596,7 @@ mod tests { #[test] fn under_point_if_offset_below_surface() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let sphere = Object::new( + let sphere = Object::primitive( Shape::Sphere, Material::glass(), Matrix::translation(0., 0., 1.), @@ -603,7 +612,7 @@ mod tests { #[test] fn over_under_points_dont_approx_eq_actual_points() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let sphere = Object::new( + let sphere = Object::primitive( Shape::Sphere, Material::glass(), Matrix::translation(0., 0., 1.), diff --git a/src/render/light.rs b/src/render/light.rs index 9d8bfbb..ab0b106 100644 --- a/src/render/light.rs +++ b/src/render/light.rs @@ -37,7 +37,7 @@ pub fn color_of_illuminated_point( normal_v: Vector, shadow_intensity: f64, ) -> Color { - let material = object.material(); + let material = object.material_unwrapped(); // combine surface color with lights's intensity (color) let effetive_color = material.color_at_object(object, point) * light_source.intensity; @@ -101,8 +101,11 @@ mod tests { approx_eq::ApproxEq, primitive::{matrix::Matrix, tuple::Tuple}, render::{ - intersection::IntersectionCollection, material::Material, object::shape::Shape, - pattern::Pattern, ray::Ray, + intersection::IntersectionCollection, + material::Material, + object::{shape::Shape, PrimitiveObject}, + pattern::Pattern, + ray::Ray, }, }; @@ -111,7 +114,7 @@ mod tests { #[test] fn lighting_with_surface_in_shadow() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., 0., -1.); let normal_v = Vector::new(0., 0., -1.); @@ -125,7 +128,7 @@ mod tests { #[test] fn lighting_with_eye_between_light_and_surface() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., 0., -1.); let normal_v = Vector::new(0., 0., -1.); @@ -139,7 +142,7 @@ mod tests { #[test] fn lighting_with_eye_between_light_and_surface_eye_offset_45() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., FRAC_1_SQRT_2, -FRAC_1_SQRT_2); let normal_v = Vector::new(0., 0., -1.); @@ -153,7 +156,7 @@ mod tests { #[test] fn lighting_with_eye_opposite_surface_light_offset_45() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., 0., -1.); let normal_v = Vector::new(0., 0., -1.); @@ -168,7 +171,7 @@ mod tests { #[test] fn lighting_with_eye_in_path_of_reflection() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., -FRAC_1_SQRT_2, -FRAC_1_SQRT_2); let normal_v = Vector::new(0., 0., -1.); @@ -183,7 +186,7 @@ mod tests { #[test] fn lighting_with_light_behind_surface() { let point = Point::zero(); - let obj = Object::with_shape(Shape::Sphere); + let obj = PrimitiveObject::with_shape(Shape::Sphere).into(); let eye_v = Vector::new(0., 0., -1.); let normal_v = Vector::new(0., 0., -1.); @@ -203,7 +206,7 @@ mod tests { specular: 0., ..Default::default() }; - let obj = Object::with_shape_material(Shape::Sphere, material); + let obj = PrimitiveObject::with_shape_material(Shape::Sphere, material).into(); let eye_v = Vector::new(0., 0., -1.); let normal_v = Vector::new(0., 0., -1.); @@ -235,7 +238,7 @@ mod tests { #[test] fn schlick_reflectance_under_total_internal_reflection() { - let sphere = Object::new(Shape::Sphere, Material::glass(), Matrix::identity()); + let sphere = Object::primitive(Shape::Sphere, Material::glass(), Matrix::identity()); let ray = Ray::new(Point::new(0., 0., FRAC_1_SQRT_2), Vector::new(0., 1., 0.)); let intersections = IntersectionCollection::from_times_and_obj( @@ -250,7 +253,7 @@ mod tests { #[test] fn schlick_reflectance_with_perpendicular_viewing_angle() { - let sphere = Object::new(Shape::Sphere, Material::glass(), Matrix::identity()); + let sphere = Object::primitive(Shape::Sphere, Material::glass(), Matrix::identity()); let ray = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 1., 0.)); let intersections = IntersectionCollection::from_times_and_obj(ray, vec![-1., 1.], &sphere); @@ -261,7 +264,7 @@ mod tests { #[test] fn schlick_reflectance_with_small_angle_and_n1_greater_than_n2() { - let sphere = Object::new(Shape::Sphere, Material::glass(), Matrix::identity()); + let sphere = Object::primitive(Shape::Sphere, Material::glass(), Matrix::identity()); let ray = Ray::new(Point::new(0., 0.99, -2.), Vector::new(0., 0., 1.)); let intersections = IntersectionCollection::from_times_and_obj(ray, vec![1.8589], &sphere); diff --git a/src/render/obj_parser.rs b/src/render/obj_parser.rs index 8d045f3..8cc0889 100644 --- a/src/render/obj_parser.rs +++ b/src/render/obj_parser.rs @@ -51,7 +51,7 @@ impl ObjParser { .windows(2) .skip(1) .map(|slice| match slice { - [id1, id2] => Object::with_shape(Shape::triangle( + [id1, id2] => Object::primitive_with_shape(Shape::triangle( v, self.vertices[*id1], self.vertices[*id2], @@ -75,7 +75,7 @@ impl ObjParser { let v2 = *pair.0; let n2 = *pair.1; - Object::with_shape(Shape::smooth_triangle( + Object::primitive_with_shape(Shape::smooth_triangle( v, self.vertices[v1], self.vertices[v2], @@ -174,15 +174,13 @@ impl ObjParser { pub fn into_group(mut self) -> ObjectGroup { self.groups.into_iter().for_each(|(_, group)| { - self.main_group.add_child(group.into_object()); + self.main_group.add_child(group.into()); }); self.main_group.clone() } pub fn parse_to_object(source: String) -> Result { - Self::default() - .parse(source) - .map(|group| group.into_object()) + Self::default().parse(source).map(|group| group.into()) } } @@ -231,8 +229,8 @@ mod tests { } fn _obj_as_triangle(object: &Object) -> Option { - match object.shape() { - Shape::Triangle(t) => Some(t.clone()), + match object.as_primitive().map(|p| p.shape()) { + Some(Shape::Triangle(t)) => Some(t.clone()), _ => None, } } @@ -331,8 +329,8 @@ mod tests { let children = group.children(); assert_eq!(children.len(), 2); - assert!(matches!(children[0].shape(), &Shape::Group(_))); - assert!(matches!(children[1].shape(), &Shape::Group(_))); + assert!(children[0].as_group().is_some()); + assert!(children[1].as_group().is_some()); } #[test] @@ -353,8 +351,8 @@ mod tests { } fn _obj_as_smooth_triangle(object: &Object) -> SmoothTriangle { - match object.shape() { - Shape::SmoothTriangle(t) => t.clone(), + match object.as_primitive().map(|p| p.shape()) { + Some(Shape::SmoothTriangle(t)) => t.clone(), _ => unreachable!(), } } diff --git a/src/render/object.rs b/src/render/object.rs index af7a0b8..b16cce0 100644 --- a/src/render/object.rs +++ b/src/render/object.rs @@ -25,56 +25,120 @@ use super::{ }; #[derive(Clone, Debug)] -pub struct Object { - shape: Shape, - material: Material, - transformation: Matrix, - transformation_inverse: Matrix, +pub enum Object { + Primitive(Box), + Group(ObjectGroup), +} + +impl From for Object { + fn from(obj: PrimitiveObject) -> Self { + Self::from_primitive(obj) + } +} + +impl From for Object { + fn from(group: ObjectGroup) -> Self { + Self::from_group(group) + } } impl Transform for Object { fn transform(&mut self, matrix: &Matrix) { - match &mut self.shape { - Shape::Group(group) => { - group.transform(matrix); - } - _ => { - self.transformation = matrix * &self.transformation; - self.transformation_inverse = self.transformation.inverse().unwrap(); - } + match self { + Self::Primitive(obj) => obj.transform(matrix), + Self::Group(group) => group.transform(matrix), } } fn transform_new(&self, matrix: &Matrix) -> Self { - let mut new_obj = self.clone(); - new_obj.transform(matrix); - new_obj + let mut new = self.clone(); + new.transform(matrix); + new } } impl Object { - pub fn new(shape: Shape, material: Material, transformation: Matrix) -> Self { - Self { - shape, - material, - transformation, - transformation_inverse: transformation - .inverse() - .expect("Object with singular tranfromation matrix cannot be rendered"), + pub fn group_with_children(children: Vec) -> Self { + Self::from_group(ObjectGroup::new(children)) + } + + pub fn from_group(group: ObjectGroup) -> Self { + Self::Group(group) + } + + pub fn from_primitive(obj: PrimitiveObject) -> Self { + Self::Primitive(Box::new(obj)) + } + + pub fn primitive(shape: Shape, material: Material, transformation: Matrix) -> Self { + Self::from_primitive(PrimitiveObject::new(shape, material, transformation)) + } + + pub fn primitive_with_shape(shape: Shape) -> Self { + Self::from_primitive(PrimitiveObject::with_shape(shape)) + } + + pub fn primitive_with_transformation(shape: Shape, transformation: Matrix) -> Self { + Self::from_primitive(PrimitiveObject::with_transformation(shape, transformation)) + } + + pub fn normal_vector_at(&self, world_point: Point) -> Vector { + self.normal_vector_at_with_intersection(world_point, None) + } + + pub fn normal_vector_at_with_intersection<'a>( + &self, + world_point: Point, + i: Option<&'a Intersection<'a>>, + ) -> Vector { + match self { + Self::Primitive(obj) => obj.normal_vector_at_with_intersection(world_point, i), + Self::Group(_) => todo!(), } } - pub fn group(children: Vec, transformation: Matrix) -> Self { - Self::with_shape(Shape::Group(ObjectGroup::with_transformations( - children, - transformation, - ))) + pub fn intersect<'a>(&'a self, world_ray: &Ray, collector: &mut IntersectionCollector<'a>) { + collector.set_next_object(self); + match self { + Self::Primitive(obj) => obj.intersect_with_collector(world_ray, collector), + Self::Group(group) => group.intersect(world_ray, collector), + } } - pub fn bounding_box(&self) -> BoundingBox { - self.shape - .bounding_box() - .transform_new(&self.transformation) + pub fn intersect_to_vec<'a>(&'a self, world_ray: &Ray) -> Vec> { + let mut collector = IntersectionCollector::with_next_object(self); + self.intersect(world_ray, &mut collector); + collector.collect_sorted() + } + + pub fn intersection_times(&self, world_ray: &Ray) -> Vec { + self.intersect_to_vec(world_ray) + .iter_mut() + .map(|i| i.time()) + .collect() + } + + pub fn is_intersected_by_ray(&self, ray: &Ray) -> bool { + !self.intersect_to_vec(ray).is_empty() + } + + pub fn material(&self) -> Option<&Material> { + self.as_primitive().map(|p| p.material()) + } + + pub fn material_mut(&mut self) -> Option<&mut Material> { + self.as_primitive_mut().map(|p| p.material_mut()) + } + + pub fn set_material(&mut self, material: Material) { + match self { + Self::Primitive(obj) => obj.set_material(material), + Self::Group(group) => group.set_material(material), + } + } + + pub fn material_unwrapped(&self) -> &Material { + self.material().expect("Object has no material") } pub fn normalize_and_center(&mut self) { @@ -90,6 +154,78 @@ impl Object { ); } + pub fn transformation(&self) -> Matrix { + match self { + Self::Primitive(obj) => obj.transformation, + Self::Group(_) => Matrix::identity(), + } + } + + pub fn transformation_inverse(&self) -> Matrix { + match self { + Self::Primitive(obj) => obj.transformation_inverse(), + Self::Group(_) => Matrix::identity(), + } + } + + pub fn bounding_box(&self) -> BoundingBox { + match self { + Self::Primitive(obj) => obj.bounding_box(), + Self::Group(group) => group.bounding_box().clone(), + } + } + pub fn as_group(&self) -> Option<&ObjectGroup> { + match self { + Self::Group(group) => Some(group), + _ => None, + } + } + pub fn as_group_mut(&mut self) -> Option<&mut ObjectGroup> { + match self { + Self::Group(group) => Some(group), + _ => None, + } + } + pub fn as_primitive(&self) -> Option<&PrimitiveObject> { + match self { + Self::Primitive(obj) => Some(obj), + _ => None, + } + } + pub fn as_primitive_mut(&mut self) -> Option<&mut PrimitiveObject> { + match self { + Self::Primitive(obj) => Some(obj), + _ => None, + } + } +} + +#[derive(Clone, Debug)] +pub struct PrimitiveObject { + shape: Shape, + material: Material, + transformation: Matrix, + transformation_inverse: Matrix, +} + +impl PrimitiveObject { + pub fn new(shape: Shape, material: Material, transformation: Matrix) -> Self { + Self { + shape, + material, + transformation, + transformation_inverse: transformation + .inverse() + .expect("Object with singular tranfromation matrix cannot be rendered"), + } + } + + pub fn bounding_box(&self) -> BoundingBox { + self.shape + .bounding_box() + .transform_new(&self.transformation) + } + pub fn with_shape(shape: Shape) -> Self { Self::with_transformation(shape, Matrix::identity()) } @@ -135,57 +271,38 @@ impl Object { world_ray: &Ray, collector: &mut IntersectionCollector<'a>, ) { - match &self.shape { - Shape::Group(ref group) => group.intersect(world_ray, collector), - _ => { - collector.set_next_object(self); - self.shape.local_intersect( - &world_ray.transform_new(&self.transformation_inverse), - collector, - ); - } - } - } - - pub fn intersect_to_vec<'a>(&'a self, world_ray: &Ray) -> Vec> { - let mut collector = IntersectionCollector::with_next_object(self); - self.intersect_with_collector(world_ray, &mut collector); - collector.collect_sorted() - } - - pub fn intersection_times(&self, world_ray: &Ray) -> Vec { - self.intersect_to_vec(world_ray) - .iter_mut() - .map(|i| i.time()) - .collect() + self.shape.local_intersect( + &world_ray.transform_new(&self.transformation_inverse), + collector, + ); } - pub fn is_intersected_by_ray(&self, ray: &Ray) -> bool { - !self.intersect_to_vec(ray).is_empty() - } pub fn material(&self) -> &Material { &self.material } pub fn set_material(&mut self, material: Material) { - if let Shape::Group(group) = &mut self.shape { - group.set_material(material.clone()) - } self.material = material; } pub fn material_mut(&mut self) -> &mut Material { &mut self.material } +} - pub fn get_group(&self) -> Option<&ObjectGroup> { - self.shape.as_group() +impl Transform for PrimitiveObject { + fn transform(&mut self, matrix: &Matrix) { + self.transformation.transform(matrix); + self.transformation_inverse = self + .transformation + .inverse() + .expect("Object with singular tranfromation matrix cannot be rendered"); } - pub fn get_group_mut(&mut self) -> Option<&mut ObjectGroup> { - match &mut self.shape { - Shape::Group(group) => Some(group), - _ => None, - } + + fn transform_new(&self, matrix: &Matrix) -> Self { + let mut new = self.clone(); + new.transform(matrix); + new } } @@ -197,14 +314,14 @@ mod tests { #[test] fn identiy_matrix_is_obj_default_transformation() { assert_eq!( - Object::with_shape(Shape::Sphere).transformation_inverse(), + Object::primitive_with_shape(Shape::Sphere).transformation_inverse(), Matrix::identity() ); } #[test] fn normal_is_normalized() { - let sphere_obj = Object::with_shape(Shape::Sphere); + let sphere_obj = Object::primitive_with_shape(Shape::Sphere); let frac_sqrt_3_3 = 3_f64.sqrt() / 3.; let normal = diff --git a/src/render/object/bounding_box.rs b/src/render/object/bounding_box.rs index 22febf8..554608e 100644 --- a/src/render/object/bounding_box.rs +++ b/src/render/object/bounding_box.rs @@ -67,7 +67,7 @@ impl BoundingBox { self.max.z().max(point.z()), ); } - pub fn add_bounding_box(&mut self, other: BoundingBox) { + pub fn add_bounding_box(&mut self, other: &BoundingBox) { if other.is_empty() { return; } @@ -173,7 +173,7 @@ impl BoundingBox { let z_len = self.max.z() - self.min.z(); let center = self.center(); let pattern = Pattern::Const(Color::new(0.5, 0.5, 0.5)); - Object::new( + Object::primitive( Shape::Cube, Material { pattern, @@ -252,7 +252,7 @@ mod tests { bb1.add_point(Point::new(1.0, 2.0, 3.0)); let mut bb2 = BoundingBox::empty(); bb2.add_point(Point::new(4.0, 5.0, 6.0)); - bb1.add_bounding_box(bb2); + bb1.add_bounding_box(&bb2); assert_eq!(bb1.min, Point::new(1.0, 2.0, 3.0)); assert_eq!(bb1.max, Point::new(4.0, 5.0, 6.0)); } diff --git a/src/render/object/cone.rs b/src/render/object/cone.rs index 8ed71d3..1142bb0 100644 --- a/src/render/object/cone.rs +++ b/src/render/object/cone.rs @@ -148,7 +148,7 @@ mod tests { #[test] fn intersecting_cone() { - let cone = Object::with_shape(Shape::default_cone()); + let cone = Object::primitive_with_shape(Shape::default_cone()); let examples = vec![ (Point::new(0., 0., -5.), Vector::new(0., 0., 1.), (5., 5.)), @@ -176,7 +176,7 @@ mod tests { #[test] fn intersecting_cone_with_ray_parallel_to_one_half() { - let cone = Object::with_shape(Shape::default_cone()); + let cone = Object::primitive_with_shape(Shape::default_cone()); let ray = Ray::new(Point::new(0., 0., -1.), Vector::new(0., 1., 1.).normalize()); let times = cone.intersection_times(&ray); @@ -186,7 +186,7 @@ mod tests { #[test] fn intersecting_cone_caps() { - let cone = Object::with_shape(Shape::Cone(Cone { + let cone = Object::primitive_with_shape(Shape::Cone(Cone { y_min: -0.5, y_max: 0.5, closed: true, @@ -206,7 +206,7 @@ mod tests { #[test] fn normal_of_cone_caps() { - let cone = Object::with_shape(Shape::default_cone()); + let cone = Object::primitive_with_shape(Shape::default_cone()); let examples = vec![ (Point::new(0., 0., 0.), Vector::new(0., 0., 0.)), diff --git a/src/render/object/cube.rs b/src/render/object/cube.rs index cc35aa4..dbd4307 100644 --- a/src/render/object/cube.rs +++ b/src/render/object/cube.rs @@ -76,7 +76,7 @@ mod tests { #[test] fn ray_intersects_cube() { - let cube = Object::with_shape(Shape::Cube); + let cube = Object::primitive_with_shape(Shape::Cube); let examples = vec![ Ray::new(Point::new(5., 0.5, 0.), Vector::new(-1., 0., 0.)), Ray::new(Point::new(-5., 0.5, 0.), Vector::new(1., 0., 0.)), @@ -104,7 +104,7 @@ mod tests { #[test] fn ray_misses_cube() { - let cube = Object::with_shape(Shape::Cube); + let cube = Object::primitive_with_shape(Shape::Cube); let rays = vec![ Ray::new(Point::new(-2., 0., 0.), Vector::new(0.2673, 0.5345, 0.8018)), diff --git a/src/render/object/cylinder.rs b/src/render/object/cylinder.rs index 41506d4..796f60e 100644 --- a/src/render/object/cylinder.rs +++ b/src/render/object/cylinder.rs @@ -142,7 +142,7 @@ mod tests { #[test] fn ray_misses_cylinder() { - let cyl = Object::with_shape(Shape::default_cylinder()); + let cyl = Object::primitive_with_shape(Shape::default_cylinder()); let examples = vec![ Ray::new(Point::new(1., 0., 0.), Vector::new(0., 1., 0.)), Ray::new(Point::new(0., 0., 0.), Vector::new(0., 1., 0.)), @@ -156,7 +156,7 @@ mod tests { #[test] fn ray_intersects_cylinder() { - let cyl = Object::with_shape(Shape::default_cylinder()); + let cyl = Object::primitive_with_shape(Shape::default_cylinder()); let examples = vec![ ( @@ -189,7 +189,7 @@ mod tests { #[test] fn normal_of_cylinder() { - let cyl = Object::with_shape(Shape::default_cylinder()); + let cyl = Object::primitive_with_shape(Shape::default_cylinder()); let examples = vec![ (Point::new(1., 0., 0.), Vector::new(1., 0., 0.)), @@ -204,7 +204,7 @@ mod tests { } fn get_cylinder() -> Object { - Object::with_shape(Shape::Cylinder(Cylinder { + Object::primitive_with_shape(Shape::Cylinder(Cylinder { y_min: 1., y_max: 2., closed: false, @@ -241,7 +241,7 @@ mod tests { #[test] fn intersecting_cylinder_end_caps() { - let cyl = Object::with_shape(Shape::Cylinder(Cylinder { + let cyl = Object::primitive_with_shape(Shape::Cylinder(Cylinder { y_min: 1., y_max: 2., closed: true, diff --git a/src/render/object/group.rs b/src/render/object/group.rs index c6044a1..a7a412a 100644 --- a/src/render/object/group.rs +++ b/src/render/object/group.rs @@ -4,7 +4,7 @@ use crate::{ render::{intersection::IntersectionCollector, material::Material, ray::Ray}, }; -use super::{bounding_box::BoundingBox, shape::Shape, Object}; +use super::{bounding_box::BoundingBox, Object}; #[derive(Clone, Debug)] /// A group of objects that can be transformed simultaneously. @@ -28,7 +28,7 @@ impl ObjectGroup { pub fn new(children: Vec) -> Self { let mut bounding_box = BoundingBox::empty(); for child in children.iter() { - bounding_box.add_bounding_box(child.bounding_box()); + bounding_box.add_bounding_box(&child.bounding_box()); } Self { children, @@ -50,7 +50,7 @@ impl ObjectGroup { } } pub fn add_child(&mut self, child: Object) { - self.bounding_box.add_bounding_box(child.bounding_box()); + self.bounding_box.add_bounding_box(&child.bounding_box()); self.children.push(child); } pub fn add_children(&mut self, children: impl IntoIterator) { @@ -87,7 +87,7 @@ impl ObjectGroup { if dist_to_group < min_d || dist_to_group.approx_eq(&min_d) { group.children.push(child); } else { - boxes[min_id].add_bounding_box(child_box); + boxes[min_id].add_bounding_box(&child_box); vectors[min_id].push(child); } }); @@ -98,7 +98,7 @@ impl ObjectGroup { .zip(boxes.into_iter()) .filter(|(v, _)| !v.is_empty()) .map(|(children, bounding_box)| { - ObjectGroup::with_bounding_box(children, bounding_box).into_object() + ObjectGroup::with_bounding_box(children, bounding_box).into() }), ); @@ -106,7 +106,7 @@ impl ObjectGroup { group .children .iter_mut() - .filter_map(|child| child.get_group_mut().map(|g| g as &mut ObjectGroup)), + .filter_map(|child| child.as_group_mut().map(|g| g as &mut ObjectGroup)), ); } } @@ -116,18 +116,12 @@ impl ObjectGroup { pub fn into_children(self) -> Vec { self.children } - pub fn into_shape(self) -> Shape { - Shape::Group(self) - } - pub fn into_object(self) -> Object { - Object::with_shape(self.into_shape()) - } pub fn intersect<'a>(&'a self, world_ray: &Ray, collector: &mut IntersectionCollector<'a>) { if !self.bounding_box.is_intersected(world_ray) { return; } for child in self.children.iter() { - child.intersect_with_collector(world_ray, collector) + child.intersect(world_ray, collector) } } pub fn bounding_box(&self) -> &BoundingBox { @@ -170,50 +164,46 @@ mod tests { primitive::{matrix::Matrix, point::Point, tuple::Tuple, vector::Vector}, render::{ intersection::IntersectionCollection, - object::{group::ObjectGroup, shape::Shape, Object}, + object::{group::ObjectGroup, shape::Shape, Object, PrimitiveObject}, ray::Ray, }, }; #[test] fn intersecting_ray_with_empty_group() { - let group = ObjectGroup::empty(); - let object = Object::with_shape(group.into_shape()); + let object = Object::group_with_children(Vec::new()); let ray = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); assert!(object.intersect_to_vec(&ray).is_empty()); } #[test] fn intersecting_ray_with_nonempty_group() { - let s1 = Object::with_shape(Shape::Sphere); - let s2 = Object::sphere(Point::new(0., 0., -3.), 1.); - let s3 = Object::sphere(Point::new(5., 0., 0.), 1.); + let s1 = Object::primitive_with_shape(Shape::Sphere); + let s2 = PrimitiveObject::sphere(Point::new(0., 0., -3.), 1.).into(); + let s3 = PrimitiveObject::sphere(Point::new(5., 0., 0.), 1.).into(); - let group = ObjectGroup::new(vec![s1, s2, s3]); - let object = Object::with_shape(group.into_shape()); + let object = Object::group_with_children(vec![s1, s2, s3]); let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); let xs = IntersectionCollection::from_ray_and_obj(ray, &object); let data = xs.vec(); + let group = object.as_group().unwrap(); - match object.shape() { - Shape::Group(group) => { - assert_eq!(data.len(), 4); + assert_eq!(data.len(), 4); - assert!(std::ptr::eq(data[0].object(), &group.children[1])); - assert!(std::ptr::eq(data[1].object(), &group.children[1])); - assert!(std::ptr::eq(data[2].object(), &group.children[0])); - assert!(std::ptr::eq(data[3].object(), &group.children[0])); - } - _ => panic!("expected Shape::Group"), - } + assert!(std::ptr::eq(data[0].object(), &group.children[1])); + assert!(std::ptr::eq(data[1].object(), &group.children[1])); + assert!(std::ptr::eq(data[2].object(), &group.children[0])); + assert!(std::ptr::eq(data[3].object(), &group.children[0])); } #[test] fn intersecting_transformed_group() { - let sphere = Object::sphere(Point::new(5., 0., 0.), 1.); - let group = ObjectGroup::with_transformations(vec![sphere], Matrix::scaling_uniform(2.)); - let object = Object::with_shape(group.into_shape()); + let sphere = PrimitiveObject::sphere(Point::new(5., 0., 0.), 1.).into(); + let object = Object::from_group(ObjectGroup::with_transformations( + vec![sphere], + Matrix::scaling_uniform(2.), + )); let ray = Ray::new(Point::new(10., 0., -10.), Vector::new(0., 0., 1.)); assert_eq!(object.intersect_to_vec(&ray).len(), 2); @@ -221,12 +211,18 @@ mod tests { #[test] fn normal_on_group_child() { - let sphere = Object::with_transformation(Shape::Sphere, Matrix::translation(5., 0., 0.)); - let g2 = Object::group(vec![sphere], Matrix::scaling(1., 2., 3.)); - let g1 = Object::group(vec![g2], Matrix::rotation_y(std::f64::consts::FRAC_PI_2)); - - let sphere = &g1.get_group().unwrap().children[0] - .get_group() + let sphere = + Object::primitive_with_transformation(Shape::Sphere, Matrix::translation(5., 0., 0.)); + let g2 = + ObjectGroup::with_transformations(vec![sphere], Matrix::scaling(1., 2., 3.)).into(); + let g1: Object = ObjectGroup::with_transformations( + vec![g2], + Matrix::rotation_y(std::f64::consts::FRAC_PI_2), + ) + .into(); + + let sphere = &g1.as_group().unwrap().children[0] + .as_group() .unwrap() .children[0]; let normal = sphere.normal_vector_at(Point::new(1.7321, 1.1547, -5.5774)); diff --git a/src/render/object/plane.rs b/src/render/object/plane.rs index 1c10faf..c93cfe7 100644 --- a/src/render/object/plane.rs +++ b/src/render/object/plane.rs @@ -39,7 +39,7 @@ mod tests { #[test] fn ray_intersecting_plane_from_above() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let ray = Ray::new(Point::new(0., 1., 0.), Vector::new(0., -1., 0.)); assert_eq!(plane.intersection_times(&ray), vec![1.]); @@ -47,7 +47,7 @@ mod tests { #[test] fn ray_intersecting_plane_from_below() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let ray = Ray::new(Point::new(0., -1., 0.), Vector::new(0., 1., 0.)); assert_eq!(plane.intersection_times(&ray), vec![1.]); @@ -55,7 +55,7 @@ mod tests { #[test] fn normal_of_plane_is_const_everywhere() { - let plane = Object::with_shape(Shape::Plane); + let plane = Object::primitive_with_shape(Shape::Plane); let expected = Vector::new(0., 1., 0.); @@ -66,7 +66,7 @@ mod tests { #[test] fn normal_on_surface_of_cube() { - let cube = Object::with_shape(Shape::Cube); + let cube = Object::primitive_with_shape(Shape::Cube); let examples = vec![ (Point::new(1., 0.5, -0.8), Vector::new(1., 0., 0.)), (Point::new(-1., -0.2, 0.9), Vector::new(-1., 0., 0.)), diff --git a/src/render/object/shape.rs b/src/render/object/shape.rs index d0a32e5..dfd104e 100644 --- a/src/render/object/shape.rs +++ b/src/render/object/shape.rs @@ -8,8 +8,8 @@ use crate::{ }; use super::{ - bounding_box::BoundingBox, cone::Cone, cube::UnitCube, cylinder::Cylinder, group::ObjectGroup, - plane::PlaneXZ, smooth_triangle::SmoothTriangle, sphere::UnitSphere, + bounding_box::BoundingBox, cone::Cone, cube::UnitCube, cylinder::Cylinder, plane::PlaneXZ, + smooth_triangle::SmoothTriangle, sphere::UnitSphere, }; #[derive(Clone, Debug)] @@ -26,7 +26,6 @@ pub enum Shape { Cone(Cone), Triangle(Triangle), SmoothTriangle(SmoothTriangle), - Group(ObjectGroup), } impl Shape { @@ -43,9 +42,6 @@ impl Shape { Shape::Cone(cone) => cone.local_normal_at(object_point), Shape::Triangle(triangle) => triangle.normal(), Shape::SmoothTriangle(triangle) => triangle.local_normal_at(i), - Shape::Group(_) => { - unimplemented!("Internal bug: this function should not be called on a group") - } } } pub fn local_intersect(&self, object_ray: &Ray, collector: &mut IntersectionCollector) { @@ -57,9 +53,6 @@ impl Shape { Shape::Cone(cone) => cone.local_intersect(object_ray, collector), Shape::Triangle(triangle) => triangle.local_intersect(object_ray, collector), Shape::SmoothTriangle(triangle) => triangle.local_intersect(object_ray, collector), - Shape::Group(_) => { - unimplemented!("Internal bug: this function should not be called on a group") - } } } pub fn bounding_box(&self) -> BoundingBox { @@ -71,7 +64,6 @@ impl Shape { Shape::Cone(cone) => cone.bounding_box(), Shape::Triangle(triangle) => triangle.bounding_box(), Shape::SmoothTriangle(triangle) => triangle.bounding_box(), - Shape::Group(group) => group.bounding_box().clone(), } } pub fn cylinder(height: f64, closed: bool) -> Self { @@ -112,11 +104,4 @@ impl Shape { ) -> Self { Shape::SmoothTriangle(SmoothTriangle::new(p1, p2, p3, n1, n2, n3)) } - - pub fn as_group(&self) -> Option<&ObjectGroup> { - match self { - Shape::Group(group) => Some(group), - _ => None, - } - } } diff --git a/src/render/object/smooth_triangle.rs b/src/render/object/smooth_triangle.rs index b78a4f1..cea2472 100644 --- a/src/render/object/smooth_triangle.rs +++ b/src/render/object/smooth_triangle.rs @@ -125,7 +125,7 @@ mod tests { let n1 = Vector::new(0., 1., 0.); let n2 = Vector::new(-1., 0., 0.); let n3 = Vector::new(1., 0., 0.); - Object::with_shape(Shape::smooth_triangle(p1, p2, p3, n1, n2, n3)) + Object::primitive_with_shape(Shape::smooth_triangle(p1, p2, p3, n1, n2, n3)) } #[test] diff --git a/src/render/object/sphere.rs b/src/render/object/sphere.rs index 3361e35..a5cc642 100644 --- a/src/render/object/sphere.rs +++ b/src/render/object/sphere.rs @@ -48,7 +48,7 @@ mod tests { vector::Vector, }, render::{ - object::{shape::Shape, Object}, + object::{shape::Shape, Object, PrimitiveObject}, ray::Ray, }, }; @@ -56,7 +56,7 @@ mod tests { #[test] fn transformed_sphere() { // < -2; 6 > - let obj = Object::sphere(Point::new(2., 2., 2.), 4.); + let obj = Object::from(PrimitiveObject::sphere(Point::new(2., 2., 2.), 4.)); let direction = Vector::new(0., 0., 1.); assert!(obj.is_intersected_by_ray(&Ray::new(Point::new(2., 2., 2.), direction))); @@ -75,20 +75,21 @@ mod tests { #[test] fn intersect_scaled_sphere() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_transformation(Shape::Sphere, Matrix::scaling_uniform(2.)); + let obj = Object::primitive_with_transformation(Shape::Sphere, Matrix::scaling_uniform(2.)); assert_eq!(obj.intersection_times(&ray), vec![3., 7.]); } #[test] fn intersect_translated_sphere() { let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); - let obj = Object::with_transformation(Shape::Sphere, Matrix::translation(5., 0., 0.)); + let obj = + Object::primitive_with_transformation(Shape::Sphere, Matrix::translation(5., 0., 0.)); assert_eq!(obj.intersection_times(&ray), vec![]); } #[test] fn normal_on_sphere_x_axis() { - let sphere_obj = Object::with_shape(Shape::Sphere); + let sphere_obj = Object::primitive_with_shape(Shape::Sphere); assert_eq!( sphere_obj.normal_vector_at(Point::new(1., 0., 0.,)), @@ -97,7 +98,7 @@ mod tests { } #[test] fn normal_on_sphere_y_axis() { - let sphere_obj = Object::with_shape(Shape::Sphere); + let sphere_obj = Object::primitive_with_shape(Shape::Sphere); assert_eq!( sphere_obj.normal_vector_at(Point::new(0., 1., 0.,)), @@ -106,7 +107,7 @@ mod tests { } #[test] fn normal_on_sphere_z_axis() { - let sphere_obj = Object::with_shape(Shape::Sphere); + let sphere_obj = Object::primitive_with_shape(Shape::Sphere); assert_eq!( sphere_obj.normal_vector_at(Point::new(0., 0., 1.,)), @@ -115,7 +116,7 @@ mod tests { } #[test] fn normal_on_sphere_at_noaxial_point() { - let sphere_obj = Object::with_shape(Shape::Sphere); + let sphere_obj = Object::primitive_with_shape(Shape::Sphere); let frac_sqrt_3_3 = 3_f64.sqrt() / 3.; assert_eq!( @@ -125,7 +126,7 @@ mod tests { } #[test] fn compute_normal_on_translated_sphere() { - let mut sphere_obj = Object::with_shape(Shape::Sphere); + let mut sphere_obj = Object::primitive_with_shape(Shape::Sphere); sphere_obj.transform(&Matrix::translation(0., 1., 0.)); assert_eq!( sphere_obj.normal_vector_at(Point::new(0., 1. + FRAC_1_SQRT_2, -FRAC_1_SQRT_2)), @@ -134,7 +135,7 @@ mod tests { } #[test] fn compute_normal_on_transformed_sphere() { - let mut sphere_obj = Object::with_shape(Shape::Sphere); + let mut sphere_obj = Object::primitive_with_shape(Shape::Sphere); sphere_obj.transform(&(Matrix::scaling(1., 0.5, 1.) * Matrix::rotation_z(PI / 5.))); assert_eq!( sphere_obj.normal_vector_at(Point::new(0., FRAC_1_SQRT_2, -FRAC_1_SQRT_2)), diff --git a/src/render/object/triangle.rs b/src/render/object/triangle.rs index 4114b77..714e0ca 100644 --- a/src/render/object/triangle.rs +++ b/src/render/object/triangle.rs @@ -114,7 +114,7 @@ mod tests { } fn get_triangle() -> Object { - Object::with_shape(Shape::triangle( + Object::primitive_with_shape(Shape::triangle( Point::new(0., 1., 0.), Point::new(-1., 0., 0.), Point::new(1., 0., 0.), @@ -124,7 +124,7 @@ mod tests { #[test] fn finding_normal_on_triangle() { let t = get_triangle(); - let t_shape = match t.shape() { + let t_shape = match t.as_primitive().unwrap().shape() { Shape::Triangle(ref triangle) => triangle.clone(), _ => unreachable!(), }; diff --git a/src/render/pattern.rs b/src/render/pattern.rs index 65e8c23..0f76c1d 100644 --- a/src/render/pattern.rs +++ b/src/render/pattern.rs @@ -174,7 +174,8 @@ mod tests { #[test] fn stripes_with_object_transformation() { - let sphere = Object::with_transformation(Shape::Sphere, Matrix::scaling(2., 2., 2.)); + let sphere = + Object::primitive_with_transformation(Shape::Sphere, Matrix::scaling(2., 2., 2.)); let stripe = Pattern::stripe(Color::white(), Color::black(), None); assert_eq!( @@ -185,7 +186,7 @@ mod tests { #[test] fn stripes_with_pattern_transformation() { - let sphere = Object::with_shape(Shape::Sphere); + let sphere = Object::primitive_with_shape(Shape::Sphere); let stripe = Pattern::stripe( Color::white(), Color::black(), @@ -200,7 +201,8 @@ mod tests { #[test] fn stripes_with_object_and_pattern_transformation() { - let sphere = Object::with_transformation(Shape::Sphere, Matrix::scaling_uniform(2.)); + let sphere = + Object::primitive_with_transformation(Shape::Sphere, Matrix::scaling_uniform(2.)); let stripe = Pattern::stripe( Color::white(), Color::black(), diff --git a/src/render/world.rs b/src/render/world.rs index 0d71b81..b41e814 100644 --- a/src/render/world.rs +++ b/src/render/world.rs @@ -3,7 +3,6 @@ use crate::{ primitive::{matrix::Matrix, point::Point, tuple::Tuple}, }; -use super::intersection::{IntersecComputations, IntersectionCollection}; use super::{ camera::Camera, canvas::Canvas, @@ -13,6 +12,10 @@ use super::{ object::Object, ray::Ray, }; +use super::{ + intersection::{IntersecComputations, IntersectionCollection}, + object::PrimitiveObject, +}; use super::{object::shape::Shape, pattern::Pattern}; pub struct World { @@ -54,7 +57,7 @@ impl World { ) -> Self { let now = std::time::Instant::now(); for obj in &mut objects { - if let Some(group) = obj.get_group_mut() { + if let Some(group) = obj.as_group_mut() { group.partition(); } } @@ -180,7 +183,7 @@ impl World { if inter.time().approx_eq(&distance) || inter.time() > distance { break; } - intensity += 1. - inter.object().material().transparency; + intensity += 1. - inter.object().material_unwrapped().transparency; if intensity >= 1. { return 1.; } @@ -190,19 +193,27 @@ impl World { fn reflected_color(&self, hit_comps: &IntersecComputations, depth: usize) -> Color { if depth >= self.max_recursive_depth - || hit_comps.object().material().reflectivity.approx_eq(&0.) + || hit_comps + .object() + .material_unwrapped() + .reflectivity + .approx_eq(&0.) { return Color::black(); } let reflected_ray = Ray::new(hit_comps.over_point(), hit_comps.reflect_v()); let color = self.color_at_depth(reflected_ray, depth + 1); - color * hit_comps.object().material().reflectivity + color * hit_comps.object().material_unwrapped().reflectivity } fn refracted_color(&self, hit_comps: &IntersecComputations, depth: usize) -> Color { if depth >= self.max_recursive_depth - || hit_comps.object().material().transparency.approx_eq(&0.) + || hit_comps + .object() + .material_unwrapped() + .transparency + .approx_eq(&0.) { return Color::black(); } @@ -222,7 +233,7 @@ impl World { let refracted_ray = Ray::new(hit_comps.under_point(), direction); let color = self.color_at_depth(refracted_ray, depth + 1); - color * hit_comps.object().material().transparency + color * hit_comps.object().material_unwrapped().transparency } pub fn shade_hit(&self, hit_comps: IntersecComputations, depth: usize) -> Color { @@ -240,7 +251,7 @@ impl World { let reflected = self.reflected_color(&hit_comps, depth); let refracted = self.refracted_color(&hit_comps, depth); - let material = hit_comps.object().material(); + let material = hit_comps.object().material_unwrapped(); let use_schlick = material.reflectivity > 0. && material.transparency > 0. @@ -261,7 +272,7 @@ impl World { // Default testing world with bool shadows impl World { pub fn default_testing() -> Self { - let sphere1 = Object::with_shape_material( + let sphere1 = Object::primitive( Shape::Sphere, Material { pattern: Pattern::Const(Color::new(0.8, 1.0, 0.6)), @@ -270,8 +281,11 @@ impl World { specular: 0.2, ..Default::default() }, + Matrix::identity(), ); - let sphere2 = Object::with_transformation(Shape::Sphere, Matrix::scaling(0.5, 0.5, 0.5)); + let sphere2 = + PrimitiveObject::with_transformation(Shape::Sphere, Matrix::scaling(0.5, 0.5, 0.5)) + .into(); let objects = vec![sphere1, sphere2]; let lights = vec![PointLightSource::new( @@ -376,8 +390,8 @@ mod tests { Color::white(), )); - world.add_obj(Object::with_shape(Shape::Sphere)); - world.add_obj(Object::with_transformation( + world.add_obj(Object::primitive_with_shape(Shape::Sphere)); + world.add_obj(Object::primitive_with_transformation( Shape::Sphere, Matrix::translation(0., 0., 10.), )); @@ -394,7 +408,7 @@ mod tests { let mut w = World::default_testing(); let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); let shape = &mut w.objects[1]; - shape.material_mut().ambient = 1.; + shape.material_mut().unwrap().ambient = 1.; let i = Intersection::new(1., &w.objects[1]); let comps = i.computations(&r); @@ -405,7 +419,7 @@ mod tests { #[test] fn shade_hit_with_reflective_material() { let mut w = World::default_testing(); - let plane = Object::new( + let plane = Object::primitive( Shape::Plane, Material { reflectivity: 0.5, @@ -436,7 +450,7 @@ mod tests { Color::white(), )); - let lower = Object::new( + let lower = Object::primitive( Shape::Plane, Material { reflectivity: 1., @@ -444,7 +458,7 @@ mod tests { }, Matrix::translation(0., -1., 0.), ); - let upper = Object::new( + let upper = Object::primitive( Shape::Plane, Material { reflectivity: 1., @@ -463,7 +477,7 @@ mod tests { #[test] fn reflected_color_at_max_recursive_depth() { let mut world = World::default_testing(); - let plane = Object::new( + let plane = Object::primitive( Shape::Plane, Material { reflectivity: 0.5, @@ -501,8 +515,8 @@ mod tests { fn refracted_color_at_max_recursive_depth() { let mut world = World::default_testing(); let shape = &mut world.objects[0]; - shape.material_mut().transparency = 1.; - shape.material_mut().refractive_index = 1.5; + shape.material_mut().unwrap().transparency = 1.; + shape.material_mut().unwrap().refractive_index = 1.5; let shape = &world.objects[0]; let ray = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); @@ -519,8 +533,8 @@ mod tests { fn refracted_color_under_total_internal_reflection() { let mut world = World::default_testing(); let shape = &mut world.objects[0]; - shape.material_mut().transparency = 1.; - shape.material_mut().refractive_index = 1.5; + shape.material_mut().unwrap().transparency = 1.; + shape.material_mut().unwrap().refractive_index = 1.5; let shape = &world.objects[0]; let ray = Ray::new(Point::new(0., 0., SQRT_2 / 2.), Vector::new(0., 1., 0.)); @@ -537,12 +551,12 @@ mod tests { let mut world = World::default_testing(); let a = &mut world.objects[0]; - a.material_mut().ambient = 1.; - a.material_mut().pattern = Pattern::test_pattern(None); + a.material_mut().unwrap().ambient = 1.; + a.material_mut().unwrap().pattern = Pattern::test_pattern(None); let b = &mut world.objects[1]; - b.material_mut().transparency = 1.; - b.material_mut().refractive_index = 1.5; + b.material_mut().unwrap().transparency = 1.; + b.material_mut().unwrap().refractive_index = 1.5; let ray = Ray::new(Point::new(0., 0., 0.1), Vector::new(0., 1., 0.)); @@ -561,7 +575,7 @@ mod tests { #[test] fn shading_transparent_material() { let mut world = World::default_testing(); - let floor = Object::new( + let floor = Object::primitive( Shape::Plane, Material { transparency: 0.5, @@ -570,7 +584,7 @@ mod tests { }, Matrix::translation(0., -1., 0.), ); - let ball = Object::new( + let ball = Object::primitive( Shape::Sphere, Material { pattern: Pattern::Const(Color::red()), @@ -598,7 +612,7 @@ mod tests { #[test] fn shading_reflective_transparent_material() { let mut world = World::default_testing(); - let floor = Object::new( + let floor = Object::primitive( Shape::Plane, Material { transparency: 0.5, @@ -608,7 +622,7 @@ mod tests { }, Matrix::translation(0., -1., 0.), ); - let ball = Object::new( + let ball = Object::primitive( Shape::Sphere, Material { pattern: Pattern::Const(Color::red()),