From 05a16bbaf4bc782df0842aa8c3b4f9e3e330630a Mon Sep 17 00:00:00 2001 From: Karol Zwolak Date: Sun, 10 Mar 2024 15:06:50 +0100 Subject: [PATCH] refactor: object is now a enum and its either primitive or a group --- src/playing_around/cubes.rs | 34 ++--- src/playing_around/cylinders.rs | 12 +- src/playing_around/making_patterns.rs | 9 +- src/playing_around/making_scene.rs | 69 +++++---- src/playing_around/reflections.rs | 18 ++- src/playing_around/refractions.rs | 10 +- src/playing_around/shadows.rs | 36 +++-- src/render/light.rs | 27 ++-- src/render/object.rs | 201 +++++++++++++++++++------- src/render/object/bounding_box.rs | 6 +- src/render/object/group.rs | 25 +++- src/render/object/shape.rs | 15 -- src/render/world.rs | 16 +- 13 files changed, 301 insertions(+), 177 deletions(-) diff --git a/src/playing_around/cubes.rs b/src/playing_around/cubes.rs index 0a2fa34..0327a85 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 dc58f8d..a29f57f 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(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/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..9e8fec6 100644 --- a/src/playing_around/making_scene.rs +++ b/src/playing_around/making_scene.rs @@ -8,54 +8,67 @@ 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, PrimitiveObject}, + 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 mut right_sphere = Object::primitive( Shape::Sphere, - middle_sphere.material().clone(), + Material { + pattern: Pattern::Const(Color::new(0.5, 1., 0.1)), + ..middle_sphere.material().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 mut 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(), Matrix::scaling(10., 0.01, 10.) @@ -65,7 +78,7 @@ pub fn scene_walls() -> Vec { .transformed(), ); - let right_wall = Object::new( + let right_wall = Object::primitive( Shape::Sphere, floor.material().clone(), Matrix::scaling(10., 0.01, 10.) diff --git a/src/playing_around/reflections.rs b/src/playing_around/reflections.rs index 6f95245..37c7edb 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 mut 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/light.rs b/src/render/light.rs index 9d8bfbb..6dabd6e 100644 --- a/src/render/light.rs +++ b/src/render/light.rs @@ -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/object.rs b/src/render/object.rs index e81718c..e11007c 100644 --- a/src/render/object.rs +++ b/src/render/object.rs @@ -25,14 +25,138 @@ use super::{ }; #[derive(Clone, Debug)] -pub struct Object { +pub enum Object { + Primitive(PrimitiveObject), + Group(ObjectGroup), +} + +impl From for Object { + fn from(obj: PrimitiveObject) -> Self { + Self::Primitive(obj) + } +} + +impl From for Object { + fn from(group: ObjectGroup) -> Self { + Self::Group(group) + } +} + +impl Object { + pub fn group(children: Vec) -> Self { + Self::Group(ObjectGroup::new(children)) + } + + pub fn primitive(shape: Shape, material: Material, transformation: Matrix) -> Self { + Self::Primitive(PrimitiveObject::new(shape, material, 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(group) => todo!(), + } + } + + pub fn intersect_with_collector<'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 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() + } + + pub fn is_intersected_by_ray(&self, ray: &Ray) -> bool { + !self.intersect_to_vec(ray).is_empty() + } + + pub fn material(&self) -> &Material { + match self { + Self::Primitive(obj) => obj.material(), + Self::Group(group) => &Material::default(), + } + } + + pub fn transformation(&self) -> Matrix { + match self { + Self::Primitive(obj) => obj.transformation, + Self::Group(group) => Matrix::identity(), + } + } + + pub fn transformation_inverse(&self) -> Matrix { + match self { + Self::Primitive(obj) => obj.transformation_inverse(), + Self::Group(group) => Matrix::identity(), + } + } + + pub fn bounding_box(&self) -> &BoundingBox { + match self { + Self::Primitive(obj) => obj.bounding_box(), + Self::Group(group) => group.bounding_box(), + } + } + 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 Object { +impl PrimitiveObject { pub fn new(shape: Shape, material: Material, transformation: Matrix) -> Self { Self { shape, @@ -44,15 +168,8 @@ impl Object { } } - pub fn group(children: Vec, transformation: Matrix) -> Self { - Self::with_shape(Shape::Group(ObjectGroup::with_transformations( - children, - transformation, - ))) - } - - pub fn bounding_box(&self) -> BoundingBox { - self.shape.bounding_box().transformed(self.transformation) + pub fn bounding_box(&self) -> &BoundingBox { + &self.shape.bounding_box().transformed(self.transformation) } pub fn with_shape(shape: Shape) -> Self { @@ -80,13 +197,8 @@ impl Object { self.transformation_inverse } pub fn apply_transformation(&mut self, matrix: Matrix) { - match &mut self.shape { - Shape::Group(group) => group.apply_transformation(matrix), - _ => { - self.transformation = matrix * self.transformation; - self.transformation_inverse = self.transformation.inverse().unwrap(); - } - } + self.transformation = matrix * self.transformation; + self.transformation_inverse = self.transformation.inverse().unwrap(); } pub fn normal_vector_at(&self, world_point: Point) -> Vector { self.normal_vector_at_with_intersection(world_point, None) @@ -109,55 +221,32 @@ 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(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(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 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 transformed(self) -> Self { + Self } - pub fn get_group_mut(&mut self) -> Option<&mut ObjectGroup> { - match &mut self.shape { - Shape::Group(group) => Some(group), - _ => None, - } + fn transform_borrowed(&mut self, matrix: &Matrix) { + self.apply_transformation(*matrix); } } diff --git a/src/render/object/bounding_box.rs b/src/render/object/bounding_box.rs index 7f10817..cbae70c 100644 --- a/src/render/object/bounding_box.rs +++ b/src/render/object/bounding_box.rs @@ -43,7 +43,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; } @@ -169,7 +169,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, @@ -248,7 +248,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/group.rs b/src/render/object/group.rs index 1e6182c..acb63fb 100644 --- a/src/render/object/group.rs +++ b/src/render/object/group.rs @@ -1,6 +1,6 @@ use crate::{ approx_eq::ApproxEq, - primitive::matrix::Matrix, + primitive::matrix::{Matrix, Transform}, render::{intersection::IntersectionCollector, material::Material, ray::Ray}, }; @@ -49,11 +49,11 @@ impl ObjectGroup { } self.bounding_box.transform(matrix); } - pub fn set_material(&mut self, material: Material) { - for child in self.children.iter_mut() { - child.set_material(material.clone()); - } - } + // pub fn set_material(&mut self, material: Material) { + // for child in self.children.iter_mut() { + // child.set_material(material.clone()); + // } + // } pub fn add_child(&mut self, child: Object) { self.bounding_box.add_bounding_box(child.bounding_box()); self.children.push(child); @@ -147,6 +147,19 @@ impl ObjectGroup { } } +impl Transform for ObjectGroup { + fn transformed(self) -> Self { + todo!() + } + + fn transform_borrowed(&mut self, matrix: &Matrix) { + for child in self.children.iter_mut() { + child.transform_borrowed(matrix); + } + self.bounding_box.transform(*matrix); + } +} + impl Default for ObjectGroup { fn default() -> Self { Self::empty() diff --git a/src/render/object/shape.rs b/src/render/object/shape.rs index d0a32e5..bd4ef7a 100644 --- a/src/render/object/shape.rs +++ b/src/render/object/shape.rs @@ -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/world.rs b/src/render/world.rs index 0d71b81..de87a36 100644 --- a/src/render/world.rs +++ b/src/render/world.rs @@ -405,7 +405,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 +436,7 @@ mod tests { Color::white(), )); - let lower = Object::new( + let lower = Object::primitive( Shape::Plane, Material { reflectivity: 1., @@ -444,7 +444,7 @@ mod tests { }, Matrix::translation(0., -1., 0.), ); - let upper = Object::new( + let upper = Object::primitive( Shape::Plane, Material { reflectivity: 1., @@ -463,7 +463,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, @@ -561,7 +561,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 +570,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 +598,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 +608,7 @@ mod tests { }, Matrix::translation(0., -1., 0.), ); - let ball = Object::new( + let ball = Object::primitive( Shape::Sphere, Material { pattern: Pattern::Const(Color::red()),