From cff3d78450236ee100f7ac590881837690fa5a70 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Mon, 18 Mar 2024 18:39:25 +0100 Subject: [PATCH] [FEAT] Add truncated cylinders --- .vscode/settings.json | 1 + apps/CMakeLists.txt | 3 + apps/chapter_13.cpp | 102 ++++++++++++++++++++++++ raytracing/src/shapes/cylinder.cpp | 48 ++++++++++- raytracing/src/shapes/cylinder.h | 12 +++ tests/12_cubes.cpp | 2 +- tests/13_cylinders.cpp | 123 +++++++++++++++++++++++++++++ 7 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 apps/chapter_13.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index de4201a..4936f35 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -108,6 +108,7 @@ "/home/jbnadal/sources/jb/raytracer_challenge/build/apps/CMakeFiles/chapter_11_p1.dir", "/home/jbnadal/sources/jb/raytracer_challenge/build/apps/CMakeFiles/chapter_11_p2.dir", "/home/jbnadal/sources/jb/raytracer_challenge/build/apps/CMakeFiles/chapter_12.dir", + "/home/jbnadal/sources/jb/raytracer_challenge/build/apps/CMakeFiles/chapter_13.dir", "/home/jbnadal/sources/jb/raytracer_challenge/build/tests/CMakeFiles/raytracing_test.dir", "/home/jbnadal/sources/jb/raytracer_challenge/build/build/_deps/catch2-src/src/CMakeFiles/Catch2.dir/catch2/benchmark", "/home/jbnadal/sources/jb/raytracer_challenge/build/build/_deps/catch2-src/src/CMakeFiles/Catch2.dir/catch2/benchmark/detail", diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 3902c87..96f4c84 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -30,3 +30,6 @@ target_link_libraries(chapter_11_p2 PRIVATE raytracing OpenMP::OpenMP_CXX) add_executable(chapter_12 chapter_12.cpp) target_link_libraries(chapter_12 PRIVATE raytracing OpenMP::OpenMP_CXX) + +add_executable(chapter_13 chapter_13.cpp) +target_link_libraries(chapter_13 PRIVATE raytracing OpenMP::OpenMP_CXX) diff --git a/apps/chapter_13.cpp b/apps/chapter_13.cpp new file mode 100644 index 0000000..7f0afa4 --- /dev/null +++ b/apps/chapter_13.cpp @@ -0,0 +1,102 @@ +/*! + * chapter_13.cpp + * + * Copyright (c) 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: 18/03/2024 + * + */ + +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com + +// Coordinate came from: +// https://github.com/sraaphorst/raytracer-kotlin/blob/main/src/main/kotlin/apps/ch13_world.kt + +#include +#include + +#include + +/* ------------------------------------------------------------------------- */ + +using namespace Raytracer; +using namespace std; + +/* ------------------------------------------------------------------------- */ + +int main(void) +{ + World the_world; + Camera the_camera; + Canvas the_canvas; + Plane *the_floor; + Cylinder *the_cylinder; + chrono::time_point the_start, the_end; + + printf("Chapter 13 example.\n"); + + // Floor is an extremely flattened sphere with a matte texture. + the_floor = new Plane(); + the_floor->set_transform(Matrix::rotation_y(0.3) * + Matrix::scaling(0.25, 0.25, 0.25)); + Material &the_floor_material = the_floor->material(); + the_floor_material.set_pattern(new CheckersPattern(Color(0.5, 0.5, 0.5), Color(0.75, 0.75, 0.75))); + the_floor_material.set_ambient(0.9); + the_floor_material.set_diffuse(0.9); + the_floor_material.set_specular(0); + the_world.add_object(the_floor); + + // Cylinder + the_cylinder = new Cylinder(); + the_cylinder->set_minimum(0); + the_cylinder->set_maximum(0.75); + the_cylinder->set_transform(Matrix::translation(-1, 0, 1) * + Matrix::scaling(0.5, 1, 0.5)); + Material &the_cylinder_material = the_cylinder->material(); + the_cylinder_material.set_color(Color(0, 0, 0.6)); + the_cylinder_material.set_diffuse(0.1); + the_cylinder_material.set_specular(0.9); + the_cylinder_material.set_shininess(300); + the_cylinder_material.set_reflective(0.9); + the_world.add_object(the_cylinder); + + // The Light source is white, shining from above and to the left + the_world.set_light(PointLight(Tuple::Point(1, 6.9, -4.9), Color(1, 1, 1))); + + // Configure the camera. + // the_camera = Camera(100, 50, std::numbers::pi / 10); + the_camera = Camera(320, 200, std::numbers::pi / 10); + // the_camera = Camera(640, 480, std::numbers::pi / 10); + the_camera.show_progress_bar(); + the_camera.set_transform( + Matrix::view_transform(Tuple::Point(8, 3.5, -9), Tuple::Point(0, 0.3, 0), Tuple::Vector(0, 1, 0))); + + the_start = chrono::high_resolution_clock::now(); + the_camera.show_progress_bar(); + the_canvas = the_camera.render(the_world); + the_end = chrono::high_resolution_clock::now(); + + the_canvas.save_to_file("chapter13.ppm"); + + chrono::duration the_elapsed_time = the_end - the_start; + printf("Execution Time: %f secondes\n", the_elapsed_time.count()); + + return 0; +} diff --git a/raytracing/src/shapes/cylinder.cpp b/raytracing/src/shapes/cylinder.cpp index 1c3db35..05bb868 100644 --- a/raytracing/src/shapes/cylinder.cpp +++ b/raytracing/src/shapes/cylinder.cpp @@ -43,6 +43,7 @@ Intersections Cylinder::local_intersect(const Ray &a_ray) { double the_a, the_b, the_c, the_disc; double the_t0, the_t1; + double the_y0, the_y1; Intersections the_intersections; const Tuple &the_ray_direction = a_ray.direction(); const Tuple &the_ray_origin = a_ray.origin(); @@ -68,9 +69,22 @@ Intersections Cylinder::local_intersect(const Ray &a_ray) the_t0 = (-the_b - std::sqrt(the_disc)) / (2 * the_a); the_t1 = (-the_b + std::sqrt(the_disc)) / (2 * the_a); + if (the_t0 > the_t1) + { + std::swap(the_t0, the_t1); + } - the_intersections.add(Intersection(the_t0, this)); - the_intersections.add(Intersection(the_t1, this)); + the_y0 = the_ray_origin.y() + the_t0 * the_ray_direction.y(); + if ((m_minimum < the_y0) && (the_y0 < m_maximum)) + { + the_intersections.add(Intersection(the_t0, this)); + } + + the_y1 = the_ray_origin.y() + the_t1 * the_ray_direction.y(); + if ((m_minimum < the_y1) && (the_y1 < m_maximum)) + { + the_intersections.add(Intersection(the_t1, this)); + } return the_intersections; } @@ -79,5 +93,33 @@ Intersections Cylinder::local_intersect(const Ray &a_ray) Tuple Cylinder::local_normal_at(const Tuple &a_local_point) const { - return Tuple::Vector(0, 0, 0); + return Tuple::Vector(a_local_point.x(), 0, a_local_point.z()); +} + +/* ------------------------------------------------------------------------- */ + +double Cylinder::minimum(void) +{ + return m_minimum; +} + +/* ------------------------------------------------------------------------- */ + +void Cylinder::set_minimum(double a_value) +{ + m_minimum = a_value; +} + +/* ------------------------------------------------------------------------- */ + +double Cylinder::maximum(void) +{ + return m_maximum; +} + +/* ------------------------------------------------------------------------- */ + +void Cylinder::set_maximum(double a_value) +{ + m_maximum = a_value; } diff --git a/raytracing/src/shapes/cylinder.h b/raytracing/src/shapes/cylinder.h index 9efbce5..40313e9 100644 --- a/raytracing/src/shapes/cylinder.h +++ b/raytracing/src/shapes/cylinder.h @@ -28,6 +28,8 @@ /* ------------------------------------------------------------------------- */ +#include + #include "shapes/shape.h" /* ------------------------------------------------------------------------- */ @@ -40,6 +42,16 @@ namespace Raytracer Cylinder(void) = default; Intersections local_intersect(const Ray &a_ray) override; Tuple local_normal_at(const Tuple &a_local_point) const override; + + double minimum(void); + void set_minimum(double a_value); + + double maximum(void); + void set_maximum(double a_value); + + private: + double m_minimum = std::numeric_limits::infinity(); + double m_maximum = std::numeric_limits::infinity(); }; }; // namespace Raytracer diff --git a/tests/12_cubes.cpp b/tests/12_cubes.cpp index 4d2cd54..f0b61bc 100644 --- a/tests/12_cubes.cpp +++ b/tests/12_cubes.cpp @@ -146,7 +146,7 @@ SCENARIO("A Ray misses a cube", "[features/cubes.feature]") SCENARIO("The normal on the surface of a cube", "[features/cubes.feature]") { - // | origin | normal | + // | point | normal | // | point(1, 0.5, -0.8) | vector(1, 0, 0) | // | point(-1, -0.2, 0.9) | vector(-1, 0, 0) | // | point(-0.4, 1, -0.1) | vector(0, 1, 0) | diff --git a/tests/13_cylinders.cpp b/tests/13_cylinders.cpp index 9c52e87..f8625f5 100644 --- a/tests/13_cylinders.cpp +++ b/tests/13_cylinders.cpp @@ -44,6 +44,25 @@ public: /* ------------------------------------------------------------------------- */ +class CylinderTestNormal +{ +public: + Tuple point; + Tuple normal; +}; + +/* ------------------------------------------------------------------------- */ + +class CylinderTestConstrained +{ +public: + Tuple point; + Tuple direction; + uint16_t count; +}; + +/* ------------------------------------------------------------------------- */ + SCENARIO("A Ray misses a cylinder", "[features/cylinders.feature]") { // | origin | direction | @@ -122,3 +141,107 @@ SCENARIO("A Ray strikes a cylinder", "[features/cylinders.feature]") } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Normal vector on a cylinder", "[features/cylinders.feature]") +{ + // | point | normal | + // | point(1, 0, 0) | vector(1, 0, 0) | + // | point(0, 5, -1) | vector(0, 0, -1) | + // | point(0, -2, 1) | vector(0, 0, 1) | + // | point(-1, 1, 0) | vector(-1, 0, 0) | + CylinderTestNormal the_test[] = { + { Tuple::Point(1, 0, 0), Tuple::Vector(1, 0, 0)}, + { Tuple::Point(0, 5, -1), Tuple::Vector(0, 0, -1)}, + { Tuple::Point(0, -2, 1), Tuple::Vector(0, 0, 1)}, + {Tuple::Point(-1, 1, 0), Tuple::Vector(-1, 0, 0)} + }; + + GIVEN("cyl <- cylinder()") + { + Cylinder cyl; + WHEN("n <- local_normal_at(cyl,)") + { + for (int i = 0; i < 3; i++) + { + Tuple p = the_test[i].point; + Tuple normal = cyl.local_normal_at(p); + THEN("n = ") + { + REQUIRE(normal == the_test[i].normal); + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The default minimum and maximum for a cylinder", "[features/cylinders.feature]") +{ + GIVEN("cyl <- cylinder()") + { + Cylinder cyl; + THEN("cym.minimum = -infinity") + { + REQUIRE(cyl.minimum() == std::numeric_limits::infinity()); + } + AND_THEN("cym.maximum = -infinity") + { + REQUIRE(cyl.maximum() == std::numeric_limits::infinity()); + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a constrained cylinder", "[features/cylinders.feature]") +{ + // | | point | direction | count | + // | 1 | point(0, 1.5, 0) | vector(0.1, 1, 0) | 0 | + // | 2 | point(0, 3, -5) | vector(0, 0, 1) | 0 | + // | 3 | point(0, 0, -5) | vector(0, 0, 1) | 0 | + // | 4 | point(0, 2, -5) | vector(0, 0, 1) | 0 | + // | 5 | point(0, 1, -5) | vector(0, 0, 1) | 0 | + // | 6 | point(0, 1.5, -2) | vector(0, 0, 1) | 2 | + CylinderTestConstrained the_test[] = { + {Tuple::Point(0, 1.5, 0), Tuple::Vector(0.1, 1, 0), 0}, + {Tuple::Point(0, 3, -5), Tuple::Vector(0, 0, 1), 0}, + {Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1), 0}, + {Tuple::Point(0, 2, -5), Tuple::Vector(0, 0, 1), 0}, + {Tuple::Point(0, 1, -5), Tuple::Vector(0, 0, 1), 0}, + {Tuple::Point(0, 1.5, -20), Tuple::Vector(0, 0, 1), 2} + }; + GIVEN("cyl <- cylinder()") + { + Cylinder cyl; + AND_GIVEN("cyl.minimum <- 1") + { + cyl.set_minimum(1); + AND_GIVEN("cyl.maximum <- 2") + { + cyl.set_maximum(2); + AND_GIVEN("direction <- normalize()") + { + AND_GIVEN("r <- ray(, direction)") + { + WHEN("xs <- local_intersect(cyl,r)") + { + for (int i = 0; i < 6; i++) + { + Tuple direction = the_test[i].direction.normalize(); + Ray r(the_test[i].point, direction); + Intersections xs = cyl.local_intersect(r); + THEN("xs.count = ") + { + REQUIRE(xs.count() == the_test[i].count); + } + } + } + } + } + } + } + } +}