From 81e323fdf4afae77d632a32d3bbfde7ec3b8fe3f Mon Sep 17 00:00:00 2001 From: Godzil Date: Sat, 22 Feb 2020 17:30:15 +0000 Subject: [PATCH] Added CUBES! --- source/include/cube.h | 28 +++++ source/include/math_helper.h | 3 + source/include/shape.h | 2 + source/math_helper.cpp | 38 ++++++ source/shapes/cube.cpp | 76 ++++++++++++ tests/CMakeLists.txt | 12 +- tests/ch10_test.cpp | 2 +- tests/ch12_test.cpp | 213 ++++++++++++++++++++++++++++++++++ tests/cube_test.cpp | 117 +++++++++++++++++++ tests/math_test.cpp | 97 ++++++++++++++++ tests/transformation_test.cpp | 10 -- 11 files changed, 584 insertions(+), 14 deletions(-) create mode 100644 source/include/cube.h create mode 100644 source/shapes/cube.cpp create mode 100644 tests/ch12_test.cpp create mode 100644 tests/cube_test.cpp create mode 100644 tests/math_test.cpp diff --git a/source/include/cube.h b/source/include/cube.h new file mode 100644 index 0000000..03fcfb9 --- /dev/null +++ b/source/include/cube.h @@ -0,0 +1,28 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Cube header + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#ifndef DORAYME_CUBE_H +#define DORAYME_CUBE_H + +#include +#include +#include + +class Cube : public Shape { +private: + void checkAxis(double axeOrigine, double axeDirection, double *axeMin, double *axeMax); + + Intersect localIntersect(Ray r); + + Tuple localNormalAt(Tuple point); + +public: + Cube() : Shape(SHAPE_CUBE) {}; +}; + +#endif /* DORAYME_CUBE_H */ diff --git a/source/include/math_helper.h b/source/include/math_helper.h index ff2f0e3..887fbfd 100644 --- a/source/include/math_helper.h +++ b/source/include/math_helper.h @@ -18,4 +18,7 @@ bool double_equal(double a, double b); double deg_to_rad(double deg); +double min3(double a, double b, double c); +double max3(double a, double b, double c); + #endif /* DORAYME_MATH_HELPER_H */ diff --git a/source/include/shape.h b/source/include/shape.h index 11f1f08..c536078 100644 --- a/source/include/shape.h +++ b/source/include/shape.h @@ -22,6 +22,8 @@ enum ShapeType SHAPE_NONE, SHAPE_SPHERE, SHAPE_PLANE, + SHAPE_CUBE, + SHAPE_CONE, }; /* Base class for all object that can be presented in the world */ diff --git a/source/math_helper.cpp b/source/math_helper.cpp index b5619d6..48c8db9 100644 --- a/source/math_helper.cpp +++ b/source/math_helper.cpp @@ -31,4 +31,42 @@ bool double_equal(double a, double b) double deg_to_rad(double deg) { return deg * M_PI / 180.; +} + +double min3(double a, double b, double c) +{ + if (a <= b) + { + if (c < a) return c; + return a; + } + if (b <= a) + { + if (c < b) return c; + return b; + } + if (c <= a) + { + if (b < c) return b; + } + return c; +} + +double max3(double a, double b, double c) +{ + if (a >= b) + { + if (c > a) return c; + return a; + } + if (b >= a) + { + if (c > b) return c; + return b; + } + if (c >= a) + { + if (b > c) return b; + } + return c; } \ No newline at end of file diff --git a/source/shapes/cube.cpp b/source/shapes/cube.cpp new file mode 100644 index 0000000..27ccfe2 --- /dev/null +++ b/source/shapes/cube.cpp @@ -0,0 +1,76 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Cube implementation + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include + +void Cube::checkAxis(double axeOrigin, double axeDirection, double *axeMin, double *axeMax) +{ + double tMinNumerator = (-1 - axeOrigin); + double tMaxNumerator = (1 - axeOrigin); + + if (fabs(axeDirection) >= getEpsilon()) + { + *axeMin = tMinNumerator / axeDirection; + *axeMax = tMaxNumerator / axeDirection; + } + else + { + *axeMin = tMinNumerator * INFINITY; + *axeMax = tMaxNumerator * INFINITY; + } + + if (*axeMin > *axeMax) + { + double swap = *axeMax; + *axeMax = *axeMin; + *axeMin = swap; + } +} + +Intersect Cube::localIntersect(Ray r) +{ + Intersect ret; + + double xtMin, xtMax, ytMin, ytMax, ztMin, ztMax; + double tMin, tMax; + + this->checkAxis(r.origin.x, r.direction.x, &xtMin, &xtMax); + this->checkAxis(r.origin.y, r.direction.y, &ytMin, &ytMax); + this->checkAxis(r.origin.z, r.direction.z, &ztMin, &ztMax); + + tMin = max3(xtMin, ytMin, ztMin); + tMax = min3(xtMax, ytMax, ztMax); + + if (tMin <= tMax) + { + ret.add(Intersection(tMin, this)); + ret.add(Intersection(tMax, this)); + } + + return ret; +} + +Tuple Cube::localNormalAt(Tuple point) +{ + double maxC = max3(fabs(point.x), fabs(point.y), fabs(point.z)); + + if (maxC == fabs(point.x)) + { + return Vector(point.x, 0, 0); + } + else if (maxC == fabs(point.y)) + { + return Vector(0, point.y, 0); + } + + return Vector(0, 0, point.z); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2d4d5eb..ea0df44 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,9 +3,9 @@ project(DoRayTested) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -set(TESTS_SRC tuple_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp ray_test.cpp - intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_test.cpp - shape_test.cpp plane_test.cpp pattern_test.cpp) +set(TESTS_SRC math_test.cpp tuple_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp + ray_test.cpp intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_test.cpp + shape_test.cpp plane_test.cpp pattern_test.cpp cube_test.cpp) add_executable(testMyRays) target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) @@ -64,6 +64,11 @@ target_include_directories(ch11_test PUBLIC ../source/include) target_sources(ch11_test PRIVATE ch11_test.cpp) target_link_libraries(ch11_test rayonnement) +add_executable(ch12_test) +target_include_directories(ch12_test PUBLIC ../source/include) +target_sources(ch12_test PRIVATE ch12_test.cpp) +target_link_libraries(ch12_test rayonnement) + add_test(NAME Chapter05_Test COMMAND $) add_test(NAME Chapter06_Test COMMAND $) add_test(NAME Chapter07_Test COMMAND $) @@ -72,3 +77,4 @@ add_test(NAME Chapter10_Test COMMAND $) add_test(NAME Chapter11_Reflection COMMAND $) add_test(NAME Chapter11_Refraction COMMAND $) add_test(NAME Chapter11_Test COMMAND $) +add_test(NAME Chapter12_Test COMMAND $) \ No newline at end of file diff --git a/tests/ch10_test.cpp b/tests/ch10_test.cpp index 5b1ea8c..4f538e6 100644 --- a/tests/ch10_test.cpp +++ b/tests/ch10_test.cpp @@ -79,7 +79,7 @@ int main() w.addLight(&light); /* Set the camera */ - Camera camera = Camera(1920, 1080, M_PI / 3); + Camera camera = Camera(100, 50, M_PI / 3); camera.setTransform(viewTransform(Point(0, 1.5, -5), Point(0, 1, 0), Vector(0, 1, 0))); diff --git a/tests/ch12_test.cpp b/tests/ch12_test.cpp new file mode 100644 index 0000000..50ae205 --- /dev/null +++ b/tests/ch12_test.cpp @@ -0,0 +1,213 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Render test for reflection in chapter 12. + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +int main() +{ + World w = World(); + + /* Add light */ + Light light = Light(POINT_LIGHT, Point(0, 6.9, -5), Colour(1, 1, 0.9)); + w.addLight(&light); + + /* ----------------------------- */ + + /* The floor / ceiling */ + Cube floor = Cube(); + floor.setTransform( scaling(20, 7, 20) * translation(0, 1, 0)); + floor.material.pattern = new CheckersPattern(Colour(0, 0, 0), Colour(0.25, 0.25, 0.25)); + floor.material.pattern->setTransform(scaling(0.07, 0.07, 0.07)); + floor.material.ambient = 0.25; + floor.material.diffuse = 0.7; + floor.material.specular = 0.9; + floor.material.shininess = 300; + floor.material.reflective = 0.1; + w.addObject(&floor); + + /* Walls */ + Cube walls = Cube(); + walls.setTransform(scaling(10, 10, 10)); + walls.material.pattern = new CheckersPattern(Colour( 0.4863, 0.3765, 0.2941), Colour(0.3725, 0.2902, 0.2275 )); + walls.material.pattern->setTransform(scaling(0.05, 20, 0.05)); + walls.material.ambient = 0.1; + walls.material.diffuse = 0.7; + walls.material.specular = 0.9; + walls.material.shininess = 300; + walls.material.reflective = 0.1; + w.addObject(&walls); + + /* Table top */ + Cube tableTop = Cube(); + tableTop.setTransform(translation(0, 3.1, 0) * scaling(3, 0.1, 2)); + tableTop.material.pattern = new StripPattern(Colour(0.5529, 0.4235, 0.3255), Colour(0.6588, 0.5098, 0.4000 )); + tableTop.material.pattern->setTransform(scaling(0.05, 0.05, 0.05) * rotationY(0.1)); + tableTop.material.ambient = 0.1; + tableTop.material.diffuse = 0.7; + tableTop.material.specular = 0.9; + tableTop.material.shininess = 300; + tableTop.material.reflective = 0.2; + w.addObject(&tableTop); + + /* Leg 1 */ + Cube leg1 = Cube(); + leg1.setTransform(translation(2.7, 1.5, -1.7) * scaling(0.1, 1.5, 0.1)); + leg1.material.colour = Colour(0.5529, 0.4235, 0.3255); + leg1.material.ambient = 0.2; + leg1.material.diffuse = 0.7; + w.addObject(&leg1); + + /* Leg 2 */ + Cube leg2 = Cube(); + leg2.setTransform(translation(2.7, 1.5, 1.7) * scaling(0.1, 1.5, 0.1)); + leg2.material.colour = Colour(0.5529, 0.4235, 0.3255); + leg2.material.ambient = 0.2; + leg2.material.diffuse = 0.7; + w.addObject(&leg2); + + /* Leg 3 */ + Cube leg3 = Cube(); + leg3.setTransform(translation(-2.7, 1.5, -1.7) * scaling(0.1, 1.5, 0.1)); + leg3.material.colour = Colour(0.5529, 0.4235, 0.3255); + leg3.material.ambient = 0.2; + leg3.material.diffuse = 0.7; + w.addObject(&leg3); + + /* Leg 4 */ + Cube leg4 = Cube(); + leg4.setTransform(translation(-2.7, 1.5, 1.7) * scaling(0.1, 1.5, 0.1)); + leg4.material.colour = Colour(0.5529, 0.4235, 0.3255); + leg4.material.ambient = 0.2; + leg4.material.diffuse = 0.7; + w.addObject(&leg4); + + /* ----------------------------- */ + + /* Glass cube */ + Cube glassCube = Cube(); + glassCube.setTransform(translation(0, 3.45001, 0) * rotationY(0.2) * scaling(0.25, 0.25, 0.25)); + glassCube.dropShadow = false; + glassCube.material.colour = Colour(1, 1, 0.8); + glassCube.material.ambient = 0; + glassCube.material.diffuse = 0.3; + glassCube.material.specular = 0.9; + glassCube.material.shininess = 300; + glassCube.material.reflective = 0.7; + glassCube.material.transparency = 0.7; + glassCube.material.refractiveIndex = 1.5; + w.addObject(&glassCube); + + /* Little cube 1 */ + Cube lilCube1 = Cube(); + lilCube1.setTransform(translation(1, 3.25, -0.9) * + rotationY(-0.4) * + scaling(0.15, 0.15, 0.15)); + lilCube1.material.colour = Colour(1, 0.5, 0.5); + lilCube1.material.reflective = 0.6; + lilCube1.material.diffuse = 0.4; + w.addObject(&lilCube1); + + /* Little cube 2 */ + Cube lilCube2 = Cube(); + lilCube2.setTransform(translation(-1.5, 3.27, 0.3) * + rotationY(0.4) * + scaling(0.15, 0.07, 0.15)); + lilCube2.material.colour = Colour(1, 1, 0.5); + w.addObject(&lilCube2); + + /* Little cube 3 */ + Cube lilCube3 = Cube(); + lilCube3.setTransform(translation(0, 3.25, 1) * + rotationY(0.4) * + scaling(0.2, 0.05, 0.05)); + lilCube3.material.colour = Colour(0.5, 1, 0.5); + w.addObject(&lilCube3); + + /* Little cube 4 */ + Cube lilCube4 = Cube(); + lilCube4.setTransform(translation(-0.6, 3.4, -1) * + rotationY(0.8) * + scaling(0.05, 0.2, 0.05)); + lilCube4.material.colour = Colour(0.5, 0.5, 1); + w.addObject(&lilCube4); + + /* Little cube 5 */ + Cube lilCube5 = Cube(); + lilCube5.setTransform(translation(2, 3.4, 1) * + rotationY(0.8) * + scaling(0.05, 0.2, 0.05)); + lilCube5.material.colour = Colour(0.5, 1, 1); + w.addObject(&lilCube5); + + /* ----------------------------- */ + + /* Frame 1 */ + Cube frame1 = Cube(); + frame1.setTransform(translation(-10, 4, 1) * scaling(0.05, 1, 1)); + frame1.material.colour = Colour(0.7098, 0.2471, 0.2196); + frame1.material.diffuse = 0.6; + w.addObject(&frame1); + + /* Frame 2 */ + Cube frame2 = Cube(); + frame2.setTransform(translation(-10, 3.4, 2.7) * scaling(0.05, 0.4, 0.4)); + frame2.material.colour = Colour(0.2667, 0.2706, 0.6902); + frame2.material.diffuse = 0.6; + w.addObject(&frame2); + + /* Frame 3 */ + Cube frame3 = Cube(); + frame3.setTransform(translation(-10, 4.6, 2.7) * scaling(0.05, 0.4, 0.4)); + frame3.material.colour = Colour(0.3098, 0.5961, 0.3098); + frame3.material.diffuse = 0.6; + w.addObject(&frame3); + + /* ----------------------------- */ + + /* Mirror */ + Cube mirror = Cube(); + mirror.setTransform(translation(-2, 3.5, 9.95) * scaling(4.8, 1.4, 0.06)); + mirror.material.colour = Colour(0, 0, 0); + mirror.material.diffuse = 0; + mirror.material.ambient = 0; + mirror.material.specular = 1; + mirror.material.shininess = 300; + mirror.material.reflective = 1; + w.addObject(&mirror); + + /* ----------------------------- */ + + /* Set the camera */ + Camera camera = Camera(400, 200, 0.785); + camera.setTransform(viewTransform(Point(8, 6, -8), + Point(0, 3, 0), + Vector(0, 1, 0))); + + /* Now render it */ + Canvas image = camera.render(w, 4); + + image.SaveAsPNG("ch12_test.png"); + + return 0; +} \ No newline at end of file diff --git a/tests/cube_test.cpp b/tests/cube_test.cpp new file mode 100644 index 0000000..19250e3 --- /dev/null +++ b/tests/cube_test.cpp @@ -0,0 +1,117 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Cube unit tests + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include + +TEST(CubeTest, A_ray_intersects_a_cube) +{ + Cube c = Cube(); + + Point Origins[] = { + Point(5, 0.5, 0), + Point(-5, 0.5, 0), + Point(0.5, 5, 0), + Point(0.5, -5, 0), + Point(0.5, 0, 5), + Point(0.5, 0, -5), + Point(0, 0.5, 0), + }; + + Vector Directions[] = { + Vector(-1, 0, 0), + Vector(1, 0, 0), + Vector(0, -1, 0), + Vector(0, 1, 0), + Vector(0, 0, -1), + Vector(0, 0, 1), + Vector(0, 0, 1), + }; + + double t1[] = { 4, 4, 4, 4, 4, 4, -1 }; + double t2[] = { 6, 6, 6, 6, 6, 6, 1 }; + + int i; + for(i = 0; i < 7; i++) + { + Ray r = Ray(Origins[i], Directions[i]); + Intersect xs = c.intersect(r); + + ASSERT_EQ(xs.count(), 2); + EXPECT_EQ(xs[0].t, t1[i]); + EXPECT_EQ(xs[1].t, t2[i]); + } +} + +TEST(CubeTest, A_ray_miss_a_cube) +{ + Cube c = Cube(); + + Point Origins[] = { + Point(-2, 0, 0), + Point(0, -2, 0), + Point(0, 0, -2), + Point(2, 0, 2), + Point(0, 2, 2), + Point(2, 2, 0), + }; + + Vector Directions[] = { + Vector(0.2673, 0.5345, 0.8018), + Vector(0.8018, 0.2673, 0.5345), + Vector(0.5345, 0.8018, 0.2673), + Vector(0, 0, -1), + Vector(0, -1, 0), + Vector(-1, 0, 0), + }; + + int i; + for(i = 0; i < 6; i++) + { + Ray r = Ray(Origins[i], Directions[i]); + Intersect xs = c.intersect(r); + + ASSERT_EQ(xs.count(), 0); + } +} + +TEST(CubeTest, The_normal_on_the_surface_of_a_cube) +{ + Cube c = Cube(); + + Point HitPoints[] = { + Point(1, 0.5, -0.8), + Point(-1, -0.2, 0.9), + Point(-0.4, 1, -0.1), + Point(0.3, -1, -0.7), + Point(-0.6, 0.3, 1), + Point(0.4, 0.4, -1), + Point(1, 1, 1), + Point(-1, -1, -1), + }; + + Vector ExpectedNormals[] = { + Vector(1, 0, 0), + Vector(-1, 0, 0), + Vector(0, 1, 0), + Vector(0, -1, 0), + Vector(0, 0, 1), + Vector(0, 0, -1), + Vector(1, 0, 0), + Vector(-1, 0, 0), + }; + + int i; + for(i = 0; i < 8; i++) + { + ASSERT_EQ(c.normalAt(HitPoints[i]), ExpectedNormals[i]); + } +} \ No newline at end of file diff --git a/tests/math_test.cpp b/tests/math_test.cpp new file mode 100644 index 0000000..594297e --- /dev/null +++ b/tests/math_test.cpp @@ -0,0 +1,97 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Math helper unit tests + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include + +TEST(MathTest, Default_epsilon) +{ + ASSERT_EQ(getEpsilon(), FLT_EPSILON); +} + +TEST(MathTest, Check_setting_epsilon) +{ + set_equal_precision(0.00001); + + ASSERT_EQ(getEpsilon(), 0.00001); + + set_equal_precision(FLT_EPSILON); +} + +TEST(MathTest, Check_double_equal_is_working) +{ + ASSERT_TRUE(double_equal(0 - DBL_EPSILON, 0)); + + ASSERT_FALSE(double_equal(0 - FLT_EPSILON, 0)); + ASSERT_TRUE(double_equal(0 - FLT_EPSILON/2.0, 0)); + + ASSERT_FALSE(double_equal(0 - 0.001, 0)); + ASSERT_FALSE(double_equal(0 - 0.00001, 0)); + ASSERT_FALSE(double_equal(0 - 0.000001, 0)); + + set_equal_precision(0.00001); + ASSERT_TRUE(double_equal(0 - FLT_EPSILON*2, 0)); + ASSERT_FALSE(double_equal(0 - 0.001, 0)); + ASSERT_FALSE(double_equal(0 - 0.00001, 0)); + ASSERT_TRUE(double_equal(0 - 0.000001, 0)); + + set_equal_precision(FLT_EPSILON); +} + + + +TEST(MathTest, Check_that_deg_to_rad_is_working) +{ + double angle180 = deg_to_rad(180); + double angle90 = deg_to_rad(90); + double angle270 = deg_to_rad(270); + + ASSERT_EQ(angle180, M_PI); + ASSERT_EQ(angle90, M_PI / 2.); + ASSERT_EQ(angle270, M_PI * 1.5); +} + +TEST(MathTest, Min_is_working) +{ + ASSERT_EQ(min3(1, 2, 3), 1); + ASSERT_EQ(min3(1, 3, 2), 1); + ASSERT_EQ(min3(2, 1, 3), 1); + ASSERT_EQ(min3(2, 3, 1), 1); + ASSERT_EQ(min3(3, 1, 2), 1); + ASSERT_EQ(min3(3, 2, 1), 1); + + ASSERT_EQ(min3(1, 2, 2), 1); + ASSERT_EQ(min3(2, 1, 2), 1); + ASSERT_EQ(min3(2, 2, 1), 1); + + ASSERT_EQ(min3(3, 2, 2), 2); + ASSERT_EQ(min3(2, 3, 2), 2); + ASSERT_EQ(min3(2, 2, 3), 2); + + ASSERT_EQ(min3(2, 2, 2), 2); +} + +TEST(MathTest, Max_is_working) +{ + ASSERT_EQ(max3(1, 2, 3), 3); + ASSERT_EQ(max3(1, 3, 2), 3); + ASSERT_EQ(max3(2, 1, 3), 3); + ASSERT_EQ(max3(2, 3, 1), 3); + ASSERT_EQ(max3(3, 1, 2), 3); + ASSERT_EQ(max3(3, 2, 1), 3); + + ASSERT_EQ(max3(1, 2, 2), 2); + ASSERT_EQ(max3(2, 1, 2), 2); + ASSERT_EQ(max3(2, 2, 1), 2); + + ASSERT_EQ(max3(3, 2, 2), 3); + ASSERT_EQ(max3(2, 3, 2), 3); + ASSERT_EQ(max3(2, 2, 3), 3); + + ASSERT_EQ(max3(2, 2, 2), 2); +} \ No newline at end of file diff --git a/tests/transformation_test.cpp b/tests/transformation_test.cpp index ef3c9dd..994c613 100644 --- a/tests/transformation_test.cpp +++ b/tests/transformation_test.cpp @@ -245,14 +245,4 @@ TEST(TransformationTest, An_arbitrary_view_transformation) set_equal_precision(FLT_EPSILON); } -TEST(TransformationTest, Check_that_deg_to_rad_is_working) -{ - double angle180 = deg_to_rad(180); - double angle90 = deg_to_rad(90); - double angle270 = deg_to_rad(270); - - ASSERT_EQ(angle180, M_PI); - ASSERT_EQ(angle90, M_PI / 2.); - ASSERT_EQ(angle270, M_PI * 1.5); -}