/*! * 13_cylinders.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: 18/03/2024 * */ /*---------------------------------------------------------------------------*/ #include #include "raytracing.h" #include "common_data.h" using namespace Raytracer; /* ------------------------------------------------------------------------- */ SCENARIO("A Ray misses a cylinder", "[features/cylinders.feature]") { // | origin | direction | // | point(1, 0, 0) | vector(0, 1, 0) | // | point(0, 0, 0) | vector(0, 1, 0) | // | point(0, 0, -5) | vector(1, 1, 1) | DataOrigDirT0T1 the_test[] = { {Tuple::Point(1, 0, 0), Tuple::Vector(0, 1, 0)}, {Tuple::Point(0, 0, 0), Tuple::Vector(0, 1, 0)}, {Tuple::Point(0, 0, -5), Tuple::Vector(1, 1, 1)} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; AND_GIVEN("r <- ray(, )") { WHEN("xs <- local_intersect(cyl,r)") { for (int i = 0; i < 3; i++) { Ray r(the_test[i].origin, the_test[i].direction); Intersections xs = cyl.local_intersect(r); THEN("xs.count = 0") { REQUIRE(xs.count() == 0); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A Ray strikes a cylinder", "[features/cylinders.feature]") { // | origin | direction | t0 | t1 | // | point(1, 0, -5) | vector(0, 0, 1) | 5 | 5 | // | point(0, 0, -5) | vector(0, 0, 1) | 4 | 6 | // | point(0.5, 0, -5) | vector(0.1, 1, 1) | 6.80798 | 7.08872 | DataOrigDirT0T1 the_test[] = { { Tuple::Point(1, 0, -5), Tuple::Vector(0, 0, 1), 5, 5}, { Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1), 4, 6}, {Tuple::Point(0.5, 0, -5), Tuple::Vector(0.1, 1, 1), 6.80798, 7.08872} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; AND_GIVEN("direction <- normalize()") { AND_GIVEN("r <- ray(, direction)") { WHEN("xs <- local_intersect(cyl,r)") { for (int i = 0; i < 3; i++) { Tuple direction = the_test[i].direction.normalize(); Ray r(the_test[i].origin, direction); Intersections xs = cyl.local_intersect(r); THEN("xs.count = 2") { REQUIRE(xs.count() == 2); } AND_THEN("xs[0].t = ") { REQUIRE(xs[0].distance_t() == the_test[i].t0); } AND_THEN("xs[1].t = ") { REQUIRE(xs[1].distance_t() == the_test[i].t1); } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Normal vector on a cylinder", "[features/cylinders.feature]") { // | point | normal | // | point(1, 0, 0) | vector(1, 0, 0) | // | point(0, 5, -1) | vector(0, 0, -1) | // | point(0, -2, 1) | vector(0, 0, 1) | // | point(-1, 1, 0) | vector(-1, 0, 0) | DataPointNormal the_test[] = { { Tuple::Point(1, 0, 0), Tuple::Vector(1, 0, 0)}, { Tuple::Point(0, 5, -1), Tuple::Vector(0, 0, -1)}, { Tuple::Point(0, -2, 1), Tuple::Vector(0, 0, 1)}, {Tuple::Point(-1, 1, 0), Tuple::Vector(-1, 0, 0)} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; WHEN("n <- local_normal_at(cyl,)") { for (int i = 0; i < 4; i++) { Tuple p = the_test[i].point; Tuple normal = cyl.local_normal_at(p); THEN("n = ") { REQUIRE(normal == the_test[i].normal); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The default minimum and maximum for a cylinder", "[features/cylinders.feature]") { GIVEN("cyl <- cylinder()") { Cylinder cyl; THEN("cym.minimum = -infinity") { REQUIRE(cyl.minimum() == -std::numeric_limits::infinity()); } AND_THEN("cym.maximum = infinity") { REQUIRE(cyl.maximum() == std::numeric_limits::infinity()); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersecting a constrained cylinder", "[features/cylinders.feature]") { // | | point | direction | count | // | 1 | point(0, 1.5, 0) | vector(0.1, 1, 0) | 0 | // | 2 | point(0, 3, -5) | vector(0, 0, 1) | 0 | // | 3 | point(0, 0, -5) | vector(0, 0, 1) | 0 | // | 4 | point(0, 2, -5) | vector(0, 0, 1) | 0 | // | 5 | point(0, 1, -5) | vector(0, 0, 1) | 0 | // | 6 | point(0, 1.5, -2) | vector(0, 0, 1) | 2 | DataPointDirCount the_test[] = { {Tuple::Point(0, 1.5, 0), Tuple::Vector(0.1, 1, 0), 0}, {Tuple::Point(0, 3, -5), Tuple::Vector(0, 0, 1), 0}, {Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1), 0}, {Tuple::Point(0, 2, -5), Tuple::Vector(0, 0, 1), 0}, {Tuple::Point(0, 1, -5), Tuple::Vector(0, 0, 1), 0}, {Tuple::Point(0, 1.5, -20), Tuple::Vector(0, 0, 1), 2} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; AND_GIVEN("cyl.minimum <- 1") { cyl.set_minimum(1); AND_GIVEN("cyl.maximum <- 2") { cyl.set_maximum(2); AND_GIVEN("direction <- normalize()") { AND_GIVEN("r <- ray(, direction)") { WHEN("xs <- local_intersect(cyl,r)") { for (int i = 0; i < 6; i++) { Tuple direction = the_test[i].direction.normalize(); Ray r(the_test[i].point, direction); Intersections xs = cyl.local_intersect(r); THEN("xs.count = ") { REQUIRE(xs.count() == the_test[i].count); } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The default closed value for a cylinder", "[features/cylinders.feature]") { GIVEN("cyl <- cylinder()") { Cylinder cyl; THEN("cyl.closed = false") { REQUIRE(cyl.closed() == false); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersecting the caps of a closed cylinder", "[features/cylinders.feature]") { // | | point | direction | count | // | 1 | point(0, 3, 0) | vector(0, -1, 0) | 2 | // | 2 | point(0, 3, -2) | vector(0, -1, 2) | 2 | // | 3 | point(0, 4, -2) | vector(0, -1, 1) | 2 | # corner case // | 4 | point(0, 0, -2) | vector(0, 1, 2) | 2 | // | 5 | point(0, -1, -2) | vector(0, 1, 1) | 2 | # corner case DataPointDirCount the_test[] = { {Tuple::Point(0, 3, 0), Tuple::Vector(0, -1, 0), 2}, {Tuple::Point(0, 3, -2), Tuple::Vector(0, -1, 2), 2}, {Tuple::Point(0, 4, -2), Tuple::Vector(0, -1, 1), 2}, {Tuple::Point(0, 0, -2), Tuple::Vector(0, 1, 2), 2}, {Tuple::Point(0, -1, -2), Tuple::Vector(0, 1, 1), 2} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; AND_GIVEN("cyl.minimum <- 1") { cyl.set_minimum(1); AND_GIVEN("cyl.maximum <- 2") { cyl.set_maximum(2); AND_GIVEN("cyl.closed <- true") { cyl.set_closed(true); AND_GIVEN("direction <- normalize()") { AND_GIVEN("r <- ray(, direction)") { WHEN("xs <- local_intersect(cyl,r)") { for (int i = 0; i < 5; i++) { Tuple direction = the_test[i].direction.normalize(); Ray r(the_test[i].point, direction); Intersections xs = cyl.local_intersect(r); THEN("xs.count = ") { REQUIRE(xs.count() == the_test[i].count); } } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The normal vector on the cylinder's end caps", "[features/cylinders.feature]") { // | point | normal | // | point(0, 1, 0) | vector(0, -1, 0) | // | point(0.5, 1, 0) | vector(0, -1, 0) | // | point(0, 1, 0.5) | vector(0, -1, 0) | // | point(0, 2, 0) | vector(0, 1, 0) | // | point(0.5, 2, 0) | vector(0, 1, 0) | // | point(0, 2, 0.5) | vector(0, 1, 0) | DataPointNormal the_test[] = { { Tuple::Point(0, 1, 0), Tuple::Vector(0, -1, 0)}, {Tuple::Point(0.5, 1, 0), Tuple::Vector(0, -1, 0)}, { Tuple::Point(0, 1, 0.5), Tuple::Vector(0, -1, 0)}, { Tuple::Point(0, 2, 0), Tuple::Vector(0, 1, 0)}, {Tuple::Point(0.5, 2, 0), Tuple::Vector(0, 1, 0)}, { Tuple::Point(0, 2, 0.5), Tuple::Vector(0, 1, 0)} }; GIVEN("cyl <- cylinder()") { Cylinder cyl; AND_GIVEN("cyl.minimum <- 1") { cyl.set_minimum(1); AND_GIVEN("cyl.maximum <- 2") { cyl.set_maximum(2); AND_GIVEN("cyl.closed <- true") { cyl.set_closed(true); WHEN("n <- local_normal_at(cyl,)") { for (int i = 0; i < 6; i++) { Tuple p = the_test[i].point; Tuple normal = cyl.local_normal_at(p); THEN("n = ") { REQUIRE(normal == the_test[i].normal); } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersecting a cone with a ray", "[features/cones.feature]") { // | origin | direction | t0 | t1 | // | point(0, 0, -5) | vector(0, 0, 1) | 5 | 5 | // | point(0, 0, -5) | vector(1, 1, 1) | 8.66025 | 8.66025 | // | point(1, 1, -5) | vector(-0.5, -1, 1) | 4.55006 | 49.44994 | DataOrigDirT0T1 the_test[] = { {Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1), 5, 5}, {Tuple::Point(0, 0, -5), Tuple::Vector(1, 1, 1), 8.66025, 8.66025}, {Tuple::Point(1, 1, -5), Tuple::Vector(-0.5, -1, 1), 4.55006, 49.44994} }; GIVEN("shape <- cone()") { Cone shape; AND_GIVEN("direction <- normalize()") { AND_GIVEN("r <- ray(, direction)") { WHEN("xs <- local_intersect(shape, r)") { for (int i = 0; i < 5; i++) { Tuple direction = the_test[i].direction.normalize(); Ray r(the_test[i].origin, direction); Intersections xs = shape.local_intersect(r); THEN("xs.count = 2") { REQUIRE(xs.count() == 2); } AND_THEN("xs[0].t = ") { REQUIRE(xs[0].distance_t() == the_test[i].t0); } AND_THEN("xs[1].t = ") { REQUIRE(xs[1].distance_t() == the_test[i].t1); } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersecting a cone with a ray parallel ton one of its halves", "[features/cones.feature]") { GIVEN("shape <- cone()") { Cone shape; AND_GIVEN("direction <- normalize(vector(0, 1, 1))") { Tuple direction = Tuple::Vector(0, 1, 1).normalize(); AND_GIVEN("r <- ray(point(0, 0, -1), direction)") { Ray r(Tuple::Point(0, 0, -1), direction); WHEN("xs <- local_intersect(shape, r)") { Intersections xs = shape.local_intersect(r); THEN("xs.count = 1") { REQUIRE(xs.count() == 1); } AND_THEN("xs[0].t = 0.35355") { REQUIRE(double_equal(xs[0].distance_t(), 0.35355)); } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Intersecting a cone's end caps", "[features/cones.feature]") { // | origin | direction | count | // | point(0, 0, -5) | vector(0, 1, 0) | 0 | // | point(0, 0, -0.25) | vector(0, 1, 1) | 2 | // | point(0, 0, -0.25) | vector(0, 1, 0) | 4 | DataOrigDirCount the_test[] = { {Tuple::Point(0, 0, -5), Tuple::Vector(0, 1, 0), 0}, {Tuple::Point(0, 0, -0.25), Tuple::Vector(0, 1, 1), 2}, {Tuple::Point(0, 0, -0.25), Tuple::Vector(0, 1, 0), 4} }; GIVEN("shape <- cone()") { Cone shape; AND_GIVEN("shape.minimum <- 0.5") { shape.set_minimum(0.5); AND_GIVEN("shape.maximum <- 0.5") { shape.set_maximum(0.5); AND_GIVEN("shape.closed <- true") { shape.set_closed(true); AND_GIVEN("direction <- normalize()") { AND_GIVEN("r <- ray(, direction)") { WHEN("xs <- local_intersect(shape,r)") { for (int i = 0; i < 5; i++) { Tuple direction = the_test[i].direction.normalize(); Ray r(the_test[i].origin, direction); Intersections xs = shape.local_intersect(r); THEN("xs.count = ") { REQUIRE(xs.count() == the_test[i].count); } } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Computing the normal vector on a cone", "[features/cones.feature]") { // | point | normal | // | point(0, 0, 0) | vector(0, 0, 0) | // | point(1, 1, 1) | vector(1, -sqrt(2), 1) | // | point(-1, -1, 0) | vector(-1, 1, 0) | DataPointNormal the_test[] = { { Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 0)}, { Tuple::Point(1, 1, 1), Tuple::Vector(1, -sqrt(2), 1)}, {Tuple::Point(-1, -1, 0), Tuple::Vector(-1, 1, 0)} }; GIVEN("shape <- cone()") { Cone shape; WHEN("n <- local_normal_at(shape, )") { for (int i = 0; i < 3; i++) { Tuple p = the_test[i].point; Tuple normal = shape.local_normal_at(p); THEN("n = ") { REQUIRE(normal == the_test[i].normal); } } } } }