diff --git a/Makefile b/Makefile index b9bf227..7cc2ba4 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ SOURCES := main.c models.c basic_math.c vector_math.c colors.c canvas.c SOURCES += mx_attributes.c mx_operations.c mx_rotations.c mx_transformations.c SOURCES += mx_utils.c rays.c intersections.c spheres.c SOURCES += lights.c materials.c guards.c world.c view_transform.c camera.c -SOURCES += render.c controls.c shapes.c shadows.c planes.c +SOURCES += render.c controls.c shapes.c shadows.c planes.c textures.c OBJS := $(addprefix $(OBJ_DIR)/, $(SOURCES:.c=.o)) diff --git a/include/lights.h b/include/lights.h index aa9b525..418e854 100644 --- a/include/lights.h +++ b/include/lights.h @@ -6,7 +6,7 @@ /* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/11 11:06:23 by yde-goes #+# #+# */ -/* Updated: 2023/04/25 14:57:57 by yde-goes ### ########.fr */ +/* Updated: 2023/05/11 17:51:28 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ diff --git a/include/materials.h b/include/materials.h index 0b7eb84..4755111 100644 --- a/include/materials.h +++ b/include/materials.h @@ -6,7 +6,7 @@ /* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/11 11:05:52 by yde-goes #+# #+# */ -/* Updated: 2023/04/20 10:58:42 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 11:28:00 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -34,6 +34,13 @@ * by the parameter shininess. * @param shininess Represents the shininess. The higher the shininess, the * smaller and tigher the specular highlight. + * @param reflective Represents the material's surface reflection value. + * @param transparency Represents the material's transparency. If transparency + * is zero, then the surface is opaque. + * @param refractive_index Represents the degree to which light will bend when + * entering or exiting the material, compared to other + * materials. If variable value is 1.0, it means that + * the object is empty, vacuum-filled shells. */ typedef struct s_material { @@ -42,6 +49,9 @@ typedef struct s_material float diffuse; float specular; float shininess; + float reflective; + float transparency; + float refractive_index; } t_material; /* ************************************************************************** */ @@ -80,7 +90,7 @@ typedef struct s_exposure * t_material with the following default values: color = {1, 1, 1}, * ambient = 0.1, diffuse = 0.9, specular = 0.9 and shininess = 200. * - * @return (t_material) Returns a default instance of the new material. + * @return Returns a default instance of the new material. */ t_material material(void); @@ -99,7 +109,7 @@ t_material material(void); * @param point A struct of type t_tuple of the point being illuminated. * @param sight A struct of type t_sight with the values of eye and normal * vectors obtained from the Phong Reflection Model algorithm. - * @return (t_color) The function returns the final shading of that point. + * @return The function returns the final shading of that point. */ t_color lighting(t_material m, t_light light, t_tuple point, t_sight sight); diff --git a/include/shapes.h b/include/shapes.h index 7700ca6..587866a 100644 --- a/include/shapes.h +++ b/include/shapes.h @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* shapes.h :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/03/28 18:05:41 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 14:13:17 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 17:33:27 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -266,4 +266,10 @@ t_tuple normal_at_plane(t_shape *shape, t_tuple world_point); */ t_bool intersect_plane(t_hit **xs, t_shape *shape, t_ray ray); +/* ************************************************************************** */ +/* TEXTURES.C */ +/* ************************************************************************** */ + +void glassy_shape(t_shape *shape); + #endif diff --git a/include/world.h b/include/world.h index c15618f..e01c211 100644 --- a/include/world.h +++ b/include/world.h @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* world.h :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/13 14:17:44 by mdias-ma #+# #+# */ -/* Updated: 2023/05/08 16:48:57 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 14:11:54 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -16,6 +16,8 @@ # include "lights.h" # include "shapes.h" +# define MAX_RECURSION 5 + typedef struct s_world { int object_count; @@ -32,6 +34,7 @@ typedef struct s_comps t_sight sight; t_bool inside; t_tuple over_point; + t_tuple reflectv; } t_comps; /* ************************************************************************** */ @@ -78,9 +81,12 @@ t_comps prepare_computations(t_hit *intersection, t_ray ray); * * @param world The world in which the intersection occurred. * @param comps The precomputed information about the intersection. + * @param remaining Specifies the maximum recursive depth for the function if it + * needs to handle infinite recursion caused by two objects that + * mutually reflect rays between themselves. * @return The color of the intersection point. */ -t_color shade_hit(t_world world, t_comps comps); +t_color shade_hit(t_world world, t_comps comps, size_t remaining); /** * @brief Computes the color at the intersection of a given ray with a world. @@ -96,10 +102,30 @@ t_color shade_hit(t_world world, t_comps comps); * * @param world The world in which the intersection occurred. * @param ray The ray that intersected with the shapes in the world. + * @param remaining Specifies the maximum recursive depth for the function if it + * needs to handle infinite recursion caused by two objects that + * mutually reflect rays between themselves. + * reflect rays between themselves * @return The color of the intersection point, or black if there is * no such intersection. */ -t_color color_at(t_world world, t_ray ray); +t_color color_at(t_world world, t_ray ray, size_t remaining); + +/** + * @brief This function calcutes the reflected ray and its color from any + * reflective material specified in variable of type t_world. + * + * @param world A pointer to a structure of type `t_world` representing the + * world containing objects, rays and light sources. + * @param comps The precomputed information about the world. + * @param remaining Specifies the maximum recursive depth for the function if it + * needs to handle infinite recursion caused by two objects that + * mutually reflect rays between themselves. + * @return If remaining is zero or if material reflection is nonexistent, the + * function returns a color equivalent to black. Otherwise, it returns + * the reflected color from a reflective material. + */ +t_color reflected_color(t_world world, t_comps comps, size_t remaining); /* ************************************************************************** */ /* SHADOWS.C */ diff --git a/src/canvas/render.c b/src/canvas/render.c index eadf1fd..c3b1ea6 100644 --- a/src/canvas/render.c +++ b/src/canvas/render.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* render.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/21 12:16:18 by mdias-ma #+# #+# */ -/* Updated: 2023/04/23 12:51:00 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 10:36:31 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -28,7 +28,7 @@ t_bool render_scene(t_canvas *canvas, t_world *world, t_camera *camera) while (x < camera->hsize - 1) { ray = ray_for_pixel(camera, x, y); - color = color_at(*world, ray); + color = color_at(*world, ray, MAX_RECURSION); write_pixel(canvas, x, y, rgb(color)); x++; } diff --git a/src/materials/materials.c b/src/materials/materials.c index 0dc729d..f084644 100644 --- a/src/materials/materials.c +++ b/src/materials/materials.c @@ -6,7 +6,7 @@ /* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/11 11:11:46 by yde-goes #+# #+# */ -/* Updated: 2023/04/25 17:30:42 by yde-goes ### ########.fr */ +/* Updated: 2023/05/12 14:52:58 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -23,7 +23,10 @@ t_material material(void) .ambient = 0.1, .diffuse = 0.9, .specular = 0.9, - .shininess = 200.0 + .shininess = 200.0, + .reflective = 0.0, + .transparency = 0.0, + .refractive_index = 1.0 }); } diff --git a/src/shapes/spheres.c b/src/shapes/spheres.c index e430ff1..fff4d4d 100644 --- a/src/shapes/spheres.c +++ b/src/shapes/spheres.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* spheres.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/05 18:30:21 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 10:34:13 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 11:33:44 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ diff --git a/src/shapes/textures.c b/src/shapes/textures.c new file mode 100644 index 0000000..ccfff0c --- /dev/null +++ b/src/shapes/textures.c @@ -0,0 +1,19 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* textures.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: yde-goes +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2023/05/12 11:19:51 by yde-goes #+# #+# */ +/* Updated: 2023/05/12 11:27:27 by yde-goes ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "shapes.h" + +void glassy_shape(t_shape *shape) +{ + shape->material.transparency = 1.0; + shape->material.refractive_index = 1.5; +} diff --git a/src/world/shadows.c b/src/world/shadows.c index e1379ad..e9a79ed 100644 --- a/src/world/shadows.c +++ b/src/world/shadows.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* shadows.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/19 16:22:34 by yde-goes #+# #+# */ -/* Updated: 2023/05/09 14:13:16 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 10:36:50 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ diff --git a/src/world/world.c b/src/world/world.c index fc591f9..3edd187 100644 --- a/src/world/world.c +++ b/src/world/world.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* world.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/13 15:29:04 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 14:13:14 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 10:50:09 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -46,23 +46,24 @@ t_comps prepare_computations(t_hit *intersection, t_ray ray) return (comps); } comps.over_point = add(comps.point, multiply(comps.sight.normalv, EPSILON)); + comps.reflectv = reflect(ray.direction, comps.sight.normalv); comps.inside = FALSE; return (comps); } -t_color shade_hit(t_world world, t_comps comps) +t_color shade_hit(t_world world, t_comps comps, size_t remaining) { + t_color surface; + t_color reflected; + world.lights->in_shadow = is_shadowed(&world, comps.over_point); - return ( - lighting( - comps.object->material, - world.lights[0], - comps.point, - comps.sight - )); + surface = lighting(comps.object->material, + world.lights[0], comps.point, comps.sight); + reflected = reflected_color(world, comps, remaining); + return (add_color(surface, reflected)); } -t_color color_at(t_world world, t_ray ray) +t_color color_at(t_world world, t_ray ray, size_t remaining) { t_hit *x; t_comps comps; @@ -73,6 +74,18 @@ t_color color_at(t_world world, t_ray ray) if (x == NULL) return (new_color(0, 0, 0)); comps = prepare_computations(x, ray); - color = shade_hit(world, comps); + color = shade_hit(world, comps, remaining); return (color); } + +t_color reflected_color(t_world world, t_comps comps, size_t remaining) +{ + t_ray reflect_ray; + t_color color; + + if (!remaining || !comps.object->material.reflective) + return (new_color(0, 0, 0)); + reflect_ray = new_ray(comps.over_point, comps.reflectv); + color = color_at(world, reflect_ray, remaining - 1); + return (multiply_color(color, comps.object->material.reflective)); +} diff --git a/tests/Makefile b/tests/Makefile index a39f13a..6d6e787 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -24,7 +24,7 @@ SOURCES := models.c basic_math.c vector_math.c colors.c SOURCES += mx_attributes.c mx_operations.c mx_rotations.c mx_transformations.c SOURCES += mx_utils.c rays.c intersections.c spheres.c SOURCES += lights.c materials.c guards.c world.c view_transform.c camera.c -SOURCES += canvas.c render.c controls.c shadows.c shapes.c planes.c +SOURCES += canvas.c render.c controls.c shadows.c shapes.c planes.c textures.c UTILS := utils.c OBJS := $(addprefix $(OBJ_DIR)/, $(SOURCES:.c=.o)) diff --git a/tests/test_intersections.c b/tests/test_intersections.c index d6bf521..1a0ff7a 100644 --- a/tests/test_intersections.c +++ b/tests/test_intersections.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* test_intersections.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/06 09:28:46 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 14:13:18 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 14:01:37 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -58,7 +58,7 @@ Test(intersections, aggregating_intersections) } /* - * This test checks if the function "visible_hit" correctly returns the first + * This test checks if the function hit() correctly returns the first * visible intersection from a list of intersections when all intersections * have positive "t" values. The test creates two spheres and two intersections, * inserts them into the list, and checks if the function returns the expected @@ -83,7 +83,7 @@ Test(intersections, hit_when_all_intersections_have_positive_t) } /* - * This test checks if the function "visible_hit" correctly returns NULL when + * This test checks if the function hit() correctly returns NULL when * all intersections in the list have negative "t" values. The test creates a * sphere and two intersections with negative "t" values, inserts them into the * list, and checks if the function returns NULL as expected. @@ -105,7 +105,7 @@ Test(intersections, hit_when_all_intersections_have_negative_t) } /* - * This test checks if the function "visible_hit" correctly returns the lowest + * This test checks if the function hit() correctly returns the lowest * non-negative intersection from a list of intersections. The test creates a * sphere and four intersections with different "t" values, inserts them into * the list, and checks if the function returns the expected intersection. @@ -147,3 +147,92 @@ Test(intersections, hit_should_offset_point) //comps.point.z > comps.over_point.z cr_assert(gt(flt, comps.point.z, comps.over_point.z)); } + +/* + * Precomputes the reflection vector. This test creates a plane and position ray + * above it, slanting downward at a 45ยบ angle. + */ +Test(intersections, precomputing_the_reflection_vector) +{ + t_shape shape; + t_ray r; + t_hit *i; + t_comps comps; + float coord; + t_tuple expected; + + coord = sqrtf(2)/2.0; + expected = vector(0, coord, coord); + shape = new_plane(); + r = new_ray(point(0, 1, -1), vector(0, -coord, coord)); + i = intersection(sqrtf(2), &shape); + comps = prepare_computations(i, r); + + cr_assert_float_eq(comps.reflectv.x, expected.x, EPSILON); + cr_assert_float_eq(comps.reflectv.y, expected.y, EPSILON); + cr_assert_float_eq(comps.reflectv.z, expected.z, EPSILON); +} + +/** + * Finding n1 and n2 at various intersections. This test shows that + * prepare_computations() determines n1 and n2 correctly at six different + * points of intersection. Refer to page 151 on TRTC. + */ +/* Test(intesections, finding_n1_n2_at_intersections) +{ + t_shape a; + t_shape b; + t_shape c; + t_ray r; + t_hit *xs; + t_hit *i; + t_comps comps; + + a = new_sphere(); + glassy_shape(&a); + a.transform = scaling(2, 2, 2); + a.material.refractive_index = 1.5; + + b = new_sphere(); + glassy_shape(&b); + b.transform = translation(0, 0, -0.25); + b.material.refractive_index = 2.5; + + c = new_sphere(); + glassy_shape(&c); + c.transform = scaling(0, 0, 0.25); + c.material.refractive_index = 2.5; + + r = new_ray(point(0, 0, -4), vector(0, 0, 1)); + + insert_intersection(&xs, intersection(2.00, &a)); + insert_intersection(&xs, intersection(2.75, &b)); + insert_intersection(&xs, intersection(3.25, &c)); + insert_intersection(&xs, intersection(4.75, &b)); + insert_intersection(&xs, intersection(5.25, &c)); + insert_intersection(&xs, intersection(6.00, &a)); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 1.0, EPSILON); + cr_assert_float_eq(comps.n2, 1.5, EPSILON); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 1.5, EPSILON); + cr_assert_float_eq(comps.n2, 2.0, EPSILON); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 2.0, EPSILON); + cr_assert_float_eq(comps.n2, 2.5, EPSILON); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 2.5, EPSILON); + cr_assert_float_eq(comps.n2, 2.5, EPSILON); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 2.5, EPSILON); + cr_assert_float_eq(comps.n2, 1.5, EPSILON); + + comps = prepare_computations(xs, r, xs); + cr_assert_float_eq(comps.n1, 1.5, EPSILON); + cr_assert_float_eq(comps.n2, 1.0, EPSILON); +} */ diff --git a/tests/test_materials.c b/tests/test_materials.c index 4166273..ecd8782 100644 --- a/tests/test_materials.c +++ b/tests/test_materials.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* test_materials.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/11 10:46:43 by yde-goes #+# #+# */ -/* Updated: 2023/05/09 12:38:31 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 11:15:01 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -202,3 +202,27 @@ Test(materials, lighting_surface_in_shadow) cr_assert_float_eq(result.green, expected.green, EPSILON); cr_assert_float_eq(result.blue, expected.blue, EPSILON); } + +/* Show that material structure contains a new attribute, called refletive */ +Test(materials, reflectivity_for_default_material) +{ + t_material m; + + m = material(); + cr_assert(eq(flt, m.reflective, 0.0)); +} + +/* + * Transparency and Refractive Index for the default material. This test shows + * that your material structure contains two new attributes, called transparency + * and refractive_index. transparency defaults to 0, and refractive_index + * defaults to 1. + */ +Test(materials, transp_ref_index_for_default_material) +{ + t_material m; + + m = material(); + cr_assert(eq(flt, m.transparency, 0.0)); + cr_assert(eq(flt, m.refractive_index, 1.0)); +} diff --git a/tests/test_spheres.c b/tests/test_spheres.c index ef00369..a74d325 100644 --- a/tests/test_spheres.c +++ b/tests/test_spheres.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* test_spheres.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/06 09:22:45 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 12:37:58 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 12:47:56 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -296,3 +296,16 @@ Test(spheres, sphere_default_material) cr_assert(eq(flt, s.material.specular, m.specular)); cr_assert(eq(flt, s.material.shininess, m.shininess)); } + +/* A helper for producing a sphere with a glassy material. */ +Test(spheres, sphere_with_glassy_material) +{ + t_shape s; + + s = new_sphere(); + glassy_shape(&s); + + assert_matrix_equal(s.transform, get_identity_matrix()); + cr_assert(eq(flt, s.material.transparency, 1.0)); + cr_assert(eq(flt, s.material.refractive_index, 1.5)); +} diff --git a/tests/test_world.c b/tests/test_world.c index db6968d..8e54c8a 100644 --- a/tests/test_world.c +++ b/tests/test_world.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* test_world.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/13 14:55:26 by mdias-ma #+# #+# */ -/* Updated: 2023/05/09 12:39:10 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 14:39:10 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -149,7 +149,7 @@ Test(world, shading_intersection) i = intersection(4, w.objects); r = new_ray(point(0, 0, -5), vector(0, 0, 1)); comps = prepare_computations(i, r); - c = shade_hit(w, comps); + c = shade_hit(w, comps, MAX_RECURSION); expected = new_color(0.38066, 0.47583, 0.2855); cr_assert_float_eq(c.red, expected.red, EPSILON); @@ -174,7 +174,7 @@ Test(world, shading_intersection_from_inside) shape = w.objects[1]; i = intersection(0.5, &shape); comps = prepare_computations(i, r); - c = shade_hit(w, comps); + c = shade_hit(w, comps, MAX_RECURSION); expected = new_color(0.90498, 0.90498, 0.90498); cr_assert_float_eq(c.red, expected.red, EPSILON); @@ -193,7 +193,7 @@ Test(world, color_for_missed_ray) w = default_world(); r = new_ray(point(0, 0, -5), vector(0, 1, 0)); - c = color_at(w, r); + c = color_at(w, r, MAX_RECURSION); expected = new_color(0, 0, 0); cr_assert_float_eq(c.red, expected.red, EPSILON); @@ -212,7 +212,7 @@ Test(world, color_when_a_ray_hits) w = default_world(); r = new_ray(point(0, 0, -5), vector(0, 0, 1)); - c = color_at(w, r); + c = color_at(w, r, MAX_RECURSION); expected = new_color(0.38066, 0.47583, 0.2855); cr_assert_float_eq(c.red, expected.red, EPSILON); @@ -237,7 +237,7 @@ Test(world, color_with_an_intersection_behind_the_ray) inner = w.objects + 1; inner->material.ambient = 1; r = new_ray(point(0, 0, 0.75), vector(0, 0, -1)); - c = color_at(w, r); + c = color_at(w, r, MAX_RECURSION); cr_assert_float_eq(c.red, inner->material.color.red, EPSILON); } @@ -324,10 +324,170 @@ Test(world, shade_hit_intersection_in_shadow) r = new_ray(point(0, 0, 5), vector(0, 0, 1)); i = intersection(4, &s2); comps = prepare_computations(i, r); - c = shade_hit(w, comps); + c = shade_hit(w, comps, MAX_RECURSION); expected = new_color(0.1, 0.1, 0.1); cr_assert_float_eq(c.red, expected.red, EPSILON); cr_assert_float_eq(c.green, expected.green, EPSILON); cr_assert_float_eq(c.blue, expected.blue, EPSILON); } + +/* + * The reflected color for a nonreflective material. Shows that when a ray + * strikes a nonreflective surface, the reflected_color() function returns + * the color black. + */ +Test(world, reflected_color_for_nonreflect_material) +{ + t_world w; + t_ray r; + t_shape shape; + t_hit *i; + t_comps comps; + t_color color; + t_color expected; + + expected = new_color(0, 0, 0); + w = default_world(); + r = new_ray(point(0, 0, 0), vector(0, 0, 1)); + shape = w.objects[1]; + shape.material.ambient = 1.0; + i = intersection(1, &shape); + comps = prepare_computations(i, r); + color = reflected_color(w, comps, MAX_RECURSION); + + cr_assert_float_eq(color.red, expected.red, EPSILON); + cr_assert_float_eq(color.green, expected.green, EPSILON); + cr_assert_float_eq(color.blue, expected.blue, EPSILON); +} + +/* + * The reflected color for a reflective material. Shows that reflected_color() + * returns the color via reflection when the struck surface is reflective. + */ +//WARNING: modified test conditions +Test(world, reflected_color_for_reflective_material) +{ + t_world w; + t_ray r; + t_shape shape; + t_hit *i; + t_comps comps; + t_color color; + float coord; + t_color expected; + + expected = new_color(0.19033, 0.23791, 0.14274); + coord = sqrtf(2)/2.0; + w = default_world(); + shape = new_plane(); + shape.material.reflective = 0.5; + shape.transform = translation(0, -1, 0); + add_shape_to_world(&w, shape); + r = new_ray(point(0, 0, -3), vector(0, -coord, coord)); + i = intersection(sqrtf(2), &shape); + comps = prepare_computations(i, r); + color = reflected_color(w, comps, MAX_RECURSION); + + cr_assert_float_eq(color.red, expected.red, EPSILON); + cr_assert_float_eq(color.green, expected.green, EPSILON); + cr_assert_float_eq(color.blue, expected.blue, EPSILON); +} + +/* + * shade_hit() with reflective material. Shows that shade_hit() incorporates + * the reflected color into the final color. + */ +//WARNING: modified test conditions +Test(world, shade_hit_with_reflective_material) +{ + t_world w; + t_ray r; + t_shape shape; + t_hit *i; + t_comps comps; + t_color color; + float coord; + t_color expected; + + expected = new_color(0.87676, 0.92435, 0.82918); + coord = sqrtf(2)/2.0; + w = default_world(); + shape = new_plane(); + shape.material.reflective = 0.5; + shape.transform = translation(0, -1, 0); + add_shape_to_world(&w, shape); + r = new_ray(point(0, 0, -3), vector(0, -coord, coord)); + i = intersection(sqrtf(2), &shape); + comps = prepare_computations(i, r); + color = shade_hit(w, comps, MAX_RECURSION); + + cr_assert_float_eq(color.red, expected.red, EPSILON); + cr_assert_float_eq(color.green, expected.green, EPSILON); + cr_assert_float_eq(color.blue, expected.blue, EPSILON); +} + +/* + * color_at() with mutually reflective surfaces. shade_hit() calls + * reflected_vector() which calls color_at(). This test shows that the code + * safety handles infinite recursion caused by two objects that mutually + * reflect rays between themselves. + */ +Test(world, color_at_mutual_surface_reflection) +{ + t_world w; + t_shape lower; + t_shape upper; + t_ray r; + t_color color; + + w = default_world(); + w.lights[0] = point_light(point(0, 0, 0), new_color(1, 1, 1)); + lower = new_plane(); + lower.material.reflective = 1.0; + lower.transform = translation(0, -1, 0); + add_shape_to_world(&w, lower); + upper = new_plane(); + upper.material.reflective = 1.0; + upper.transform = translation(0, 1, 0); + add_shape_to_world(&w, upper); + r = new_ray(point(0, 0, 0), vector(0, 1, 0)); + color = color_at(w, r, 0); + + cr_assert(color.red); + cr_assert(color.green); + cr_assert(color.blue); +} + +/* + * The reflected color at the maximum recursive depth. This test shows that + * reflected_color() returns without effect when invoked at the limit of its + * recursive threshold. + */ +Test(world, reflected_color_max_recursive_depth) +{ + t_world w; + t_shape shape; + t_ray r; + t_hit *i; + t_comps comps; + t_color color; + t_color expected; + float coord; + + expected = new_color(0, 0, 0); + coord = sqrtf(2)/2.0; + w = default_world(); + shape = new_plane(); + shape.material.reflective = 0.5; + shape.transform = translation(0, -1, 0); + add_shape_to_world(&w, shape); + r = new_ray(point(0, 0, -3), vector(0, -coord, coord)); + i = intersection(sqrtf(2), &shape); + comps = prepare_computations(i, r); + color = reflected_color(w, comps, 0); + + cr_assert_float_eq(color.red, expected.red, EPSILON); + cr_assert_float_eq(color.green, expected.green, EPSILON); + cr_assert_float_eq(color.blue, expected.blue, EPSILON); +} diff --git a/tests/utils.c b/tests/utils.c index dd22034..03c1916 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* utils.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: mdias-ma +#+ +:+ +#+ */ +/* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/06 09:30:07 by mdias-ma #+# #+# */ -/* Updated: 2023/05/07 19:22:16 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 14:55:28 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -43,12 +43,14 @@ t_world default_world(void) w.object_count = 2; s1 = new_sphere(); + s1.material = material(); s1.material.color = new_color(0.8, 1.0, 0.6); s1.material.diffuse = 0.7; s1.material.specular = 0.2; s1.sphere.radius = 0.5; s2 = new_sphere(); + s2.material = material(); s2.transform = scaling(0.5, 0.5, 0.5); s2.sphere.radius = 1.0; @@ -58,6 +60,20 @@ t_world default_world(void) return (w); } +//TODO: an "add_light_to_world()" maybe will be needed to test multispot lights +void add_shape_to_world(t_world *world, t_shape shape) +{ + t_shape *new_objects; + + new_objects = ft_calloc(world->object_count + 1, sizeof(t_shape)); + ft_memmove(new_objects, world->objects, + world->object_count * sizeof(t_shape)); + new_objects[world->object_count] = shape; + free(world->objects); + world->objects = new_objects; + world->object_count++; +} + t_bool compare_spheres(t_sphere *a, t_sphere *b) { return ( diff --git a/tests/utils.h b/tests/utils.h index 018c867..79dc436 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -6,7 +6,7 @@ /* By: yde-goes +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/04/06 09:31:28 by mdias-ma #+# #+# */ -/* Updated: 2023/04/26 15:49:00 by mdias-ma ### ########.fr */ +/* Updated: 2023/05/12 10:38:05 by yde-goes ### ########.fr */ /* */ /* ************************************************************************** */ @@ -29,6 +29,7 @@ t_bool compare_spheres(t_sphere *a, t_sphere *b); t_world world_stub(void); t_world default_world(void); +void add_shape_to_world(t_world *world, t_shape shape); t_sphere *sphere_stub(void); t_bool compare_spheres(t_sphere *a, t_sphere *b); t_bool compare_tuples(t_tuple a, t_tuple b);