diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 79757f0..e384554 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -3,8 +3,8 @@ # First most is build as a library add_library(rayonnement STATIC) -set(RAY_HEADERS include/tuples.h include/math_helper.h include/colour.h include/canvas.h) -set(RAY_SOURCES tuples.cpp math_helper.cpp colour.cpp canvas.cpp) +set(RAY_HEADERS include/tuples.h include/math_helper.h include/colour.h include/canvas.h include/matrix.h) +set(RAY_SOURCES tuples.cpp math_helper.cpp colour.cpp canvas.cpp matrix.cpp) target_include_directories(rayonnement PUBLIC include) target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES}) diff --git a/source/include/matrix.h b/source/include/matrix.h new file mode 100644 index 0000000..b71731e --- /dev/null +++ b/source/include/matrix.h @@ -0,0 +1,100 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Matrix header + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#ifndef DORAYME_MATRIX_H +#define DORAYME_MATRIX_H + +#include + +class Matrix +{ +private: + /* 4x4 is the default */ + double data[4*4]; + int width; + +public: + Matrix(int width) : width(width) + { + int i; + for(i = 0; i < width*width; i++) + { + this->data[i] = 0; + } + }; + Matrix(double values[], int width) + { + int x, y; + + this->width = width; + + for(y = 0; y < this->width; y++) + { + for (x = 0 ; x < this->width ; x++) + { + this->data[this->width * x + y] = values[this->width * x + y]; + } + } + }; + double get(int x, int y) const { return this->data[this->width * x + y]; }; + void set(int x, int y, double v) { this->data[this->width * x + y] = v; }; + + Matrix identity() + { + int i; + for(i = 0; i < this->width; i++) + { + this->set(i, i, 1); + } + return *this; + } + + Matrix transpose() + { + int x, y; + Matrix ret = Matrix(this->width); + for(y = 0; y < this->width; y++) + { + for (x = 0 ; x < this->width ; x++) + { + ret.set(y, x, this->get(x, y)); + } + } + return ret; + } + + bool operator==(const Matrix &b) const; + bool operator!=(const Matrix &b) const; + + Matrix operator*(const Matrix &b) const; + Tuple operator*(const Tuple &b) const; +}; + +class Matrix4: public Matrix +{ +public: + Matrix4() : Matrix(4) { }; + Matrix4(double values[]) : Matrix(values, 4) { }; +}; + + +class Matrix2 : public Matrix +{ +public: + Matrix2() : Matrix(2) { }; + Matrix2(double values[]) : Matrix(values, 2) { }; +}; + +class Matrix3 : public Matrix +{ +public: + Matrix3() : Matrix(3) { }; + Matrix3(double values[]) : Matrix(values, 3) { }; +}; + +#endif /* DORAYME_MATRIX_H */ diff --git a/source/matrix.cpp b/source/matrix.cpp new file mode 100644 index 0000000..fafaae1 --- /dev/null +++ b/source/matrix.cpp @@ -0,0 +1,82 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Matrix implementation + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ + +#include +#include +#include + +bool Matrix::operator==(const Matrix &b) const +{ + int i; + if (this->width != b.width) + { + /* If they are not the same size don't even bother */ + return false; + } + + for(i = 0; i < this->width*this->width; i++) + { + if (!double_equal(this->data[i], b.data[i])) + { + return false; + } + } + + return true; +} + +bool Matrix::operator!=(const Matrix &b) const +{ + int i; + if (this->width != b.width) + { + /* If they are not the same size don't even bother */ + return true; + } + + for(i = 0; i < this->width*this->width; i++) + { + if (!double_equal(this->data[i], b.data[i])) + { + return true; + } + } + return false; +} + +Matrix Matrix::operator*(const Matrix &b) const +{ + int x, y, k; + Matrix ret = Matrix(this->width); + + if (this->width == b.width) + { + for (y = 0 ; y < this->width ; y++) + { + for (x = 0 ; x < this->width ; x++) + { + double v = 0; + for (k = 0 ; k < this->width ; k++) + { + v += this->get(x, k) * b.get(k, y); + } + ret.set(x, y, v); + } + } + } + return ret; +} + +Tuple Matrix::operator*(const Tuple &b) const +{ + return Tuple(b.x * this->get(0, 0) + b.y * this->get(0, 1) + b.z * this->get(0, 2) + b.w * this->get(0, 3), + b.x * this->get(1, 0) + b.y * this->get(1, 1) + b.z * this->get(1, 2) + b.w * this->get(1, 3), + b.x * this->get(2, 0) + b.y * this->get(2, 1) + b.z * this->get(2, 2) + b.w * this->get(2, 3), + b.x * this->get(3, 0) + b.y * this->get(3, 1) + b.z * this->get(3, 2) + b.w * this->get(3, 3)); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fd7ebc7..aa5b12e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ project(DoRayTested) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -set(TESTS_SRC tuples_test.cpp colour_test.cpp canvas_test.cpp) +set(TESTS_SRC tuples_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp) add_executable(testMyRays) target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) diff --git a/tests/matrix_test.cpp b/tests/matrix_test.cpp new file mode 100644 index 0000000..cee15e6 --- /dev/null +++ b/tests/matrix_test.cpp @@ -0,0 +1,173 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Matric unit tests + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include + + +TEST(MatrixTest, Constructing_and_inspecting_a_4x4_Matrix) +{ + double values[] = {1, 2, 3, 4, + 5.5, 6.5, 7.5, 8.5, + 9, 10, 11, 12, + 13.5, 14.5, 15.5, 16.5}; + + Matrix4 m = Matrix4(values); + + ASSERT_EQ(m.get(0, 0), 1); + ASSERT_EQ(m.get(0, 3), 4); + ASSERT_EQ(m.get(1, 0), 5.5); + ASSERT_EQ(m.get(1, 2), 7.5); + ASSERT_EQ(m.get(2, 2), 11); + ASSERT_EQ(m.get(3, 0), 13.5); + ASSERT_EQ(m.get(3, 2), 15.5); +} + +TEST(MatrixTest, A_2x2_matric_ought_to_be_representable) +{ + double values[] = {-3, 5, + 1, -2}; + + Matrix2 m = Matrix2(values); + + ASSERT_EQ(m.get(0, 0), -3); + ASSERT_EQ(m.get(0, 1), 5); + ASSERT_EQ(m.get(1, 0), 1); + ASSERT_EQ(m.get(1, 1), -2); +} + +TEST(MatrixTest, A_3x3_matric_ought_to_be_representable) +{ + double values[] = {-3, 5, 0, + 1, -2, -7, + 0, 1, 1}; + + Matrix3 m = Matrix3(values); + + ASSERT_EQ(m.get(0, 0), -3); + ASSERT_EQ(m.get(1, 1), -2); + ASSERT_EQ(m.get(2, 2), 1); +} + +TEST(MatrixTest, Matrix_equality_with_identical_matrix) +{ + double values1[] = {1, 2, 3, 4, + 5, 6, 7, 8, + 9, 8, 7, 6, + 5, 4, 3, 2}; + + double values2[] = {1, 2, 3, 4, + 5, 6, 7, 8, + 9, 8, 7, 6, + 5, 4, 3, 2}; + Matrix4 A = Matrix4(values1); + Matrix4 B = Matrix4(values2); + + ASSERT_EQ(A, B); +} + +TEST(MatrixTest, Matrix_equality_with_different_matrix) +{ + double values1[] = {1, 2, 3, 4, + 5, 6, 7, 8, + 9, 8, 7, 6, + 5, 4, 3, 2}; + + double values2[] = {2, 3, 4, 5, + 6, 7, 8, 9, + 8, 7, 6, 5, + 4, 3, 2, 1}; + Matrix4 A = Matrix4(values1); + Matrix4 B = Matrix4(values2); + + ASSERT_NE(A, B); +} + +TEST(MatrixTest, Multiplying_two_matrices) +{ + double values1[] = {1, 2, 3, 4, + 5, 6, 7, 8, + 9, 8, 7, 6, + 5, 4, 3, 2}; + + double values2[] = {-2, 1, 2, 3, + 3, 2, 1, -1, + 4, 3, 6, 5, + 1, 2, 7, 8}; + + double results[] = {20, 22, 50, 48, + 44, 54, 114, 108, + 40, 58, 110, 102, + 16, 26, 46, 42}; + + Matrix4 A = Matrix4(values1); + Matrix4 B = Matrix4(values2); + + + ASSERT_EQ(A * B, Matrix4(results)); +} + +TEST(MatrixTest, A_matrix_multiplyed_by_a_tuple) +{ + double valuesA[] = {1, 2, 3, 4, + 2, 4, 4, 2, + 8, 6, 4, 1, + 0, 0, 0, 1}; + + Matrix4 A = Matrix4(valuesA); + Tuple b = Tuple(1, 2, 3, 1); + + ASSERT_EQ(A * b, Tuple(18, 24, 33, 1)); +} + +TEST(MatrixTest, Multiplying_a_matrix_by_the_identity_matrix) +{ + double valuesA[] = {0, 1, 2, 4, + 1, 2, 4, 8, + 2, 4, 8, 16, + 4, 8, 16, 32}; + + Matrix4 A = Matrix4(valuesA); + Matrix ident = Matrix4().identity(); + + ASSERT_EQ(A * ident, A); +} + +TEST(MatrixTest, Multiplying_the_identity_matrix_by_a_tuple) +{ + Tuple a = Tuple(1, 2, 3, 4); + Matrix ident = Matrix4().identity(); + + ASSERT_EQ(ident * a, a); +} + +TEST(MatrixTest, Transposing_a_matrix) +{ + double valuesA[] = {0, 9, 3, 0, + 9, 8, 0, 8, + 1, 8, 5, 3, + 0, 0, 5, 8}; + + double results[] = {0, 9, 1, 0, + 9, 8, 8, 0, + 3, 0, 5, 5, + 0, 8, 3, 8}; + + Matrix A = Matrix4(valuesA); + + ASSERT_EQ(A.transpose(), Matrix4(results)); +} + +TEST(MatrixTest, Transposing_this_identity_matrix) +{ + Matrix ident = Matrix4().identity(); + + ASSERT_EQ(ident.transpose(), ident); +} \ No newline at end of file