/*! * 03_matrix.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: 01/02/2024 * */ /*---------------------------------------------------------------------------*/ #include #include "raytracing.h" using namespace Raytracer; /* ------------------------------------------------------------------------- */ SCENARIO("Constructing and inspecting a 4x4 matrix", "[features/matrices.feature]") { GIVEN("rgz following 4x4 matrix M") { Matrix M = { { 1, 2, 3, 4}, { 5.5, 6.5, 7.5, 8.5}, { 9, 10, 11, 12}, {13.5, 14.5, 15.5, 16.5} }; THEN("M.rows = 4") { REQUIRE(M.rows() == 4); } AND_THEN("M.cols = 4") { REQUIRE(M.cols() == 4); } AND_THEN("M[0][0] = 1") { REQUIRE(M[0][0] == 1); } AND_THEN("M[0][3] = 4") { REQUIRE(M[0][3] == 4); } AND_THEN("M[1][0] = 5.5") { REQUIRE(M[1][0] == 5.5); } AND_THEN("M[1][2] = 7.5") { REQUIRE(M[1][2] == 7.5); } AND_THEN("M[2][2] = 11") { REQUIRE(M[2][2] == 11); } AND_THEN("M[3][0] = 13.5") { REQUIRE(M[3][0] == 13.5); } AND_THEN("M[3][2] = 15.5") { REQUIRE(M[3][2] == 15.5); } } } /* ------------------------------------------------------------------------- */ SCENARIO("A 2x2 matrix ought to be representable", "[features/matrices.feature]") { GIVEN("the following 2x2 matrix M") { Matrix M = { {-3, 5}, { 1, -2} }; THEN("M.rows = 2") { REQUIRE(M.rows() == 2); } AND_THEN("M.cols = 2") { REQUIRE(M.cols() == 2); } AND_THEN("M[0][0] = -3") { REQUIRE(M[0][0] == -3); } AND_THEN("M[0][1] = 5") { REQUIRE(M[0][1] == 5); } AND_THEN("M[1][0] = 1") { REQUIRE(M[1][0] == 1); } AND_THEN("M[1][1] = -2") { REQUIRE(M[1][1] == -2); } } } /* ------------------------------------------------------------------------- */ SCENARIO("A 3x3 matrix ought to be representable", "[features/matrices.feature]") { GIVEN("the following 3x3 matrix M") { Matrix M = { {-3, 5, 0}, { 1, -2, -7}, { 0, 1, 1} }; THEN("M.rows = 3") { REQUIRE(M.rows() == 3); } AND_THEN("M.cols == 3") { REQUIRE(M.cols() == 3); } AND_THEN("M[0][0] = -3") { REQUIRE(M[0][0] == -3); } AND_THEN("M[1][1] = -2") { REQUIRE(M[1][1] == -2); } AND_THEN("M[2][2] = 1") { REQUIRE(M[2][2] == 1); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Matrix equality with identical matrices", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 8, 7, 6}, {5, 4, 3, 2} }; AND_GIVEN("the following matrix B") { Matrix B = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 8, 7, 6}, {5, 4, 3, 2} }; THEN("A = B") { REQUIRE(A == B); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Matrix equality with different matrices", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 8, 7, 6}, {5, 4, 3, 2} }; AND_GIVEN("the following matrix B") { Matrix B = { {2, 3, 4, 5}, {6, 7, 8, 9}, {8, 7, 6, 5}, {4, 3, 2, 1} }; THEN("A != B") { REQUIRE(A != B); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying two matrices", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 8, 7, 6}, {5, 4, 3, 2} }; AND_GIVEN("the following matrix B") { Matrix B = { {-2, 1, 2, 3}, { 3, 2, 1, -1}, { 4, 3, 6, 5}, { 1, 2, 7, 8} }; AND_GIVEN("the following matrix C") { Matrix C = { {20, 22, 50, 48}, {44, 54, 114, 108}, {40, 58, 110, 102}, {16, 26, 46, 42} }; THEN("A * B = C") REQUIRE((A * B) == C); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("A matrix multiplied by a tuple", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {1, 2, 3, 4}, {2, 4, 4, 2}, {8, 6, 4, 1}, {0, 0, 0, 1} }; AND_GIVEN("b <- tuple(1, 2, 3, 1)") { Tuple b(1, 2, 3, 1); THEN("A * b = tuple(18, 24, 33, 1)") { REQUIRE((A * b) == Tuple(18, 24, 33, 1)); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying a matrix by the identity matrix", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {0, 1, 2, 4}, {1, 2, 4, 8}, {2, 4, 8, 16}, {4, 8, 16, 32} }; THEN("A * identity_matrix = A") { REQUIRE((A * Matrix::identity()) == A); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying the identity matrix by a tuple", "[features/matrices.feature]") { GIVEN("a <- tuple(1, 2, 3, 4)") { Tuple a(1, 2, 3, 4); THEN("identity_matrix * a = a") { REQUIRE((Matrix::identity() * a) == a); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Transposing a matrix", "[features/matrices.feature]") { GIVEN("the following matrix A") { Matrix A = { {0, 9, 3, 0}, {9, 8, 0, 8}, {1, 8, 5, 3}, {0, 0, 5, 8} }; THEN("transpose(A) is the following matrix") { Matrix transposed = { {0, 9, 1, 0}, {9, 8, 8, 0}, {3, 0, 5, 5}, {0, 8, 3, 8} }; A.transpose(); REQUIRE(A == transposed); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Transposing the identity matrix", "[features/matrices.feature]") { GIVEN("A <- transpose(identity_matrix)") { Matrix A = Matrix::identity(); A.transpose(); THEN("A = identity_matrix") { REQUIRE(A == Matrix::identity()); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the determinant of a 2x2 matrix", "[features/matrices.feature]") { GIVEN("the following 2x2 matrix A") { Matrix A = { { 1, 5}, {-3, 2} }; THEN("determinant(A) = 17") { REQUIRE(A.determinant() == 17); } } } /* ------------------------------------------------------------------------- */ SCENARIO("A submatrix of a 3x3 matrix is a 2x2 matrix", "[features/matrices.feature]") { GIVEN("the following 3x3 matrix A") { Matrix A = { { 1, 5, 0}, {-3, 2, 7}, { 0, 6, -3} }; Matrix B = { {-3, 2}, { 0, 6} }; THEN("submatrix(A,0,2) is the following 2x2 matrix") { REQUIRE(A.sub_matrix(0, 2) == B); } } } /* ------------------------------------------------------------------------- */ SCENARIO("A submatrix of a 4x4 matrix is a 3x3 matrix", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { {-6, 1, 1, 6}, {-8, 5, 8, 6}, {-1, 0, 8, 2}, {-7, 1, -1, 1} }; Matrix B = { {-6, 1, 6}, {-8, 8, 6}, {-7, -1, 1} }; THEN("submatrix(B,2,1) is the following 3x3 matrix") { REQUIRE(A.sub_matrix(2, 1) == B); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating a minor of a 3x3 matrix", "[features/matrices.feature]") { GIVEN("the following 3x3 matrix A") { Matrix A = { {3, 5, 0}, {2, -1, -7}, {6, -1, 5} }; AND_GIVEN("B <- submatrix(A,1,0)") { Matrix B = A.sub_matrix(1, 0); THEN("determinant(B) = 25") { REQUIRE(B.determinant() == 25); } AND_THEN("minor(A,1,0)=25") { REQUIRE(A.minor(1, 0) == 25); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating a cofactor of a 3x3 matrix", "[features/matrices.feature]") { GIVEN("the following 3x3 matrix A") { Matrix A = { {3, 5, 0}, {2, -1, -7}, {6, -1, 5} }; THEN("minor(A, 0, 0) = -12") { REQUIRE(A.minor(0, 0) == -12); } AND_THEN("cofactor(A, 0, 0) = -12") { REQUIRE(A.cofactor(0, 0) == -12); } AND_THEN("minor(A, 1, 0) = 25") { REQUIRE(A.minor(1, 0) == 25); } AND_THEN("cofactor(A, 1, 0) = -25") { REQUIRE(A.cofactor(1, 0) == -25); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the determinant of a 3x3 matrix", "[features/matrices.feature]") { GIVEN("the following 3x3 matrix A") { Matrix A = { { 1, 2, 6}, {-5, 8, -4}, { 2, 6, 4} }; THEN("cofactor(A, 0, 0) = 56") { REQUIRE(A.cofactor(0, 0) == 56); } AND_THEN("cofactor(A, 0, 1) = 12") { REQUIRE(A.cofactor(0, 1) == 12); } AND_THEN("cofactor(A, 0, 2) = -46") { REQUIRE(A.cofactor(0, 2) == -46); } AND_THEN("determinant(A) = -196") { REQUIRE(A.determinant() == -196); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the determinant of a 4x4 matrix", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { {-2, -8, 3, 5}, {-3, 1, 7, 3}, { 1, 2, -9, 6}, {-6, 7, 7, -9} }; THEN("cofactor(A, 0, 0) = 690") { REQUIRE(A.cofactor(0, 0) == 690); } AND_THEN("cofactor(A, 0, 1) = 447") { REQUIRE(A.cofactor(0, 1) == 447); } AND_THEN("cofactor(A, 0, 2) = 210") { REQUIRE(A.cofactor(0, 2) == 210); } AND_THEN("cofactor(A, 0, 3) = 51") { REQUIRE(A.cofactor(0, 3) == 51); } AND_THEN("determinant(A) = -4071") { REQUIRE(A.determinant() == -4071); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Testing an invertible matrix for invertibility", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { {6, 4, 4, 4}, {5, 5, 7, 6}, {4, -9, 3, -7}, {9, 1, 7, -6} }; THEN("determinant(A) = -2120") { REQUIRE(A.determinant() == -2120); } AND_THEN("A is invertible") { REQUIRE(A.invertible() == true); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Testing an noninvertible matrix for invertibility", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { {-4, 2, -2, -3}, { 9, 6, 2, 6}, { 0, -5, 1, -5}, { 0, 0, 0, 0} }; THEN("determinant(A) = 0") { REQUIRE(A.determinant() == 0); } AND_THEN("A is not invertible") { REQUIRE(A.invertible() == false); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the inverse of a matrix", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { {-5, 2, 6, -8}, { 1, -5, 1, 8}, { 7, 7, -6, -7}, { 1, -3, 7, 4} }; Matrix a_inverted = { { 0.21805, 0.45113, 0.24060, -0.04511}, {-0.80827, -1.45677, -0.44361, 0.52068}, {-0.07895, -0.22368, -0.05263, 0.19737}, {-0.52256, -0.81391, -0.30075, 0.30639} }; AND_GIVEN("B <- inverse(A)") { Matrix B = A.inverse(); THEN("determinant(A) = 532") { REQUIRE(A.determinant() == 532); } AND_THEN("cofactor(A, 2, 3) = -160") { REQUIRE(A.cofactor(2, 3) == -160); } AND_THEN("B[3][2] = -160.0 / 532.0") { REQUIRE(B[3][2] == -160.0 / 532.0); } AND_THEN("cofactor(A, 3, 2) = 105") { REQUIRE(A.cofactor(3, 2) == 105); } AND_THEN("B[2][3] = 105.0 / 532.0") { REQUIRE(B[2][3] == 105.0 / 532.0); } AND_THEN("B is the following 4x4 matrix") { REQUIRE(B == a_inverted); } } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the inverse of another matrix", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { { 8, -5, 9, 2}, { 7, 5, 6, 1}, {-6, 0, 9, 6}, {-3, 0, -9, -4} }; Matrix a_inverted = { {-0.15385, -0.15385, -0.28205, -0.53846}, {-0.07692, 0.12308, 0.02564, 0.03077}, { 0.35897, 0.35897, 0.43590, 0.92308}, {-0.69231, -0.69231, -0.76923, -1.92308} }; THEN("inverse(A) is the following matrix") { REQUIRE(A.inverse() == a_inverted); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Calculating the inverse of third matrix", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { { 9, 3, 0, 9}, {-5, -2, -6, -3}, {-4, 9, 6, 4}, {-7, 6, 6, 2} }; Matrix a_inverted = { {-0.04074, -0.07778, 0.14444, -0.22222}, {-0.07778, 0.03333, 0.36667, -0.33333}, {-0.02901, -0.14630, -0.10926, 0.12963}, { 0.17778, 0.06667, -0.26667, 0.33333} }; THEN("inverse(A) is the following matrix") { REQUIRE(A.inverse() == a_inverted); } } } /* ------------------------------------------------------------------------- */ SCENARIO("Multiplying a product by its inverse", "[features/matrices.feature]") { GIVEN("the following 4x4 matrix A") { Matrix A = { { 3, -9, 7, 3}, { 3, -8, 2, -9}, {-4, 4, 4, 1}, {-6, 5, -1, 1} }; AND_GIVEN("the following 4x4 matrix B") { Matrix B = { {8, 2, 2, 2}, {3, -1, 7, 0}, {7, 0, 5, 4}, {6, -2, 0, 5} }; AND_GIVEN("C <- A * B") { Matrix C = A * B; THEN("C * inverse(B) = A") { REQUIRE(C * B.inverse() == A); } } } } }