Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee4b9ae91 | ||
|
|
514bd649c1 | ||
|
|
0621ca86e1 | ||
|
|
9f2a41e6f3 | ||
|
|
95c7616646 | ||
|
|
c4c216647d | ||
|
|
fac2212661 | ||
|
|
a9321b5051 | ||
|
|
f3678992c5 |
@@ -6,18 +6,19 @@ project(DoRayMe)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
# LodePNG don't make a .a or .so, so let's build a library here
|
||||||
|
add_library(LodePNG STATIC)
|
||||||
|
set(LODEPNG_INCLUDE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/external/lodepng)
|
||||||
|
target_sources(LodePNG PRIVATE external/lodepng/lodepng.cpp external/lodepng/lodepng.h)
|
||||||
|
|
||||||
#Add external projects that directly need to be builded
|
|
||||||
ExternalProject_Add(googletest
|
|
||||||
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/googletest"
|
|
||||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/googletest"
|
|
||||||
CONFIGURE_COMMAND ""
|
|
||||||
BUILD_COMMAND ""
|
|
||||||
INSTALL_COMMAND ""
|
|
||||||
TEST_COMMAND ""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Main app
|
# Main app
|
||||||
add_subdirectory(source)
|
add_subdirectory(source)
|
||||||
# Unit Tests
|
|
||||||
|
option(PACKAGE_TESTS "Build the tests" ON)
|
||||||
|
if(PACKAGE_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
include(GoogleTest)
|
||||||
|
add_subdirectory("${PROJECT_SOURCE_DIR}/external/googletest" "external/googletest")
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
# To simplify testing, the app is build in two passes,
|
# To simplify testing, the app is build in two passes,
|
||||||
|
|
||||||
# First most is build as a library
|
# First most is build as a library
|
||||||
add_library(rayonnement STATIC math_helper.cpp)
|
add_library(rayonnement STATIC)
|
||||||
|
|
||||||
set(RAY_HEADERS include/tuples.h include/math_helper.h)
|
set(RAY_HEADERS include/tuples.h include/math_helper.h include/colour.h include/canvas.h include/matrix.h include/transformation.h)
|
||||||
set(RAY_SOURCES tuples.cpp)
|
set(RAY_SOURCES tuples.cpp math_helper.cpp colour.cpp canvas.cpp matrix.cpp transformation.cpp)
|
||||||
|
|
||||||
target_include_directories(rayonnement PUBLIC include)
|
target_include_directories(rayonnement PUBLIC include)
|
||||||
target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES})
|
target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES})
|
||||||
|
target_link_libraries(rayonnement LodePNG)
|
||||||
|
|
||||||
# Second we build the main executable
|
# Second we build the main executable
|
||||||
add_executable(dorayme main.cpp)
|
add_executable(dorayme main.cpp)
|
||||||
target_include_directories(rayonnement PUBLIC include)
|
target_include_directories(rayonnement PUBLIC include ${LODEPNG_INCLUDE_FOLDER})
|
||||||
target_link_libraries(dorayme rayonnement)
|
target_link_libraries(dorayme rayonnement)
|
||||||
57
source/canvas.cpp
Normal file
57
source/canvas.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Canvas implementation
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <canvas.h>
|
||||||
|
#include <lodepng.h>
|
||||||
|
|
||||||
|
#define BPP (24)
|
||||||
|
#define BytePP (BPP / 8)
|
||||||
|
|
||||||
|
#define MIN(_a, _b) ((_a)<(_b)?(_a):(_b))
|
||||||
|
#define MAX(_a, _b) ((_a)>(_b)?(_a):(_b))
|
||||||
|
|
||||||
|
Canvas::Canvas(uint32_t width, uint32_t height) : width(width), height(height)
|
||||||
|
{
|
||||||
|
this->bitmap = (uint8_t *)calloc(4, width * height);
|
||||||
|
this->stride = BytePP * width;
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas::~Canvas()
|
||||||
|
{
|
||||||
|
if (this->bitmap != nullptr)
|
||||||
|
{
|
||||||
|
free(this->bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Canvas::put_pixel(uint32_t x, uint32_t y, Colour c)
|
||||||
|
{
|
||||||
|
uint32_t offset = y * this->stride + x * BytePP;
|
||||||
|
this->bitmap[offset + 0] = MAX(MIN(c.red() * 255, 255), 0);
|
||||||
|
this->bitmap[offset + 1] = MAX(MIN(c.green() * 255, 255), 0);
|
||||||
|
this->bitmap[offset + 2] = MAX(MIN(c.blue() * 255, 255), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Colour Canvas::get_pixel(uint32_t x, uint32_t y)
|
||||||
|
{
|
||||||
|
uint32_t offset = y * this->stride + x * BytePP;
|
||||||
|
return Colour(this->bitmap[offset + 0] / 255, this->bitmap[offset + 1] / 255, this->bitmap[offset + 2] / 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Canvas::SaveAsPNG(const char *filename)
|
||||||
|
{
|
||||||
|
uint32_t ret = lodepng_encode24_file(filename, this->bitmap, this->width, this->height);
|
||||||
|
|
||||||
|
if (ret > 0)
|
||||||
|
{
|
||||||
|
printf("lodepng_encode_file returned %d!\n", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
10
source/colour.cpp
Normal file
10
source/colour.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Colour implementation
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "colour.h"
|
||||||
|
|
||||||
32
source/include/canvas.h
Normal file
32
source/include/canvas.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Canvas header
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef DORAYME_CANVAS_H
|
||||||
|
#define DORAYME_CANVAS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <colour.h>
|
||||||
|
|
||||||
|
class Canvas
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint8_t *bitmap;
|
||||||
|
uint32_t stride;
|
||||||
|
public:
|
||||||
|
uint32_t width, height;
|
||||||
|
|
||||||
|
Canvas(uint32_t width, uint32_t height);
|
||||||
|
~Canvas();
|
||||||
|
|
||||||
|
void put_pixel(uint32_t x, uint32_t y, Colour c);
|
||||||
|
Colour get_pixel(uint32_t x, uint32_t y);
|
||||||
|
|
||||||
|
bool SaveAsPNG(const char *filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DORAYME_CANVAS_H */
|
||||||
32
source/include/colour.h
Normal file
32
source/include/colour.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Colour header
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef DORAYME_COLOUR_H
|
||||||
|
#define DORAYME_COLOUR_H
|
||||||
|
|
||||||
|
#include <tuples.h>
|
||||||
|
|
||||||
|
class Colour : public Tuple
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Colour(double red, double green, double blue) : Tuple(red, green, blue, 0) {};
|
||||||
|
double red() { return this->x; };
|
||||||
|
double green() { return this->y; };
|
||||||
|
double blue() { return this->z; };
|
||||||
|
double red(double v) { this->x = v; return v; };
|
||||||
|
double green(double v) { this->y = v; return v; };
|
||||||
|
double blue(double v) { this->z = v; return v; };
|
||||||
|
|
||||||
|
using Tuple::operator*;
|
||||||
|
Colour operator*(const Colour &b) const { return Colour(this->x * b.x,
|
||||||
|
this->y * b.y,
|
||||||
|
this->z * b.z); };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* DORAYME_COLOUR_H */
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#ifndef DORAYME_MATH_HELPER_H
|
#ifndef DORAYME_MATH_HELPER_H
|
||||||
#define DORAYME_MATH_HELPER_H
|
#define DORAYME_MATH_HELPER_H
|
||||||
|
|
||||||
|
void set_equal_precision(double v);
|
||||||
bool double_equal(double a, double b);
|
bool double_equal(double a, double b);
|
||||||
|
|
||||||
#endif //DORAYME_MATH_HELPER_H
|
#endif //DORAYME_MATH_HELPER_H
|
||||||
|
|||||||
67
source/include/matrix.h
Normal file
67
source/include/matrix.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 <tuples.h>
|
||||||
|
|
||||||
|
class Matrix
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
/* 4x4 is the default */
|
||||||
|
double data[4*4];
|
||||||
|
int size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Matrix(int size);
|
||||||
|
Matrix(double values[], int size);
|
||||||
|
|
||||||
|
double get(int x, int y) const { return this->data[this->size * x + y]; };
|
||||||
|
void set(int x, int y, double v) { this->data[this->size * x + y] = v; };
|
||||||
|
|
||||||
|
Matrix identity();
|
||||||
|
Matrix transpose();
|
||||||
|
double determinant();
|
||||||
|
Matrix submatrix(int row, int column);
|
||||||
|
Matrix inverse();
|
||||||
|
double minor(int row, int column) { return this->submatrix(row, column).determinant(); }
|
||||||
|
double cofactor(int row, int column) { return (((column+row)&1)?-1:1) * this->minor(row, column); }
|
||||||
|
bool operator==(const Matrix &b) const;
|
||||||
|
bool operator!=(const Matrix &b) const;
|
||||||
|
|
||||||
|
bool isInvertible() { return this->determinant() != 0; }
|
||||||
|
|
||||||
|
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 Matrix3 : public Matrix
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Matrix3() : Matrix(3) { };
|
||||||
|
Matrix3(double values[]) : Matrix(values, 3) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Matrix2 : public Matrix
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using Matrix::data;
|
||||||
|
public:
|
||||||
|
Matrix2() : Matrix(2) { };
|
||||||
|
Matrix2(double values[]) : Matrix(values, 2) { };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DORAYME_MATRIX_H */
|
||||||
24
source/include/transformation.h
Normal file
24
source/include/transformation.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Transformation header
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef DORAYME_TRANSFORMATION_H
|
||||||
|
#define DORAYME_TRANSFORMATION_H
|
||||||
|
|
||||||
|
#include <matrix.h>
|
||||||
|
|
||||||
|
Matrix translation(double x, double y, double z);
|
||||||
|
|
||||||
|
Matrix scaling(double x, double y, double z);
|
||||||
|
|
||||||
|
Matrix rotation_x(double angle);
|
||||||
|
Matrix rotation_y(double angle);
|
||||||
|
Matrix rotation_z(double angle);
|
||||||
|
|
||||||
|
Matrix shearing(double Xy, double Xx, double Yx, double Yz, double Zx, double Zy);
|
||||||
|
|
||||||
|
#endif /* DORAYME_TRANSFORMATION_H */
|
||||||
@@ -11,7 +11,14 @@
|
|||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <math_helper.h>
|
#include <math_helper.h>
|
||||||
|
|
||||||
|
static double current_precision = FLT_EPSILON;
|
||||||
|
|
||||||
|
void set_equal_precision(double v)
|
||||||
|
{
|
||||||
|
current_precision = v;
|
||||||
|
}
|
||||||
|
|
||||||
bool double_equal(double a, double b)
|
bool double_equal(double a, double b)
|
||||||
{
|
{
|
||||||
return fabs(a - b) < DBL_EPSILON;
|
return fabs(a - b) < current_precision;
|
||||||
}
|
}
|
||||||
|
|||||||
201
source/matrix.cpp
Normal file
201
source/matrix.cpp
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Matrix implementation
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <matrix.h>
|
||||||
|
#include <tuples.h>
|
||||||
|
#include <math_helper.h>
|
||||||
|
|
||||||
|
Matrix::Matrix(int width)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
this->size = width;
|
||||||
|
|
||||||
|
for(i = 0; i < width*width; i++)
|
||||||
|
{
|
||||||
|
this->data[i] = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Matrix::Matrix(double values[], int width)
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
this->size = width;
|
||||||
|
|
||||||
|
for(y = 0; y < this->size; y++)
|
||||||
|
{
|
||||||
|
for (x = 0 ; x < this->size ; x++)
|
||||||
|
{
|
||||||
|
this->data[this->size * x + y] = values[this->size * x + y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Matrix::operator==(const Matrix &b) const
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (this->size != b.size)
|
||||||
|
{
|
||||||
|
/* If they are not the same size don't even bother */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < this->size*this->size; 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->size != b.size)
|
||||||
|
{
|
||||||
|
/* If they are not the same size don't even bother */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < this->size*this->size; 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->size);
|
||||||
|
|
||||||
|
if (this->size == b.size)
|
||||||
|
{
|
||||||
|
for (y = 0 ; y < this->size ; y++)
|
||||||
|
{
|
||||||
|
for (x = 0 ; x < this->size ; x++)
|
||||||
|
{
|
||||||
|
double v = 0;
|
||||||
|
for (k = 0 ; k < this->size ; 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix Matrix::identity()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < this->size; i++)
|
||||||
|
{
|
||||||
|
this->set(i, i, 1);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix Matrix::transpose()
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
Matrix ret = Matrix(this->size);
|
||||||
|
for (y = 0 ; y < this->size ; y++)
|
||||||
|
{
|
||||||
|
for (x = 0 ; x < this->size ; x++)
|
||||||
|
{
|
||||||
|
ret.set(y, x, this->get(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix Matrix::submatrix(int row, int column)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
int x = 0, y = 0;
|
||||||
|
Matrix ret = Matrix(this->size - 1);
|
||||||
|
for (i = 0 ; i < this->size ; i++)
|
||||||
|
{
|
||||||
|
if (i == row)
|
||||||
|
{
|
||||||
|
/* Skip that row */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
y = 0;
|
||||||
|
for (j = 0 ; j < this->size ; j++)
|
||||||
|
{
|
||||||
|
if (j == column)
|
||||||
|
{
|
||||||
|
/* skip that column */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret.set(x, y, this->get(i, j));
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Matrix::determinant()
|
||||||
|
{
|
||||||
|
double det = 0;
|
||||||
|
if (this->size == 2)
|
||||||
|
{
|
||||||
|
det = this->data[0] * this->data[3] - this->data[1] * this->data[2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int col;
|
||||||
|
for(col = 0; col < this->size; col++)
|
||||||
|
{
|
||||||
|
det = det + this->get(0, col) * this->cofactor(0, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return det;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix Matrix::inverse()
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix(this->size);
|
||||||
|
|
||||||
|
if (this->isInvertible())
|
||||||
|
{
|
||||||
|
int row, col;
|
||||||
|
double c;
|
||||||
|
for (row = 0; row < this->size; row++)
|
||||||
|
{
|
||||||
|
for (col = 0; col < this->size; col++)
|
||||||
|
{
|
||||||
|
c = this->cofactor(row, col);
|
||||||
|
ret.set(col, row, c / this->determinant());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
84
source/transformation.cpp
Normal file
84
source/transformation.cpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Transformation implementation
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <transformation.h>
|
||||||
|
|
||||||
|
Matrix translation(double x, double y, double z)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4().identity();
|
||||||
|
|
||||||
|
ret.set(0, 3, x);
|
||||||
|
ret.set(1, 3, y);
|
||||||
|
ret.set(2, 3, z);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix scaling(double x, double y, double z)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4();
|
||||||
|
|
||||||
|
ret.set(0, 0, x);
|
||||||
|
ret.set(1, 1, y);
|
||||||
|
ret.set(2, 2, z);
|
||||||
|
ret.set(3, 3, 1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix rotation_x(double angle)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4().identity();
|
||||||
|
|
||||||
|
ret.set(1, 1, cos(angle));
|
||||||
|
ret.set(1, 2, -sin(angle));
|
||||||
|
ret.set(2, 1, sin(angle));
|
||||||
|
ret.set(2, 2, cos(angle));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix rotation_y(double angle)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4().identity();
|
||||||
|
|
||||||
|
ret.set(0, 0, cos(angle));
|
||||||
|
ret.set(0, 2, sin(angle));
|
||||||
|
ret.set(2, 0, -sin(angle));
|
||||||
|
ret.set(2, 2, cos(angle));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix rotation_z(double angle)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4().identity();
|
||||||
|
|
||||||
|
ret.set(0, 0, cos(angle));
|
||||||
|
ret.set(0, 1, -sin(angle));
|
||||||
|
ret.set(1, 0, sin(angle));
|
||||||
|
ret.set(1, 1, cos(angle));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix shearing(double Xy, double Xz, double Yx, double Yz, double Zx, double Zy)
|
||||||
|
{
|
||||||
|
Matrix ret = Matrix4().identity();
|
||||||
|
|
||||||
|
ret.set(0, 1, Xy);
|
||||||
|
ret.set(0, 2, Xz);
|
||||||
|
ret.set(1, 0, Yx);
|
||||||
|
ret.set(1, 2, Yz);
|
||||||
|
ret.set(2, 0, Zx);
|
||||||
|
ret.set(2, 1, Zy);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -3,10 +3,15 @@ project(DoRayTested)
|
|||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
set(TESTS_SRC tuples_test.cpp)
|
set(TESTS_SRC tuples_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp)
|
||||||
|
|
||||||
add_executable(testMyRays)
|
add_executable(testMyRays)
|
||||||
target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
|
target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
|
||||||
target_include_directories(testMyRays PUBLIC ../source/include)
|
target_include_directories(testMyRays PUBLIC ../source/include)
|
||||||
target_sources(testMyRays PRIVATE ${TESTS_SRC})
|
target_sources(testMyRays PRIVATE ${TESTS_SRC})
|
||||||
target_link_libraries(testMyRays gtest gtest_main rayonnement Threads::Threads)
|
target_link_libraries(testMyRays gtest gtest_main rayonnement Threads::Threads)
|
||||||
|
|
||||||
|
gtest_discover_tests(testMyRays
|
||||||
|
WORKING_DIRECTORY ${PROJECT_DIR}
|
||||||
|
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
|
||||||
|
)
|
||||||
53
tests/canvas_test.cpp
Normal file
53
tests/canvas_test.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Canvas unit tests
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <colour.h>
|
||||||
|
#include <canvas.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(CanvasTest, Creating_a_canvas)
|
||||||
|
{
|
||||||
|
Canvas c = Canvas(10, 20);
|
||||||
|
int x, y;
|
||||||
|
ASSERT_EQ(c.width, 10);
|
||||||
|
ASSERT_EQ(c.height, 20);
|
||||||
|
for(y = 0; y < 20; y++)
|
||||||
|
{
|
||||||
|
for(x = 0; x < 10; x++)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(c.get_pixel(x, y), Colour(0, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CanvasTest, Test_Writing_pixels_to_a_canvas_Test)
|
||||||
|
{
|
||||||
|
Canvas c = Canvas(10, 20);
|
||||||
|
Colour red = Colour(1, 0, 0);
|
||||||
|
|
||||||
|
c.put_pixel(2, 3, red);
|
||||||
|
|
||||||
|
ASSERT_EQ(c.get_pixel(2, 3), red);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CanvasTest, Save_a_PNG_file)
|
||||||
|
{
|
||||||
|
Canvas c = Canvas(5, 3);
|
||||||
|
Colour c1 = Colour(1.5, 0, 0);
|
||||||
|
Colour c2 = Colour(0, 0.5, 0);
|
||||||
|
Colour c3 = Colour(-0.5, 0, 1);
|
||||||
|
|
||||||
|
c.put_pixel(0, 0, c1);
|
||||||
|
c.put_pixel(2, 1, c2);
|
||||||
|
c.put_pixel(4, 2, c3);
|
||||||
|
|
||||||
|
ASSERT_TRUE(c.SaveAsPNG("Save_a_PNG_file.png"));
|
||||||
|
|
||||||
|
}
|
||||||
51
tests/colour_test.cpp
Normal file
51
tests/colour_test.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Colour unit tests
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <colour.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(ColourTest, Color_is_tuple)
|
||||||
|
{
|
||||||
|
Colour c = Colour(-0.5, 0.4, 1.7);
|
||||||
|
|
||||||
|
ASSERT_EQ(c.red(), -0.5);
|
||||||
|
ASSERT_EQ(c.green(), 0.4);
|
||||||
|
ASSERT_EQ(c.blue(), 1.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ColourTest, Adding_colours)
|
||||||
|
{
|
||||||
|
Colour c1 = Colour(0.9, 0.6, 0.75);
|
||||||
|
Colour c2 = Colour(0.7, 0.1, 0.25);
|
||||||
|
|
||||||
|
ASSERT_EQ(c1 + c2, Colour(1.6, 0.7, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ColourTest, Substracting_colours)
|
||||||
|
{
|
||||||
|
Colour c1 = Colour(0.9, 0.6, 0.75);
|
||||||
|
Colour c2 = Colour(0.7, 0.1, 0.25);
|
||||||
|
|
||||||
|
ASSERT_EQ(c1 - c2, Colour(0.2, 0.5, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ColourTest, Multiplying_colour_by_a_scalar)
|
||||||
|
{
|
||||||
|
Colour c = Colour(0.2, 0.3, 0.4);
|
||||||
|
|
||||||
|
ASSERT_EQ(c * 2, Colour(0.4, 0.6, 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ColourTest, Multiplying_colours)
|
||||||
|
{
|
||||||
|
Colour c1 = Colour(1, 0.2, 0.4);
|
||||||
|
Colour c2 = Colour(0.9, 1, 0.1);
|
||||||
|
|
||||||
|
ASSERT_EQ(c1 * c2, Colour(0.9, 0.2, 0.04));
|
||||||
|
}
|
||||||
390
tests/matrix_test.cpp
Normal file
390
tests/matrix_test.cpp
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Matric unit tests
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <matrix.h>
|
||||||
|
#include <tuples.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_determinant_of_a_2x2_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 1, 5,
|
||||||
|
-3, 2 };
|
||||||
|
Matrix2 A = Matrix2(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.determinant(), 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, A_submatrix_of_a_3x3_matrix_is_a_2x2_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 1, 5, 0,
|
||||||
|
-3, 2, 7,
|
||||||
|
0, 6, -3 };
|
||||||
|
double results[] = { -3, 2,
|
||||||
|
0, 6 };
|
||||||
|
Matrix3 A = Matrix3(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.submatrix(0, 2), Matrix2(results));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, A_submatrix_of_a_4x4_matrix_is_a_3x3_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { -6, 1, 1, 6,
|
||||||
|
-8, 5, 8, 6,
|
||||||
|
-1, 0, 8, 2,
|
||||||
|
-7, 1, -1, 1 };
|
||||||
|
double results[] = { -6, 1, 6,
|
||||||
|
-8, 8, 6,
|
||||||
|
-7,-1, 1 };
|
||||||
|
Matrix4 A = Matrix4(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.submatrix(2, 1), Matrix3(results));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculate_a_minor_of_a_3x3_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 3, 5, 0,
|
||||||
|
2, -1, -7,
|
||||||
|
6, -1, 5 };
|
||||||
|
|
||||||
|
Matrix3 A = Matrix3(valuesA);
|
||||||
|
|
||||||
|
Matrix B = A.submatrix(1, 0);
|
||||||
|
|
||||||
|
ASSERT_EQ(B.determinant(), 25);
|
||||||
|
ASSERT_EQ(A.minor(1, 0), 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_a_cofactor_of_a_3x3_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 3, 5, 0,
|
||||||
|
2, -1, -7,
|
||||||
|
6, -1, 5 };
|
||||||
|
|
||||||
|
Matrix3 A = Matrix3(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.minor(0, 0), -12);
|
||||||
|
ASSERT_EQ(A.cofactor(0, 0), -12);
|
||||||
|
ASSERT_EQ(A.minor(1, 0), 25);
|
||||||
|
ASSERT_EQ(A.cofactor(1, 0), -25);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_determinant_of_a_3x3_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 1, 2, 6,
|
||||||
|
-5, 8, -4,
|
||||||
|
2, 6, 4 };
|
||||||
|
|
||||||
|
Matrix A = Matrix3(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.cofactor(0, 0), 56);
|
||||||
|
ASSERT_EQ(A.cofactor(0, 1), 12);
|
||||||
|
ASSERT_EQ(A.minor(0, 2), -46);
|
||||||
|
ASSERT_EQ(A.determinant(), -196);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_determinant_of_a_4x4_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { -2, -8, 3, 5,
|
||||||
|
-3, 1, 7, 3,
|
||||||
|
1, 2, -9, 6,
|
||||||
|
-6, 7, 7, -9 };
|
||||||
|
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.cofactor(0, 0), 690);
|
||||||
|
ASSERT_EQ(A.cofactor(0, 1), 447);
|
||||||
|
ASSERT_EQ(A.cofactor(0, 2), 210);
|
||||||
|
ASSERT_EQ(A.minor(0, 3), -51);
|
||||||
|
ASSERT_EQ(A.determinant(), -4071);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Testing_an_invertible_matrix_for_invertibility)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 6, 4, 4, 4,
|
||||||
|
5, 5, 7, 6,
|
||||||
|
4, -9, 3, -7,
|
||||||
|
9, 1, 7, -6 };
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.determinant(), -2120);
|
||||||
|
ASSERT_TRUE(A.isInvertible());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Testing_an_noninvertible_matrix_for_invertibility)
|
||||||
|
{
|
||||||
|
double valuesA[] = { -4, 2, -2, -3,
|
||||||
|
9, 6, 2, 6,
|
||||||
|
0, -5, 1, -5,
|
||||||
|
0, 0, 0, 0 };
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
|
||||||
|
ASSERT_EQ(A.determinant(), 0);
|
||||||
|
ASSERT_FALSE(A.isInvertible());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_inverse_of_a_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { -5, 2, 6, -8,
|
||||||
|
1, -5, 1, 8,
|
||||||
|
7, 7, -6, -7,
|
||||||
|
1, -3, 7, 4 };
|
||||||
|
|
||||||
|
double results[] = { 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 };
|
||||||
|
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
Matrix B = A.inverse();
|
||||||
|
|
||||||
|
ASSERT_EQ(A.determinant(), 532);
|
||||||
|
ASSERT_EQ(A.cofactor(2, 3), -160);
|
||||||
|
ASSERT_NEAR(B.get(3, 2), -160./532., DBL_EPSILON);
|
||||||
|
ASSERT_EQ(A.cofactor(3, 2), 105);
|
||||||
|
ASSERT_NEAR(B.get(2, 3), 105./532., DBL_EPSILON);
|
||||||
|
|
||||||
|
/* Temporary lower the precision */
|
||||||
|
set_equal_precision(0.00001);
|
||||||
|
|
||||||
|
ASSERT_EQ(B, Matrix4(results));
|
||||||
|
|
||||||
|
/* Revert to default */
|
||||||
|
set_equal_precision(FLT_EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_inverse_of_another_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 8, -5, 9, 2,
|
||||||
|
7, 5, 6, 1,
|
||||||
|
-6, 0, 9, 6,
|
||||||
|
-3, 0, -9, -4 };
|
||||||
|
|
||||||
|
double results[] = { -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 };
|
||||||
|
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
Matrix B = A.inverse();
|
||||||
|
|
||||||
|
|
||||||
|
/* Temporary lower the precision */
|
||||||
|
set_equal_precision(0.00001);
|
||||||
|
|
||||||
|
ASSERT_EQ(B, Matrix4(results));
|
||||||
|
|
||||||
|
/* Revert to default */
|
||||||
|
set_equal_precision(FLT_EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Calculating_the_inverse_of_third_matrix)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 9, 3, 0, 9,
|
||||||
|
-5, -2, -6, -3,
|
||||||
|
-4, 9, 6, 4,
|
||||||
|
-7, 6, 6, 2 };
|
||||||
|
|
||||||
|
double results[] = { -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 };
|
||||||
|
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
Matrix B = A.inverse();
|
||||||
|
|
||||||
|
|
||||||
|
/* Temporary lower the precision */
|
||||||
|
set_equal_precision(0.00001);
|
||||||
|
|
||||||
|
ASSERT_EQ(B, Matrix4(results));
|
||||||
|
|
||||||
|
/* Revert to default */
|
||||||
|
set_equal_precision(FLT_EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Multiplying_a_product_by_its_inverse)
|
||||||
|
{
|
||||||
|
double valuesA[] = { 3, -9, 7, 3,
|
||||||
|
3, -8, 2, -9,
|
||||||
|
-4, 4, 4, 1,
|
||||||
|
-6, 5, -1, 1 };
|
||||||
|
|
||||||
|
double valuesB[] = { 8, 2, 2, 2,
|
||||||
|
3, -1, 7, 0,
|
||||||
|
7, 0, 5, 4,
|
||||||
|
6, -2, 0, 5 };
|
||||||
|
|
||||||
|
Matrix A = Matrix4(valuesA);
|
||||||
|
Matrix B = Matrix4(valuesB);
|
||||||
|
|
||||||
|
Matrix C = A * B;
|
||||||
|
|
||||||
|
ASSERT_EQ(C * B.inverse(), A);
|
||||||
|
}
|
||||||
191
tests/transformation_test.cpp
Normal file
191
tests/transformation_test.cpp
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
|
* Transformations unit tests
|
||||||
|
*
|
||||||
|
* Created by Manoël Trapier
|
||||||
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <transformation.h>
|
||||||
|
#include <tuples.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(TransformationTest, Multiplying_by_a_translation_matrix)
|
||||||
|
{
|
||||||
|
Matrix transform = translation(5, -3, 2);
|
||||||
|
Point p = Point(-3, 4, 5);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(2, 1, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Multiplying_by_the_inverse_of_a_translation_matrix)
|
||||||
|
{
|
||||||
|
Matrix transform = translation(5, -3, 2);
|
||||||
|
Matrix inv = transform.inverse();
|
||||||
|
|
||||||
|
Point p = Point(-3, 4, 5);
|
||||||
|
|
||||||
|
ASSERT_EQ(inv * p, Point(-8, 7, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Translation_does_not_affect_vectors)
|
||||||
|
{
|
||||||
|
Matrix transform = translation(5, -3, 2);
|
||||||
|
|
||||||
|
Vector v = Vector(-3, 4, 5);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * v, Vector(-3, 4, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_scaling_matrix_applied_to_a_point)
|
||||||
|
{
|
||||||
|
Matrix transform = scaling(2, 3, 4);
|
||||||
|
|
||||||
|
Point p = Point(-4, 6, 8);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(-8, 18, 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_scaling_matrix_applied_to_a_vector)
|
||||||
|
{
|
||||||
|
Matrix transform = scaling(2, 3, 4);
|
||||||
|
|
||||||
|
Vector v = Vector(-4, 6, 8);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * v, Vector(-8, 18, 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Multiplaying_by_the_inverse_of_a_scaling_matrix)
|
||||||
|
{
|
||||||
|
Matrix transform = scaling(2, 3, 4);
|
||||||
|
Matrix inv = transform.inverse();
|
||||||
|
|
||||||
|
Vector v = Vector(-4, 6, 8);
|
||||||
|
|
||||||
|
ASSERT_EQ(inv * v, Vector(-2, 2, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Reflexion_is_scaling_by_a_negative_value)
|
||||||
|
{
|
||||||
|
Matrix transform = scaling(-1, 1, 1);
|
||||||
|
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(-2, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Rotating_a_point_around_the_X_axis)
|
||||||
|
{
|
||||||
|
Point p = Point(0, 1, 0);
|
||||||
|
Matrix half_quarter = rotation_x(M_PI / 4.);
|
||||||
|
Matrix full_quarter = rotation_x(M_PI / 2.);
|
||||||
|
|
||||||
|
ASSERT_EQ(half_quarter * p, Point(0, sqrt(2)/2, sqrt(2)/2));
|
||||||
|
ASSERT_EQ(full_quarter * p, Point(0, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, The_inverse_of_an_x_rotation_rotates_in_the_opposite_direction)
|
||||||
|
{
|
||||||
|
Point p = Point(0, 1, 0);
|
||||||
|
Matrix half_quarter = rotation_x(M_PI / 4.);
|
||||||
|
Matrix inv = half_quarter.inverse();
|
||||||
|
|
||||||
|
ASSERT_EQ(inv * p, Point(0, sqrt(2)/2, -sqrt(2)/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Rotating_a_point_around_the_Y_axis)
|
||||||
|
{
|
||||||
|
Point p = Point(0, 0, 1);
|
||||||
|
Matrix half_quarter = rotation_y(M_PI / 4.);
|
||||||
|
Matrix full_quarter = rotation_y(M_PI / 2.);
|
||||||
|
|
||||||
|
ASSERT_EQ(half_quarter * p, Point(sqrt(2)/2, 0, sqrt(2)/2));
|
||||||
|
ASSERT_EQ(full_quarter * p, Point(1, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Rotating_a_point_around_the_Z_axis)
|
||||||
|
{
|
||||||
|
Point p = Point(0, 1, 0);
|
||||||
|
Matrix half_quarter = rotation_z(M_PI / 4.);
|
||||||
|
Matrix full_quarter = rotation_z(M_PI / 2.);
|
||||||
|
|
||||||
|
ASSERT_EQ(half_quarter * p, Point(-sqrt(2)/2, sqrt(2)/2, 0));
|
||||||
|
ASSERT_EQ(full_quarter * p, Point(-1, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_x_in_proportion_to_y)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(1, 0, 0, 0, 0, 0);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(5, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_x_in_proportion_to_z)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(0, 1, 0, 0, 0, 0);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(6, 3, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_y_in_proportion_to_x)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(0, 0, 1, 0, 0, 0);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(2, 5, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_y_in_proportion_to_z)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(0, 0, 0, 1, 0, 0);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(2, 7, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_z_in_proportion_to_x)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(0, 0, 0, 0, 1, 0);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(2, 3, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, A_shearing_transformation_moves_z_in_proportion_to_y)
|
||||||
|
{
|
||||||
|
Matrix transform = shearing(0, 0, 0, 0, 0, 1);
|
||||||
|
Point p = Point(2, 3, 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(transform * p, Point(2, 3, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Individual_trnasformations_are_applied_in_sequence)
|
||||||
|
{
|
||||||
|
Point p = Point(1, 0, 1);
|
||||||
|
Matrix A = rotation_x(M_PI / 2.);
|
||||||
|
Matrix B = scaling(5, 5, 5);
|
||||||
|
Matrix C = translation(10, 5, 7);
|
||||||
|
|
||||||
|
Tuple p2 = A * p;
|
||||||
|
ASSERT_EQ(p2, Point(1, -1, 0));
|
||||||
|
|
||||||
|
Tuple p3 = B * p2;
|
||||||
|
ASSERT_EQ(p3, Point(5, -5, 0));
|
||||||
|
|
||||||
|
Tuple p4 = C * p3;
|
||||||
|
ASSERT_EQ(p4, Point(15, 0, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationTest, Chained_transformation_must_be_applied_in_reverse_order)
|
||||||
|
{
|
||||||
|
Point p = Point(1, 0, 1);
|
||||||
|
Matrix A = rotation_x(M_PI / 2.);
|
||||||
|
Matrix B = scaling(5, 5, 5);
|
||||||
|
Matrix C = translation(10, 5, 7);
|
||||||
|
|
||||||
|
Matrix T = C * B * A;
|
||||||
|
ASSERT_EQ(T * p, Point(15, 0, 7));
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* DoRayMe - a quick and dirty Raytracer
|
* DoRayMe - a quick and dirty Raytracer
|
||||||
* Tuples tests
|
* Tuples unit tests
|
||||||
*
|
*
|
||||||
* Created by Manoël Trapier
|
* Created by Manoël Trapier
|
||||||
* Copyright (c) 2020 986-Studio.
|
* Copyright (c) 2020 986-Studio.
|
||||||
|
|||||||
Reference in New Issue
Block a user