diff --git a/raytracing/CMakeLists.txt b/raytracing/CMakeLists.txt index 51d1ed4..10f2158 100644 --- a/raytracing/CMakeLists.txt +++ b/raytracing/CMakeLists.txt @@ -10,8 +10,9 @@ add_definitions(--coverage) add_library(raytracing src/canvas.cpp - src/common.cpp src/color.cpp + src/common.cpp + src/intersection-data.cpp src/intersection.cpp src/intersections.cpp src/material.cpp diff --git a/raytracing/include/raytracing.h b/raytracing/include/raytracing.h index e590567..cf71cab 100644 --- a/raytracing/include/raytracing.h +++ b/raytracing/include/raytracing.h @@ -28,6 +28,7 @@ #include "canvas.h" #include "color.h" #include "common.h" +#include "intersection-data.h" #include "intersection.h" #include "intersections.h" #include "material.h" @@ -36,4 +37,4 @@ #include "ray.h" #include "sphere.h" #include "tuple.h" -#include "world.h" +#include "world.h" \ No newline at end of file diff --git a/raytracing/src/intersection-data.cpp b/raytracing/src/intersection-data.cpp new file mode 100644 index 0000000..519308f --- /dev/null +++ b/raytracing/src/intersection-data.cpp @@ -0,0 +1,131 @@ +/*! + * intersection-data.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: 21/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 "intersection-data.h" + +using namespace Raytracer; + +/* ------------------------------------------------------------------------- */ + +IntersectionData::IntersectionData(void) : m_is_inside(false), m_distance(0) +{ +} + +/* ------------------------------------------------------------------------- */ + +double IntersectionData::distance_t(void) const +{ + return m_distance; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_distance_t(double a_value) +{ + m_distance = a_value; +} + +/* ------------------------------------------------------------------------- */ + +const Shape &IntersectionData::object(void) const +{ + return m_shape; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_object(const Shape &a_shape) +{ + m_shape = a_shape; +} + +/* ------------------------------------------------------------------------- */ + +const Tuple &IntersectionData::point(void) const +{ + return m_point; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_point(const Tuple &a_point) +{ + m_point = a_point; +} + +/* ------------------------------------------------------------------------- */ + +const Tuple &IntersectionData::eyev(void) const +{ + return m_eyev; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_eyev(const Tuple &an_eyev) +{ + m_eyev = an_eyev; +} + +/* ------------------------------------------------------------------------- */ + +const Tuple &IntersectionData::normalv(void) const +{ + return m_normalv; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_normalv(const Tuple &a_normalv) +{ + m_normalv = a_normalv; +} + +/* ------------------------------------------------------------------------- */ + +const bool IntersectionData::is_inside(void) const +{ + return m_is_inside; +} + +/* ------------------------------------------------------------------------- */ + +void IntersectionData::set_inside(void) +{ + if (m_normalv.dot(m_eyev) < 0) + { + m_is_inside = true; + m_normalv = -m_normalv; + } + else + { + m_is_inside = false; + } +} diff --git a/raytracing/src/intersection-data.h b/raytracing/src/intersection-data.h new file mode 100644 index 0000000..403ebf3 --- /dev/null +++ b/raytracing/src/intersection-data.h @@ -0,0 +1,73 @@ +/*! + * intersection-data.h + * + * 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: 21/02/2024 + * + */ + +#ifndef _RAYTRACER_INTERSECTION_DATA_H +#define _RAYTRACER_INTERSECTION_DATA_H + +/* ------------------------------------------------------------------------- */ + +#include "shape.h" +#include "tuple.h" + +/* ------------------------------------------------------------------------- */ +namespace Raytracer +{ + /* ------------------------------------------------------------------------- */ + + class IntersectionData + { + public: + IntersectionData(void); + + double distance_t(void) const; + void set_distance_t(double a_value); + + const Shape &object(void) const; + void set_object(const Shape &a_shape); + + const Tuple &point(void) const; + void set_point(const Tuple &a_point); + + const Tuple &eyev(void) const; + void set_eyev(const Tuple &an_eyev); + + const Tuple &normalv(void) const; + void set_normalv(const Tuple &a_normalv); + + const bool is_inside(void) const; + void set_inside(void); + + private: + bool m_is_inside; + double m_distance; + Shape m_shape; // TODO ?? + Tuple m_point; + Tuple m_eyev; + Tuple m_normalv; + }; + +}; // namespace Raytracer + +#endif /* _RAYTRACER_INTERSECTION_DATA_H */ diff --git a/raytracing/src/intersection.cpp b/raytracing/src/intersection.cpp index 869c453..16f3383 100644 --- a/raytracing/src/intersection.cpp +++ b/raytracing/src/intersection.cpp @@ -159,3 +159,22 @@ bool Intersection::is_defined(void) { return !m_is_nothing; } + +/* ------------------------------------------------------------------------- */ + +IntersectionData Intersection::prepare_computations(Ray a_ray) const +{ + IntersectionData the_data; + + // Copy intersections properties for conveniance + the_data.set_distance_t(m_distance_t); + the_data.set_object(m_shape); + + // Precompute some useful values + the_data.set_point(a_ray.position(m_distance_t)); + the_data.set_eyev(-a_ray.direction()); + the_data.set_normalv(m_shape.normal_at(the_data.point())); + the_data.set_inside(); + + return the_data; +} diff --git a/raytracing/src/intersection.h b/raytracing/src/intersection.h index fae3b52..ff6328c 100644 --- a/raytracing/src/intersection.h +++ b/raytracing/src/intersection.h @@ -28,6 +28,7 @@ /* ------------------------------------------------------------------------- */ +#include "intersection-data.h" #include "shape.h" /* ------------------------------------------------------------------------- */ @@ -59,6 +60,8 @@ namespace Raytracer bool is_nothing(void); bool is_defined(void); + IntersectionData prepare_computations(Ray a_ray) const; + private: bool m_is_nothing; double m_distance_t; diff --git a/raytracing/src/world.cpp b/raytracing/src/world.cpp index d87f1de..9cc9b3b 100644 --- a/raytracing/src/world.cpp +++ b/raytracing/src/world.cpp @@ -117,6 +117,13 @@ void World::add_object(Shape *a_shape) /* ------------------------------------------------------------------------- */ +Shape *World::objects(uint32_t an_index) +{ + return m_objects[an_index]; +} + +/* ------------------------------------------------------------------------- */ + bool World::contains(const Shape &a_shape) { @@ -148,14 +155,23 @@ Intersections World::intersect_world(const Ray &a_ray) /* ------------------------------------------------------------------------- */ +Color World::shade_hit(const IntersectionData &an_intersection_data) +{ + return an_intersection_data.object().material().lighting( + m_light, an_intersection_data.point(), an_intersection_data.eyev(), an_intersection_data.normalv()); +} + +/* ------------------------------------------------------------------------- */ + World World::default_world(void) { World the_world; Sphere *s1, *s2; - the_world.set_light(PointLight(Tuple::Point(-10, 10, 10), Color::White())); + the_world.set_light(PointLight(Tuple::Point(-10, 10, -10), Color::White())); + + s1 = new Sphere; - s1 = new Sphere; Material &the_s1_mat = s1->material(); the_s1_mat.set_color(Color(0.8, 1.0, 0.6)); the_s1_mat.set_diffuse(0.7); diff --git a/raytracing/src/world.h b/raytracing/src/world.h index 91c83b7..8f55e64 100644 --- a/raytracing/src/world.h +++ b/raytracing/src/world.h @@ -56,9 +56,12 @@ namespace Raytracer void set_light(const PointLight &a_light); void add_object(Shape *a_shape); + Shape *objects(uint32_t an_index); + bool contains(const Shape &a_shape); Intersections intersect_world(const Ray &a_ray); + Color shade_hit(const IntersectionData &an_intersection_data); static World default_world(void); diff --git a/tests/07_making_scene.cpp b/tests/07_making_scene.cpp index 6770800..52d1af1 100644 --- a/tests/07_making_scene.cpp +++ b/tests/07_making_scene.cpp @@ -53,9 +53,9 @@ SCENARIO("Creating a world", "[features/world.feature]") SCENARIO("The default world", "[features/world.feature]") { - GIVEN("light <- point_light(point(-10, 10, 10), color(1, 1, 1))") + GIVEN("light <- point_light(point(-10, 10, -10), color(1, 1, 1))") { - PointLight light = PointLight(Tuple::Point(-10, 10, 10), Color(1, 1, 1)); + PointLight light = PointLight(Tuple::Point(-10, 10, -10), Color(1, 1, 1)); AND_GIVEN("s1 <- sphere") // with: // | material.color | (0.8, 1.0, 0.6) | @@ -130,3 +130,181 @@ SCENARIO("Intersect a world with a ray", "[features/world.feature]") } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Precompute the state of an intersection", "[features/intersections.feature]") +{ + GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- sphere()") + { + Sphere shape; + AND_GIVEN("i <- intersection(4, shape)") + { + Intersection i(4, shape); + WHEN("comps <- prepare_computations(i,r)") + { + IntersectionData comps = i.prepare_computations(r); + THEN("comps.t = i.t") + { + REQUIRE(comps.distance_t() == i.distance_t()); + } + AND_THEN("comps.object = i.object") + { + REQUIRE(comps.object() == i.object()); + } + AND_THEN("comps.point = point(0, 0, -1)") + { + REQUIRE(comps.point() == Tuple::Point(0, 0, -1)); + } + AND_THEN("comps.eyev = vector(0, 0, -1)") + { + REQUIRE(comps.eyev() == Tuple::Vector(0, 0, -1)); + } + AND_THEN("comps.normalv = vector(0, 0, -1)") + { + REQUIRE(comps.normalv() == Tuple::Vector(0, 0, -1)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The hit, when an intersection occurs on the outside", "[features/intersections.feature]") +{ + GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- sphere()") + { + Sphere shape; + AND_GIVEN("i <- intersection(4, shape)") + { + Intersection i(4, shape); + WHEN("comps <- prepare_computations(i,r)") + { + IntersectionData comps = i.prepare_computations(r); + THEN("comps.inside = false") + { + REQUIRE(comps.is_inside() == false); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The hit, when an intersection occurs on the inside", "[features/intersections.feature]") +{ + GIVEN("r <- ray(point(0, 0, 0), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- sphere()") + { + Sphere shape; + AND_GIVEN("i <- intersection(1, shape)") + { + Intersection i(1, shape); + WHEN("comps <- prepare_computations(i,r)") + { + IntersectionData comps = i.prepare_computations(r); + THEN("comps.point = point(0, 0, 1)") + { + REQUIRE(comps.point() == Tuple::Point(0, 0, 1)); + } + AND_THEN("comps.eyev = vector(0, 0, -1)") + { + REQUIRE(comps.eyev() == Tuple::Vector(0, 0, -1)); + } + AND_THEN("comps.inside = false") + { + REQUIRE(comps.is_inside() == true); + } + AND_THEN("comps.normalv = vector(0, 0, -1)") + { + REQUIRE(comps.normalv() == Tuple::Vector(0, 0, -1)); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Shading an intersection", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("r <- ray(point(0, 0, -5), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- first object of w") + { + Shape *shape = w.objects(0); + AND_GIVEN("i <- intersection(4, shape)") + { + Intersection i(4, *shape); + WHEN("comps <- prepare_computations(i,r)") + { + IntersectionData comps = i.prepare_computations(r); + AND_WHEN("c <- shade_hit(w, comps)") + { + Color c = w.shade_hit(comps); + THEN("c = color(0.38066, 0.47583, 0.2855)") + { + REQUIRE(c == Color(0.38066, 0.47583, 0.2855)); + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Shading an intersection from the inside", "[features/world.feature]") +{ + GIVEN("w <- default_world()") + { + World w = World::default_world(); + AND_GIVEN("w.light <- point_light(point(0, 0.25, 0))") + { + w.set_light(PointLight(Tuple::Point(0, 0.25, 0), Color(1, 1, 1))); + AND_GIVEN("r <- ray(point(0, 0, 0), vector(0, 0, 1))") + { + Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 1)); + AND_GIVEN("shape <- second object of w") + { + Shape *shape = w.objects(1); + AND_GIVEN("i <- intersection(0.5, shape)") + { + Intersection i(0.5, *shape); + WHEN("comps <- prepare_computations(i,r)") + { + IntersectionData comps = i.prepare_computations(r); + AND_WHEN("c <- shade_hit(w, comps)") + { + Color c = w.shade_hit(comps); + THEN("c = color(0.90498, 0.90498, 0.90498)") + { + REQUIRE(c == Color(0.90498, 0.90498, 0.90498)); + } + } + } + } + } + } + } + } +}