/*! * 07_making_scene.cpp * * Copyright (c) 2015-2024, NADAL Jean-Baptiste. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * @Author: NADAL Jean-Baptiste * @Date: 14/02/2024 * */ /*---------------------------------------------------------------------------*/ #include #include "raytracing.h" using namespace Raytracer; /* ------------------------------------------------------------------------- */ SCENARIO("Creating a world", "[features/world.feature]") { GIVEN("w <- world()") { World w; THEN("w contains no objects") { REQUIRE(w.objects_count() == 0); } AND_THEN("w has no light source") { REQUIRE(w.has_light() == false); } } } /* ------------------------------------------------------------------------- */ SCENARIO("The default world", "[features/world.feature]") { GIVEN("light <- point_light(point(-10, 10, -10), color(1, 1, 1))") { PointLight light = PointLight(Tuple::Point(-10, 10, -10), Color(1, 1, 1)); AND_GIVEN("s1 <- sphere") // with: // | material.color | (0.8, 1.0, 0.6) | // | material.diffuse | 0.7 | // | material.specular | 0.2 | { Sphere s1; s1.material().set_color(Color(0.8, 1.0, 0.6)); s1.material().set_diffuse(0.7); s1.material().set_specular(0.2); AND_GIVEN("s2 <- sphere") // with: // | transform | scaling(0.5, 0.5, 0.5) | { Sphere s2; s2.set_transform(Matrix::scaling(0.5, 0.5, 0.5)); WHEN("w <- default_world") { World w = World::default_world(); THEN("w.light = light") { REQUIRE(w.light() == light); } AND_THEN("w contains s1") { REQUIRE(w.contains(s1) == true); } AND_THEN("w contains s2") { REQUIRE(w.contains(s2) == true); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersect a world with a ray", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); WHEN("xs <- intersect_world(w,r)") { Intersections xs = w.intersect_world(r); THEN("xs.count = 4") { REQUIRE(xs.count() == 4); } AND_THEN("xs[0].t = 4") { REQUIRE(xs[0].distance_t() == 4); } AND_THEN("xs[1].t = 4.5") { REQUIRE(xs[1].distance_t() == 4.5); } AND_THEN("xs[2].t = 5.5") { REQUIRE(xs[2].distance_t() == 5.5); } AND_THEN("xs[3].t = 6") { REQUIRE(xs[3].distance_t() == 6); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Precompute the state of an intersection", "[features/intersections.feature]") { GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); AND_GIVEN("shape <- sphere()") { Sphere shape; AND_GIVEN("i <- intersection(4, shape)") { Intersection i(4, shape); WHEN("comps <- prepare_computations(i,r)") { IntersectionData comps = i.prepare_computations(r); THEN("comps.t = i.t") { REQUIRE(comps.distance_t() == i.distance_t()); } AND_THEN("comps.object = i.object") { REQUIRE(comps.object() == i.object()); } AND_THEN("comps.point = point(0, 0, -1)") { REQUIRE(comps.point() == Tuple::Point(0, 0, -1)); } AND_THEN("comps.eyev = vector(0, 0, -1)") { REQUIRE(comps.eyev() == Tuple::Vector(0, 0, -1)); } AND_THEN("comps.normalv = vector(0, 0, -1)") { REQUIRE(comps.normalv() == Tuple::Vector(0, 0, -1)); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The hit, when an intersection occurs on the outside", "[features/intersections.feature]") { GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); AND_GIVEN("shape <- sphere()") { Sphere shape; AND_GIVEN("i <- intersection(4, shape)") { Intersection i(4, shape); WHEN("comps <- prepare_computations(i,r)") { IntersectionData comps = i.prepare_computations(r); THEN("comps.inside = false") { REQUIRE(comps.is_inside() == false); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The hit, when an intersection occurs on the inside", "[features/intersections.feature]") { GIVEN("r <- ray(point(0, 0, 0), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 1)); AND_GIVEN("shape <- sphere()") { Sphere shape; AND_GIVEN("i <- intersection(1, shape)") { Intersection i(1, shape); WHEN("comps <- prepare_computations(i,r)") { IntersectionData comps = i.prepare_computations(r); THEN("comps.point = point(0, 0, 1)") { REQUIRE(comps.point() == Tuple::Point(0, 0, 1)); } AND_THEN("comps.eyev = vector(0, 0, -1)") { REQUIRE(comps.eyev() == Tuple::Vector(0, 0, -1)); } AND_THEN("comps.inside = false") { REQUIRE(comps.is_inside() == true); } AND_THEN("comps.normalv = vector(0, 0, -1)") { REQUIRE(comps.normalv() == Tuple::Vector(0, 0, -1)); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Shading an intersection", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); AND_GIVEN("shape <- first object of w") { Shape *shape = w.objects(0); AND_GIVEN("i <- intersection(4, shape)") { Intersection i(4, *shape); WHEN("comps <- prepare_computations(i,r)") { IntersectionData comps = i.prepare_computations(r); AND_WHEN("c <- shade_hit(w, comps)") { Color c = w.shade_hit(comps); THEN("c = color(0.38066, 0.47583, 0.2855)") { REQUIRE(c == Color(0.38066, 0.47583, 0.2855)); } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Shading an intersection from the inside", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("w.light <- point_light(point(0, 0.25, 0))") { w.set_light(PointLight(Tuple::Point(0, 0.25, 0), Color(1, 1, 1))); AND_GIVEN("r <- ray(point(0, 0, 0), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 1)); AND_GIVEN("shape <- second object of w") { Shape *shape = w.objects(1); AND_GIVEN("i <- intersection(0.5, shape)") { Intersection i(0.5, *shape); WHEN("comps <- prepare_computations(i,r)") { IntersectionData comps = i.prepare_computations(r); AND_WHEN("c <- shade_hit(w, comps)") { Color c = w.shade_hit(comps); THEN("c = color(0.90498, 0.90498, 0.90498)") { REQUIRE(c == Color(0.90498, 0.90498, 0.90498)); } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The color when a ray misses", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 1, 0))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 1, 0)); WHEN("c <- color_at(w, r)") { Color c = w.color_at(r); THEN("c = color(0, 0, 0)") { REQUIRE(c == Color(0, 0, 0)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The color when a ray hits", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") { Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); WHEN("c <- color_at(w, r)") { Color c = w.color_at(r); THEN("c = color(0.38066, 0.47583, 0.2855)") { REQUIRE(c == Color(0.38066, 0.47583, 0.2855)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The color with an intersection behind the ray", "[features/world.feature]") { GIVEN("w <- default_world()") { World w = World::default_world(); AND_GIVEN("outer <- the first object in w") { Shape *outer = w.objects(0); AND_GIVEN("outer.material.ambient <- 1") { outer->material().set_ambient(1); AND_GIVEN("inner <- the second object in w") { Shape *inner = w.objects(1); AND_GIVEN("inner.material.ambient <- 1") { inner->material().set_ambient(1); AND_GIVEN("r <- ray(point(0, 0, 0.75), vector(0, 0, -1))") { Ray r(Tuple::Point(0, 0, 0.75), Tuple::Vector(0, 0, -1)); WHEN("c <- color_at(w, r)") { Color c = w.color_at(r); THEN("c = inner.material.color") { REQUIRE(c == inner->material().color()); } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The transformation matrix for the default orientation", "[features/transformations.feature]") { GIVEN("from <- point(0, 0, 0)") { Tuple from = Tuple::Point(0, 0, 0); AND_GIVEN("to <- point(0, 0, -1)") { Tuple to = Tuple::Point(0, 0, -1); AND_GIVEN("up <- vector(0, 1, 0)") { Tuple up = Tuple::Vector(0, 1, 0); WHEN("t <- view_transform(from, to, up)") { Matrix t = Matrix::view_transform(from, to, up); THEN("t = identify_matrix") { REQUIRE(t == Matrix::identity()); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A view transformation matrix looking in positive z direction", "[features/transformations.feature]") { GIVEN("from <- point(0, 0, 0)") { Tuple from = Tuple::Point(0, 0, 0); AND_GIVEN("to <- point(0, 0, 1)") { Tuple to = Tuple::Point(0, 0, 1); AND_GIVEN("up <- vector(0, 1, 0)") { Tuple up = Tuple::Vector(0, 1, 0); WHEN("t <- view_transform(from, to, up)") { Matrix t = Matrix::view_transform(from, to, up); THEN("t = scaling(-1, 1, -1)") { REQUIRE(t == Matrix::scaling(-1, 1, -1)); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A view transformation moves the world", "[features/transformations.feature]") { GIVEN("from <- point(0, 0, 8)") { Tuple from = Tuple::Point(0, 0, 8); AND_GIVEN("to <- point(0, 0, 0)") { Tuple to = Tuple::Point(0, 0, 0); AND_GIVEN("up <- vector(0, 1, 0)") { Tuple up = Tuple::Vector(0, 1, 0); WHEN("t <- view_transform(from, to, up)") { Matrix t = Matrix::view_transform(from, to, up); THEN("t = translation(0, 0, -8)") { REQUIRE(t == Matrix::translation(0, 0, -8)); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("An arbitrary view transformation", "[features/transformations.feature]") { GIVEN("from <- point(1, 3, 2)") { Tuple from = Tuple::Point(1, 3, 2); AND_GIVEN("to <- point(4, -2, 8)") { Tuple to = Tuple::Point(4, -2, 8); AND_GIVEN("up <- vector(1, 1, 0)") { Tuple up = Tuple::Vector(1, 1, 0); WHEN("t <- view_transform(from, to, up)") { Matrix t = Matrix::view_transform(from, to, up); THEN("t iq the following 4x4 matrix") { // | -0.50709 | 0.50709 | 0.67612 | -2.36643 | // | 0.76772 | 0.60609 | 0.12122 | -2.82843 | // | -0.35857 | 0.59761 | -0.71714 | 0.00000 | // | 0.00000 | 0.00000 | 0.00000 | 1.00000 | REQUIRE(t == Matrix({ {-0.50709, 0.50709, 0.67612, -2.36643}, { 0.76772, 0.60609, 0.12122, -2.82843}, {-0.35857, 0.59761, -0.71714, 0.00000}, { 0.00000, 0.00000, 0.00000, 1.00000} })); } } } } } }