/*! * 04_transformations.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: 05/02/2024 * */ /*---------------------------------------------------------------------------*/ #include #include "raytracing.h" using namespace Raytracer; /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying by a translation matrix", "[features/transformations.feature]") { GIVEN("transform <- translation(5, -3, 2)") { Matrix transform = Matrix::translation(5, -3, 2); AND_GIVEN("p <- point(-3, 4, 5)") { Tuple p = Tuple::Point(-3, 4, 5); THEN("transform * p = point(2, 1, 7)") { REQUIRE(transform * p == Tuple::Point(2, 1, 7)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying by the inverse of a translation matrix", "[features/transformations.feature]") { GIVEN("transform <- translation(5, -3, 2)") { Matrix transform = Matrix::translation(5, -3, 2); AND_GIVEN("inv <- inverse(transform)") { Matrix inv = transform.inverse(); AND_GIVEN("p <- point(-3, 4, 5)") { Tuple p = Tuple::Point(-3, 4, 5); THEN("inv * p = point(-8, 7, 3)") { REQUIRE(inv * p == Tuple::Point(-8, 7, 3)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Translation does not affect vectors", "[features/transformations.feature]") { GIVEN("transform <- translation(5, -3, 2)") { Matrix transform = Matrix::translation(5, -3, 2); AND_GIVEN("v <- vector(-3, 4, 5)") { Tuple v = Tuple::Vector(-3, 4, 5); THEN("transform * v = v") { REQUIRE(transform * v == v); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A scaling matrix applied to a point", "[features/transformations.feature]") { GIVEN("transform <- scaling(2, 3, 4)") { Matrix transform = Matrix::scaling(2, 3, 4); AND_GIVEN("p <- point(-4, 6, 8)") { Tuple p = Tuple::Point(-4, 6, 8); THEN("transform * p = point(-8, 18, 32)") { REQUIRE(transform * p == Tuple::Point(-8, 18, 32)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A scaling matrix applied to a vector", "[features/transformations.feature]") { GIVEN("transform <- scaling(2, 3, 4)") { Matrix transform = Matrix::scaling(2, 3, 4); AND_GIVEN("v <- vector(-4, 6, 8)") { Tuple v = Tuple::Vector(-4, 6, 8); THEN("transform * p = vector(-8, 18, 32)") { REQUIRE(transform * v == Tuple::Vector(-8, 18, 32)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying by the inverse of a scaling matrix", "[features/transformations.feature]") { GIVEN("transform <- scaling(2, 3, 4)") { Matrix transform = Matrix::scaling(2, 3, 4); AND_GIVEN("inv <- inverse(transform)") { Matrix inv = transform.inverse(); AND_GIVEN("v <- vector(-4, 6, 8)") { Tuple v = Tuple::Vector(-4, 6, 8); THEN("inv * v = vector(-2, 2, 2)") { REQUIRE(inv * v == Tuple::Vector(-2, 2, 2)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Reflection is scaling by a negative value", "[features/transformations.feature]") { GIVEN("transform <- scaling(-1, 1, 1)") { Matrix transform = Matrix::scaling(-1, 1, 1); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p = point(-2, 3, 4)") { REQUIRE(transform * p == Tuple::Point(-2, 3, 4)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Rotating a point around the x axis", "[features/transformations.feature]") { GIVEN("p <- point(0, 1, 0)") { Tuple p = Tuple::Point(0, 1, 0); AND_GIVEN("half_quarter <- rotation_x(pi/4)") { Matrix half_quarter = Matrix::rotation_x(std::numbers::pi / 4); AND_GIVEN("full_quarter <- rotation_x(pi/2)") { Matrix full_quarter = Matrix::rotation_x(std::numbers::pi / 2); THEN("half_quarter * p = point(0, sqrt(2) / 2, sqrt(2) / 2)") { REQUIRE(half_quarter * p == Tuple::Point(0, sqrt(2) / 2, sqrt(2) / 2)); } AND_THEN("full_quarter * p == point(0, 0, 1)") { REQUIRE(full_quarter * p == Tuple::Point(0, 0, 1)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("The inverse of an x-rotation rotates in the opposite direction", "[features/transformations.feature]") { GIVEN("p <- point(0, 1, 0)") { Tuple p = Tuple::Point(0, 1, 0); AND_GIVEN("half_quarter <- rotation_x(pi/4)") { Matrix half_quarter = Matrix::rotation_x(std::numbers::pi / 4); AND_GIVEN("inv <- inverse(half_quarter)") { Matrix inv = half_quarter.inverse(); THEN("inv * p = point(0, sqrt(2) / 2, -sqrt(2) / 2)") { REQUIRE(inv * p == Tuple::Point(0, sqrt(2) / 2, -sqrt(2) / 2)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Rotating a point around the y axis", "[features/transformations.feature]") { GIVEN("p <- point(0, 1, 0)") { Tuple p = Tuple::Point(0, 0, 1); AND_GIVEN("half_quarter <- rotation_y(pi/4)") { Matrix half_quarter = Matrix::rotation_y(std::numbers::pi / 4); AND_GIVEN("full_quarter <- rotation_y(pi/2)") { Matrix full_quarter = Matrix::rotation_y(std::numbers::pi / 2); THEN("half_quarter * p = point(sqrt(2) / 2, 0, sqrt(2) / 2)") { REQUIRE(half_quarter * p == Tuple::Point(sqrt(2) / 2, 0, sqrt(2) / 2)); } AND_THEN("full_quarter * p = point(1, 0, 0)") { REQUIRE(full_quarter * p == Tuple::Point(1, 0, 0)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Rotating a point around the z axis", "[features/transformations.feature]") { GIVEN("p <- point(0, 1, 0)") { Tuple p = Tuple::Point(0, 1, 0); AND_GIVEN("full_quarter <- rotation_z(pi/4)") { Matrix half_quarter = Matrix::rotation_z(std::numbers::pi / 4); AND_GIVEN("full_quarter <- rotation_z(pi/2)") { Matrix full_quarter = Matrix::rotation_z(std::numbers::pi / 2); Tuple z = half_quarter * p; THEN("half_quarter * p = point(-sqrt(2) / 2, sqrt(2) / 2, 0)") { REQUIRE(half_quarter * p == Tuple::Point(-sqrt(2) / 2, sqrt(2) / 2, 0)); } AND_THEN("full_quarter * p = point(-1, 0, 0)") { REQUIRE(full_quarter * p == Tuple::Point(-1, 0, 0)); } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A shearing transformation moves x in proportion to y", "[features/transformations.feature]") { GIVEN("transform <- shearing(1, 0, 0, 0, 0, 0)") { Matrix transform = Matrix::shearing(1, 0, 0, 0, 0, 0); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p == point(5, 3, 4)") { REQUIRE(transform * p == Tuple::Point(5, 3, 4)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A shearing transformation moves y in proportion to x", "[features/transformations.feature]") { GIVEN("transform <- shearing(0, 0, 1, 0, 0, 0)") { Matrix transform = Matrix::shearing(0, 0, 1, 0, 0, 0); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p == point(2, 5, 4)") { REQUIRE(transform * p == Tuple::Point(2, 5, 4)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A shearing transformation moves y in proportion to z", "[features/transformations.feature]") { GIVEN("transform <- shearing(0, 0, 0, 1, 0, 0)") { Matrix transform = Matrix::shearing(0, 0, 0, 1, 0, 0); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p == point(2, 7, 4)") { REQUIRE(transform * p == Tuple::Point(2, 7, 4)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A shearing transformation moves z in proportion to x", "[features/transformations.feature]") { GIVEN("transform <- shearing(0, 0, 0, 0, 1, 0)") { Matrix transform = Matrix::shearing(0, 0, 0, 0, 1, 0); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p == point(2, 3, 6)") { REQUIRE(transform * p == Tuple::Point(2, 3, 6)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A shearing transformation moves z in proportion to y", "[features/transformations.feature]") { GIVEN("transform <- shearing(0, 0, 0, 0, 0, 1)") { Matrix transform = Matrix::shearing(0, 0, 0, 0, 0, 1); AND_GIVEN("p <- point(2, 3, 4)") { Tuple p = Tuple::Point(2, 3, 4); THEN("transform * p == point(2, 3, 7)") { REQUIRE(transform * p == Tuple::Point(2, 3, 7)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Individual transformations are applied in sequence", "[features/transformations.feature]") { GIVEN("p <- point(1, 0, 1)") { Tuple p = Tuple::Point(1, 0, 1); AND_GIVEN("A <- rotation_x(pi/2)") { Matrix A = Matrix::rotation_x(std::numbers::pi / 2); AND_GIVEN("B <- scaling(5, 5, 5)") { Matrix B = Matrix::scaling(5, 5, 5); AND_GIVEN("C <- translation(10, 5, 7))") { Matrix C = Matrix::translation(10, 5, 7); // Apply rotation first. WHEN("p2 <- A * p") { Tuple p2 = A * p; THEN("p2 = point(1, -1, 0)") { REQUIRE(p2 == Tuple::Point(1, -1, 0)); } // Then Apply scaling WHEN("p3 <- B * p2") { Tuple p3 = B * p2; THEN("p3 = point(5, -5, 0)") { REQUIRE(p3 == Tuple::Point(5, -5, 0)); } // Then Apply translation WHEN("p4 = C * p3") { Tuple p4 = C * p3; THEN("p4 = point(15, 0, 7)") { REQUIRE(p4 == Tuple::Point(15, 0, 7)); } } } } } } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Chained transformation must be applied in revert order", "[features/transformations.feature]") { GIVEN("p <- point(1, 0, 1)") { Tuple p = Tuple::Point(1, 0, 1); AND_GIVEN("A <- rotation_x(pi/2)") { Matrix A = Matrix::rotation_x(std::numbers::pi / 2); AND_GIVEN("B <- scaling(5, 5, 5)") { Matrix B = Matrix::scaling(5, 5, 5); AND_GIVEN("C <- translation(10, 5, 7))") { Matrix C = Matrix::translation(10, 5, 7); WHEN("t <- C * B * A") { Matrix T = C * B * A; THEN("T * p == point(15, 0, 7)") { REQUIRE(T * p == Tuple::Point(15, 0, 7)); } } } } } } }