diff --git a/raytracing/src/shape.cpp b/raytracing/src/shape.cpp index fc573f8..d31bb7e 100644 --- a/raytracing/src/shape.cpp +++ b/raytracing/src/shape.cpp @@ -128,16 +128,32 @@ void Shape::set_material(const Material &a_material) Tuple Shape::normal_at(const Tuple &a_world_point) const { - Tuple the_object_point = m_transform.inverse() * a_world_point; - Tuple the_object_normal = the_object_point - Tuple::Point(0, 0, 0); - Tuple the_world_normal = m_transform.inverse().transpose() * the_object_normal; + Tuple the_local_point = m_transform.inverse() * a_world_point; + Tuple the_local_normal = local_normal_at(the_local_point); + Tuple the_world_normal = m_transform.inverse().transpose() * the_local_normal; the_world_normal.set_w(0); return the_world_normal.normalize(); } /* ------------------------------------------------------------------------- */ -Intersections Shape::intersect(const Ray &a_ray) const +Tuple Shape::local_normal_at(const Tuple &a_local_point) const +{ + return a_local_point; +} + +/* ------------------------------------------------------------------------- */ + +Intersections Shape::intersect(const Ray &a_ray) +{ + Ray the_ray = a_ray.transform(transform().inverse()); + + return local_intersect(the_ray); +} + +/* ------------------------------------------------------------------------- */ + +Intersections Shape::local_intersect(const Ray &a_ray) { Intersections the_ret; diff --git a/raytracing/src/shape.h b/raytracing/src/shape.h index 87cd1b9..b1e6450 100644 --- a/raytracing/src/shape.h +++ b/raytracing/src/shape.h @@ -61,8 +61,10 @@ namespace Raytracer void set_material(const Material &a_material); Tuple normal_at(const Tuple &a_point) const; + virtual Tuple local_normal_at(const Tuple &a_local_point) const; - virtual Intersections intersect(const Ray &a_ray) const; + Intersections intersect(const Ray &a_ray); + virtual Intersections local_intersect(const Ray &a_ray); protected: void inc_id(void); diff --git a/raytracing/src/sphere.cpp b/raytracing/src/sphere.cpp index a4c1a6d..1a667e7 100644 --- a/raytracing/src/sphere.cpp +++ b/raytracing/src/sphere.cpp @@ -46,13 +46,11 @@ Sphere::Sphere(void) /* ------------------------------------------------------------------------- */ -Intersections Sphere::intersect(const Ray &a_ray) const +Intersections Sphere::local_intersect(const Ray &a_ray) { Intersections the_intersections; - Ray the_ray = a_ray.transform(transform().inverse()); - - Tuple the_sphere_to_ray = the_ray.origin() - Tuple::Point(0, 0, 0); - Tuple the_direction = the_ray.direction(); + Tuple the_sphere_to_ray = a_ray.origin() - Tuple::Point(0, 0, 0); + Tuple the_direction = a_ray.direction(); double the_a = the_direction.dot(the_direction); double the_b = 2 * the_direction.dot(the_sphere_to_ray); @@ -69,3 +67,10 @@ Intersections Sphere::intersect(const Ray &a_ray) const return the_intersections; } + +/* ------------------------------------------------------------------------- */ + +Tuple Sphere::local_normal_at(const Tuple &a_local_point) const +{ + return a_local_point - Tuple::Point(0, 0, 0); +} diff --git a/raytracing/src/sphere.h b/raytracing/src/sphere.h index ec532a6..768b2b5 100644 --- a/raytracing/src/sphere.h +++ b/raytracing/src/sphere.h @@ -38,7 +38,8 @@ namespace Raytracer { public: Sphere(void); - Intersections intersect(const Ray &a_ray) const override; + Intersections local_intersect(const Ray &a_ray) override; + Tuple local_normal_at(const Tuple &a_local_point) const override; }; }; // namespace Raytracer diff --git a/tests/09_planes.cpp b/tests/09_planes.cpp new file mode 100644 index 0000000..b7422dc --- /dev/null +++ b/tests/09_planes.cpp @@ -0,0 +1,246 @@ +/*! + * 09_planes.cpp + * + * Copyright (c) 2015-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: 27/02/2024 + * + */ + +/*---------------------------------------------------------------------------*/ + +#include + +#include "raytracing.h" + +using namespace Raytracer; + +/* ------------------------------------------------------------------------- */ + +class TestShape : public Shape +{ +public: + TestShape(void) = default; + + Intersections local_intersect(const Ray &a_ray) override + { + saved_ray = a_ray; + return Intersections(); + } + + Tuple local_normal_at(const Tuple &a_local_point) const override + { + return Tuple::Vector(a_local_point.x(), a_local_point.y(), a_local_point.z()); + } + + Ray saved_ray; +}; + +/* ------------------------------------------------------------------------- */ + +TestShape test_shape(void) +{ + return TestShape(); +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The default transformation", "[features/shapes.feature]") +{ + GIVEN("s <- test_shape()") + { + TestShape s; + THEN("s.transform = identity_matrix") + { + REQUIRE(s.transform() == Matrix::identity()); + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Assigning a transformation", "[features/shapes.feature]") +{ + GIVEN("s <- test_shape()") + { + TestShape s; + WHEN("set_transform(s, translation(2, 3, 4))") + { + s.set_transform(Matrix::translation(2, 3, 4)); + THEN("s.transform = translation(2, 3, 4)") + { + REQUIRE(s.transform() == Matrix::translation(2, 3, 4)); + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("The default material (shape)", "[features/shapes.feature]") +{ + GIVEN("s <- test_shape()") + { + TestShape s; + WHEN("m <- s.material()") + { + Material m = s.material(); + THEN("m = material()") + { + REQUIRE(m == Material()); + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Assigning a material", "[features/shapes.feature]") +{ + GIVEN("s <- test_shape()") + { + TestShape s; + AND_GIVEN("m = material()") + { + Material m; + AND_GIVEN("m.ambient = 1") + { + m.set_ambient(1); + WHEN("s.material <- m") + { + s.set_material(m); + THEN("s.material = m") + { + REQUIRE(s.material() == m); + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a scaled shape with a ray", "[features/shapes.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("s <- test_shape()") + { + TestShape *s = new TestShape; + WHEN("set_transform(s,scaling(2, 2, 2))") + { + s->set_transform(Matrix::scaling(2, 2, 2)); + AND_WHEN("xs <- intersect(s,r)") + { + Intersections xs = s->intersect(r); + THEN("s.saved_ray.origin = point(0, 0, -2.5)") + { + REQUIRE(s->saved_ray.origin() == Tuple::Point(0, 0, -2.5)); + } + AND_THEN("s.saved_ray.direction = vector(0, 0, 0.5)") + { + REQUIRE(s->saved_ray.direction() == Tuple::Vector(0, 0, 0.5)); + } + } + } + delete s; + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a translated shape with a ray", "[features/shapes.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("s <- test_shape()") + { + TestShape *s = new TestShape; + WHEN("set_transform(s,translation(5, 0, 0))") + { + s->set_transform(Matrix::translation(5, 0, 0)); + AND_WHEN("xs <- intersect(s,r)") + { + Intersections xs = s->intersect(r); + THEN("s.saved_ray.origin = point(-5, 0, -5)") + { + REQUIRE(s->saved_ray.origin() == Tuple::Point(-5, 0, -5)); + } + AND_THEN("s.saved_ray.direction = vector(0, 0, 1)") + { + REQUIRE(s->saved_ray.direction() == Tuple::Vector(0, 0, 1)); + } + } + } + delete s; + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Computing the normal on a translated shape", "[features/shapes.feature]") +{ + AND_GIVEN("s <- test_shape()") + { + TestShape s; + WHEN("set_transform(s,translation(0, 1, 0))") + { + s.set_transform(Matrix::translation(0, 1, 0)); + AND_WHEN("n <- normal_at(s, point(0, 1.70711, -0.70711))") + { + Tuple n = s.normal_at(Tuple::Point(0, 1.70711, -0.70711)); + THEN("n = vector(0, 0.70711, -0.70711)") + { + REQUIRE(n == Tuple::Vector(0, 0.70711, -0.70711)); + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Computing the normal on a transformed shape", "[features/shapes.feature]") +{ + AND_GIVEN("s <- test_shape()") + { + TestShape s; + AND_GIVEN("m <- scaling(1, 0.5, 1) * rotation_z(pi/5)") + { + Matrix m = Matrix::scaling(1, 0.5, 1) * Matrix::rotation_z(std::numbers::pi / 5); + WHEN("set_transform(m)") + { + s.set_transform(m); + AND_WHEN("n <- normal_at(s, point(0, sqrt(2)/2, -sqrt(2)/2))") + { + Tuple n = s.normal_at(Tuple::Point(0, sqrt(2) / 2, -sqrt(2) / 2)); + THEN("n = vector(0, 0.97014, -0.24254)") + { + REQUIRE(n == Tuple::Vector(0, 0.97014, -0.24254)); + } + } + } + } + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c09bac..fffcdb6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(raytracing_test 06_light_shading.cpp 07_making_scene.cpp 08_shadows.cpp + 09_planes.cpp ) include_directories("${CMAKE_SOURCE_DIR}/tests")