diff --git a/README.md b/README.md index a681424..ccbcc8b 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,6 @@ Ray Tracing: The Next Week book: http://raytracerchallenge.com/ ### Chapter 06 ![chapter_06](data/chapter_06.png) + +### Chapter 07 +![chapter_07](data/chapter_07.png) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index d9394c5..7636efd 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -10,3 +10,6 @@ target_link_libraries(chapter_05 PRIVATE raytracing gcov) add_executable(chapter_06 chapter_06.cpp) target_link_libraries(chapter_06 PRIVATE raytracing gcov) + +add_executable(chapter_07 chapter_07.cpp) +target_link_libraries(chapter_07 PRIVATE raytracing gcov) diff --git a/apps/chapter_07.cpp b/apps/chapter_07.cpp new file mode 100644 index 0000000..17e97d0 --- /dev/null +++ b/apps/chapter_07.cpp @@ -0,0 +1,108 @@ +/*! + * chapter_07.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: 26/02/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 + +#include + +#include + +/* ------------------------------------------------------------------------- */ + +using namespace Raytracer; + +/* ------------------------------------------------------------------------- */ + +int main(void) +{ + World the_world; + Camera the_camera; + Canvas the_canvas; + Sphere *the_floor, *the_left_wall, *the_right_wall; + Sphere *the_middle, *the_right, *the_left; + printf("Chapter 07 example.\n"); + + // Floor is an extremely flattened sphere with a matte texture. + the_floor = new Sphere(); + the_floor->set_transform(Matrix::scaling(10, 0.01, 10)); + the_floor->material().set_color(Color(1, 0.9, 0.9)); + the_floor->material().set_specular(0); + the_world.add_object(the_floor); + + // The Wall on the left has the same scale and color as the floor, + // But is also rotated and translated into place. + the_left_wall = new Sphere(); + the_left_wall->set_transform(Matrix::translation(0, 0, 5) * Matrix::rotation_y(-std::numbers::pi / 4) * + Matrix::rotation_x(std::numbers::pi / 2) * Matrix::scaling(10, 0.01, 10)); + the_left_wall->set_material(the_floor->material()); + the_world.add_object(the_left_wall); + + // The Wall on the right is identical to the left wall, but is rotated the opposite direction in y. + the_right_wall = new Sphere(); + the_right_wall->set_transform(Matrix::translation(0, 0, 5) * Matrix::rotation_y(std::numbers::pi / 4) * + Matrix::rotation_x(std::numbers::pi / 2) * Matrix::scaling(10, 0.01, 10)); + the_right_wall->set_material(the_floor->material()); + the_world.add_object(the_right_wall); + + // The large sphere in the middle is a unit sphere, translated upward slightly and colored green. + the_middle = new Sphere(); + the_middle->set_transform(Matrix::translation(-0.5, 1, 0.5)); + the_middle->material().set_color(Color(0.1, 1, 0.5)); + the_middle->material().set_diffuse(0.7); + the_middle->material().set_specular(0.3); + the_world.add_object(the_middle); + + // The smaller green sphere on the right is scaled in half + the_right = new Sphere(); + the_right->set_transform(Matrix::translation(1.5, 0.5, -0.5) * Matrix::scaling(0.5, 0.5, 0.5)); + the_right->material().set_color(Color(0.5, 1, 0.1)); + the_right->material().set_diffuse(0.7); + the_right->material().set_specular(0.3); + the_world.add_object(the_right); + + // The smallest sphere is scaled by a third, before being translated + the_left = new Sphere(); + the_left->set_transform(Matrix::translation(-1.5, 0.33, -0.75) * Matrix::scaling(0.33, 0.33, 0.33)); + the_left->material().set_color(Color(1, 0.8, 0.1)); + the_left->material().set_diffuse(0.7); + the_left->material().set_specular(0.3); + the_world.add_object(the_left); + + // The Light source is white, shining from above and to the left + the_world.set_light(PointLight(Tuple::Point(-10, 10, -10), Color(1, 1, 1))); + + // Configure the camera. + // the_camera = Camera(100, 50, std::numbers::pi / 2); + the_camera = Camera(320, 200, std::numbers::pi / 2); + the_camera.set_transform( + Matrix::view_transform(Tuple::Point(0, 1.5, -5), Tuple::Point(0, 1, 0), Tuple::Vector(0, 1, 0))); + + the_canvas = the_camera.render(the_world); + + the_canvas.save_to_file("chapter07.ppm"); + + return 0; +} diff --git a/data/chapter_07.png b/data/chapter_07.png new file mode 100644 index 0000000..c56a3c0 Binary files /dev/null and b/data/chapter_07.png differ diff --git a/raytracing/src/camera.cpp b/raytracing/src/camera.cpp index 118f1e9..0a1e763 100644 --- a/raytracing/src/camera.cpp +++ b/raytracing/src/camera.cpp @@ -36,6 +36,12 @@ using namespace Raytracer; /* ------------------------------------------------------------------------- */ +Camera::Camera(void) : m_h_size(0), m_v_size(0), m_field_of_view(0), m_half_width(0), m_half_height(0), m_pixel_size(0) +{ +} + +/* ------------------------------------------------------------------------- */ + Camera::Camera(uint16_t a_h_size, uint16_t a_v_size, double a_field_of_view) : m_h_size(a_h_size), m_v_size(a_v_size), @@ -65,6 +71,39 @@ Camera::Camera(uint16_t a_h_size, uint16_t a_v_size, double a_field_of_view) : /* ------------------------------------------------------------------------- */ +Camera::Camera(const Camera &an_other) +{ + m_h_size = an_other.m_h_size; + m_v_size = an_other.m_v_size; + m_field_of_view = an_other.m_field_of_view; + m_transform = an_other.m_transform; + m_half_width = an_other.m_half_width; + m_half_height = an_other.m_half_height; + m_pixel_size = an_other.m_pixel_size; +} + +/* ------------------------------------------------------------------------- */ + +const Camera &Camera::operator=(const Camera &an_other) +{ + if (this == &an_other) + { + return *this; + } + + m_h_size = an_other.m_h_size; + m_v_size = an_other.m_v_size; + m_field_of_view = an_other.m_field_of_view; + m_transform = an_other.m_transform; + m_half_width = an_other.m_half_width; + m_half_height = an_other.m_half_height; + m_pixel_size = an_other.m_pixel_size; + + return *this; +} + +/* ------------------------------------------------------------------------- */ + uint16_t Camera::hsize(void) const { return m_h_size; @@ -132,3 +171,22 @@ Ray Camera::ray_for_pixel(double an_x, double an_y) const return Ray(the_origin, the_direction); } + +/* ------------------------------------------------------------------------- */ + +Canvas Camera::render(const World &a_world) +{ + Canvas the_image(m_h_size, m_v_size); + + for (int y = 0; y < m_v_size - 1; y++) + { + for (int x = 0; x < m_h_size - 1; x++) + { + Ray the_ray = ray_for_pixel(x, y); + Color the_color = a_world.color_at(the_ray); + the_image.write_pixel(x, y, the_color); + } + } + + return the_image; +} diff --git a/raytracing/src/camera.h b/raytracing/src/camera.h index e696c6e..775ccd5 100644 --- a/raytracing/src/camera.h +++ b/raytracing/src/camera.h @@ -30,8 +30,10 @@ #include +#include "canvas.h" #include "matrix.h" #include "ray.h" +#include "world.h" /* ------------------------------------------------------------------------- */ @@ -40,7 +42,11 @@ namespace Raytracer class Camera { public: + Camera(void); Camera(uint16_t a_h_size, uint16_t a_v_size, double a_field_of_view); + Camera(const Camera &a_nother); + + const Camera &operator=(const Camera &an_other); uint16_t hsize(void) const; uint16_t vsize(void) const; @@ -53,6 +59,8 @@ namespace Raytracer Ray ray_for_pixel(double an_x, double an_y) const; + Canvas render(const World &a_world); + private: uint16_t m_h_size; diff --git a/raytracing/src/canvas.cpp b/raytracing/src/canvas.cpp index 77a0d65..995a42d 100644 --- a/raytracing/src/canvas.cpp +++ b/raytracing/src/canvas.cpp @@ -38,6 +38,12 @@ using namespace Raytracer; /* ------------------------------------------------------------------------- */ +Canvas::Canvas(void) : m_width(0), m_height(0) +{ +} + +/* ------------------------------------------------------------------------- */ + Canvas::Canvas(uint16_t a_width, uint16_t a_height) : m_width(a_width), m_height(a_height) { m_pixels = std::vector>(m_width, std::vector(m_height)); @@ -53,6 +59,26 @@ Canvas::Canvas(uint16_t a_width, uint16_t a_height) : m_width(a_width), m_height /* ------------------------------------------------------------------------- */ +Canvas::Canvas(const Canvas &a_canvas) : + m_width(a_canvas.m_width), + m_height(a_canvas.m_height), + m_pixels(a_canvas.m_pixels) +{ +} + +/* ------------------------------------------------------------------------- */ + +Canvas &Canvas::operator=(const Canvas &a_canvas) +{ + m_width = a_canvas.m_width; + m_height = a_canvas.m_height; + m_pixels = a_canvas.m_pixels; + + return *this; +} + +/* ------------------------------------------------------------------------- */ + uint16_t Canvas::width(void) const { return m_width; diff --git a/raytracing/src/canvas.h b/raytracing/src/canvas.h index 743824c..1b74cca 100644 --- a/raytracing/src/canvas.h +++ b/raytracing/src/canvas.h @@ -42,7 +42,11 @@ namespace Raytracer class Canvas { public: + Canvas(void); Canvas(uint16_t a_width, uint16_t a_height); + Canvas(const Canvas &a_canvas); + + Canvas &operator=(const Canvas &a_canvas); uint16_t width(void) const; uint16_t height(void) const; diff --git a/raytracing/src/world.cpp b/raytracing/src/world.cpp index 0411e33..c80ac22 100644 --- a/raytracing/src/world.cpp +++ b/raytracing/src/world.cpp @@ -140,7 +140,7 @@ bool World::contains(const Shape &a_shape) /* ------------------------------------------------------------------------- */ -Intersections World::intersect_world(const Ray &a_ray) +Intersections World::intersect_world(const Ray &a_ray) const { Intersections the_all_intersec; @@ -155,7 +155,7 @@ Intersections World::intersect_world(const Ray &a_ray) /* ------------------------------------------------------------------------- */ -Color World::shade_hit(const IntersectionData &an_intersection_data) +Color World::shade_hit(const IntersectionData &an_intersection_data) const { return an_intersection_data.object().material().lighting( m_light, an_intersection_data.point(), an_intersection_data.eyev(), an_intersection_data.normalv()); @@ -163,7 +163,7 @@ Color World::shade_hit(const IntersectionData &an_intersection_data) /* ------------------------------------------------------------------------- */ -Color World::color_at(const Ray &a_ray) +Color World::color_at(const Ray &a_ray) const { Color the_color = Color::Black(); diff --git a/raytracing/src/world.h b/raytracing/src/world.h index b273ffe..2928e85 100644 --- a/raytracing/src/world.h +++ b/raytracing/src/world.h @@ -60,9 +60,9 @@ namespace Raytracer bool contains(const Shape &a_shape); - Intersections intersect_world(const Ray &a_ray); - Color shade_hit(const IntersectionData &an_intersection_data); - Color color_at(const Ray &a_ray); + Intersections intersect_world(const Ray &a_ray) const; + Color shade_hit(const IntersectionData &an_intersection_data) const; + Color color_at(const Ray &a_ray) const; static World default_world(void); diff --git a/tests/07_making_scene.cpp b/tests/07_making_scene.cpp index c31ca86..d693c57 100644 --- a/tests/07_making_scene.cpp +++ b/tests/07_making_scene.cpp @@ -635,3 +635,41 @@ SCENARIO("Constructing a ray when the camera is transformed", "[features/camera. } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Rendering a world with the camera", "[features/camera.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("c <- camera(11, 11, pi/2)") + { + Camera c(11, 11, std::numbers::pi / 2); + AND_GIVEN("from <- point(0, 0, -5)") + { + Tuple from = Tuple::Point(0, 0, -5); + AND_GIVEN("to <- point(0, 0, 0)") + { + Tuple to = Tuple::Point(0, 0, 0); + AND_GIVEN("up <- vector(0, 1, 0)") + { + Tuple up = Tuple::Vector(0, 1, 0); + AND_GIVEN("c.transform <- view_transform(from, to, up)") + { + c.set_transform(Matrix::view_transform(from, to, up)); + WHEN("image <- c.render(w)") + { + Canvas image = c.render(w); + THEN("pixel_at(image, 5, 5) = color(0.38066, 0.47583, 0.2855)") + { + REQUIRE(image.pixel_at(5, 5) == Color(0.38066, 0.47583, 0.2855)); + } + } + } + } + } + } + } + } +}