From 649ac3880d8e7bda8f9a8eb8750a7081886d0896 Mon Sep 17 00:00:00 2001 From: NADAL Jean-Baptiste Date: Fri, 22 Mar 2024 19:09:55 +0100 Subject: [PATCH] [ADD] add group --- raytracing/src/patterns/pattern.cpp | 2 +- raytracing/src/shapes/group.cpp | 42 +++- raytracing/src/shapes/group.h | 6 +- raytracing/src/shapes/shape.cpp | 40 +++- raytracing/src/shapes/shape.h | 3 + tests/14_groups.cpp | 298 ++++++++++++++++++++++++++++ 6 files changed, 381 insertions(+), 10 deletions(-) diff --git a/raytracing/src/patterns/pattern.cpp b/raytracing/src/patterns/pattern.cpp index 25d3c5a..c1977eb 100644 --- a/raytracing/src/patterns/pattern.cpp +++ b/raytracing/src/patterns/pattern.cpp @@ -93,7 +93,7 @@ const Color &Pattern::b(void) const const Color Pattern::pattern_at_shape(Shape *a_shape, const Tuple &a_world_point) const { - Tuple the_objet_point = a_shape->transform().inverse() * a_world_point; + Tuple the_objet_point = a_shape->world_to_object(a_world_point); Tuple the_pattern_point = m_transform.inverse() * the_objet_point; return pattern_at(the_pattern_point); diff --git a/raytracing/src/shapes/group.cpp b/raytracing/src/shapes/group.cpp index 15cdcc5..ba77899 100644 --- a/raytracing/src/shapes/group.cpp +++ b/raytracing/src/shapes/group.cpp @@ -28,7 +28,9 @@ /* ------------------------------------------------------------------------- */ +#include #include +#include #include "core/common.h" #include "core/intersections.h" @@ -41,21 +43,53 @@ using namespace Raytracer; Intersections Group::local_intersect(const Ray &a_ray) { - Intersections the_intersections; + Intersections the_all_intersec; - return the_intersections; + for (auto the_shape : m_shapes) + { + Intersections the_intersection = the_shape->intersect(a_ray); + the_all_intersec += the_intersection; + } + + return the_all_intersec; } /* ------------------------------------------------------------------------- */ Tuple Group::local_normal_at(const Tuple &a_local_point) const { - return Tuple::Vector(0, 1, 0); + printf("ERROR: local_normal_at never need to be called on a group.\n"); + assert(1); + return Tuple::Vector(0, 0, 0); } /* ------------------------------------------------------------------------- */ bool Group::is_empty(void) { - return m_data.size() == 0; + return m_shapes.empty(); +} + +/* ------------------------------------------------------------------------- */ + +void Group::add_child(Shape *a_shape) +{ + a_shape->set_parent(this); + m_shapes.push_back(a_shape); +} + +/* ------------------------------------------------------------------------- */ + +bool Group::contains(const Shape &a_shape) +{ + + for (const auto &the_shape : m_shapes) + { + if (*the_shape == a_shape) + { + return true; + } + } + + return false; } diff --git a/raytracing/src/shapes/group.h b/raytracing/src/shapes/group.h index d54bfe1..bba82be 100644 --- a/raytracing/src/shapes/group.h +++ b/raytracing/src/shapes/group.h @@ -47,8 +47,12 @@ namespace Raytracer bool is_empty(void); + void add_child(Shape *a_shape); + + bool contains(const Shape &a_shape); + private: - std::vector m_data; + std::vector m_shapes; }; }; // namespace Raytracer diff --git a/raytracing/src/shapes/shape.cpp b/raytracing/src/shapes/shape.cpp index cf453c8..3560d2b 100644 --- a/raytracing/src/shapes/shape.cpp +++ b/raytracing/src/shapes/shape.cpp @@ -154,11 +154,10 @@ void Shape::set_parent(Shape *a_shape) Tuple Shape::normal_at(const Tuple &a_world_point) const { - Tuple the_local_point = m_transform.inverse() * a_world_point; + Tuple the_local_point = world_to_object(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(); + + return normal_to_world(the_local_normal); } /* ------------------------------------------------------------------------- */ @@ -185,3 +184,36 @@ Intersections Shape::local_intersect(const Ray &a_ray) return the_ret; } + +/* ------------------------------------------------------------------------- */ + +Tuple Shape::world_to_object(const Tuple &a_point) const +{ + Tuple the_point; + if (m_parent != nullptr) + { + the_point = m_parent->world_to_object(a_point); + } + else + { + the_point = a_point; + } + + return m_transform.inverse() * the_point; +} + +/* ------------------------------------------------------------------------- */ + +Tuple Shape::normal_to_world(const Tuple &a_normal) const +{ + Tuple the_normal = m_transform.inverse().transpose() * a_normal; + the_normal.set_w(0); + the_normal = the_normal.normalize(); + + if (m_parent != nullptr) + { + the_normal = m_parent->normal_to_world(the_normal); + } + + return the_normal; +} diff --git a/raytracing/src/shapes/shape.h b/raytracing/src/shapes/shape.h index 9ce0c98..122a70b 100644 --- a/raytracing/src/shapes/shape.h +++ b/raytracing/src/shapes/shape.h @@ -70,6 +70,9 @@ namespace Raytracer Intersections intersect(const Ray &a_ray); virtual Intersections local_intersect(const Ray &a_ray); + Tuple world_to_object(const Tuple &a_point) const; + Tuple normal_to_world(const Tuple &a_normal) const; + private: Matrix m_transform; Material m_material; diff --git a/tests/14_groups.cpp b/tests/14_groups.cpp index 6650b03..b7be56f 100644 --- a/tests/14_groups.cpp +++ b/tests/14_groups.cpp @@ -64,3 +64,301 @@ SCENARIO("A shape has a parent attribute", "[features/shapes.feature]") } } } + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Adding a child to a group", "[features/groups.feature]") +{ + GIVEN("g <- group()") + { + Group g; + GIVEN("s <- test_shape()") + { + TestShape s; + WHEN("add_child(g, s)") + { + g.add_child(&s); + THEN("g is not empty") + { + REQUIRE(g.is_empty() == false); + } + AND_THEN("g includes s") + { + REQUIRE(g.contains(s) == true); + } + AND_THEN("s.parent = g") + { + REQUIRE(s.parent() == &g); + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a ray with an empty group", "[features/groups.feature]") +{ + GIVEN("g <- group()") + { + Group g; + AND_GIVEN("ray(point(0, 0, 0), vector(0, 0, 1)") + { + Ray r(Tuple::Point(0, 0, 0), Tuple::Vector(0, 0, 1)); + WHEN("xs <-local_intersect(g, r)") + { + Intersections xs = g.local_intersect(r); + THEN("xs is empty") + { + REQUIRE(xs.count() == 0); + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a ray with a nonempty group", "[features/groups.feature]") +{ + GIVEN("g <- group()") + { + Group g; + AND_GIVEN("s1 <- sphere()") + { + Sphere s1; + AND_GIVEN("s2 <- sphere()") + { + Sphere s2; + AND_GIVEN("set_transform(s2, translation(0, 0, -3))") + { + s2.set_transform(Matrix::translation(0, 0, -3)); + AND_GIVEN("s3 <- sphere()") + { + Sphere s3; + AND_GIVEN("set_transform(s3, translation(5, 0, 0))") + { + s3.set_transform(Matrix::translation(5, 0, 0)); + AND_GIVEN("add_child(g, s1)") + { + g.add_child(&s1); + AND_GIVEN("add_child(g, s2)") + { + g.add_child(&s2); + AND_GIVEN("add_child(g, s3)") + { + g.add_child(&s3); + WHEN("ray(point(0, 0, -5), vector(0, 0, 1)") + { + Ray r(Tuple::Point(0, 0, -5), Tuple::Vector(0, 0, 1)); + AND_WHEN("xs <-local_intersect(g, r)") + { + Intersections xs = g.local_intersect(r); + THEN("xs.count = 4") + { + REQUIRE(xs.count() == 4); + } + AND_THEN("xs[0].object = s2") + { + REQUIRE(*xs[0].object() == s2); + } + AND_THEN("xs[1].object = s2") + { + REQUIRE(*xs[1].object() == s2); + } + AND_THEN("xs[2].object = s1") + { + REQUIRE(*xs[2].object() == s1); + } + AND_THEN("xs[3].object = s1") + { + REQUIRE(*xs[3].object() == s1); + } + } + } + } + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Intersecting a transformed group", "[features/groups.feature]") +{ + GIVEN("g <- group()") + { + Group g; + AND_GIVEN("set_transform(g, scaling(2, 2, 2))") + { + g.set_transform(Matrix::scaling(2, 2, 2)); + AND_GIVEN("s <- sphere()") + { + Sphere s; + AND_GIVEN("set_transform(s, translation(5, 0, 0))") + { + s.set_transform(Matrix::translation(5, 0, 0)); + AND_GIVEN("add_child(g, s)") + { + g.add_child(&s); + WHEN("ray(point(10, 0, -10), vector(0, 0, 1)") + { + Ray r(Tuple::Point(10, 0, -10), Tuple::Vector(0, 0, 1)); + AND_WHEN("xs <-intersect(g, r)") + { + Intersections xs = g.intersect(r); + THEN("xs.count = 2") + { + REQUIRE(xs.count() == 2); + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Converting a point from world to object space", "[features/shapes.feature]") +{ + GIVEN("g1 <- group()") + { + Group g1; + AND_GIVEN("set_transform(g1, rotation_y(pi/2))") + { + g1.set_transform(Matrix::rotation_y(std::numbers::pi / 2)); + GIVEN("g2 <- group()") + { + Group g2; + AND_GIVEN("set_transform(g2, scaling(2, 2, 2))") + { + g2.set_transform(Matrix::scaling(2, 2, 2)); + AND_GIVEN("add_child(g1, g2)") + { + g1.add_child(&g2); + AND_GIVEN("s <- sphere()") + { + Sphere s; + AND_GIVEN("set_transform(s, translation(5, 0, 0))") + { + s.set_transform(Matrix::translation(5, 0, 0)); + AND_GIVEN("add_child(g2, s)") + { + g2.add_child(&s); + WHEN("p <- world_to_object(s, point(-2, 0, -10))") + { + Tuple p = s.world_to_object(Tuple::Point(-2, 0, -10)); + THEN("p = point(0, 0, -1)") + { + REQUIRE(p == Tuple::Point(0, 0, -1)); + } + } + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Converting a normal from object to world space", "[features/shapes.feature]") +{ + GIVEN("g1 <- group()") + { + Group g1; + AND_GIVEN("set_transform(g1, rotation_y(pi/2))") + { + g1.set_transform(Matrix::rotation_y(std::numbers::pi / 2)); + GIVEN("g2 <- group()") + { + Group g2; + AND_GIVEN("set_transform(g2, scaling(1, 2, 3))") + { + g2.set_transform(Matrix::scaling(1, 2, 3)); + AND_GIVEN("add_child(g1, g2)") + { + g1.add_child(&g2); + AND_GIVEN("s <- sphere()") + { + Sphere s; + AND_GIVEN("set_transform(s, translation(5, 0, 0))") + { + s.set_transform(Matrix::translation(5, 0, 0)); + AND_GIVEN("add_child(g2, s)") + { + g2.add_child(&s); + WHEN("n <- normal_to_world(s, vector(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3))") + { + Tuple n = s.normal_to_world(Tuple::Vector(sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3)); + THEN("n = vector(0.2857, 0.4286, -0.8571)") + { + REQUIRE(n == Tuple::Vector(0.2857, 0.4286, -0.8571)); + } + } + } + } + } + } + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +SCENARIO("Finding the normal on a child object", "[features/shapes.feature]") +{ + GIVEN("g1 <- group()") + { + Group g1; + AND_GIVEN("set_transform(g1, rotation_y(pi/2))") + { + g1.set_transform(Matrix::rotation_y(std::numbers::pi / 2)); + GIVEN("g2 <- group()") + { + Group g2; + AND_GIVEN("set_transform(g2, scaling(1, 2, 3))") + { + g2.set_transform(Matrix::scaling(1, 2, 3)); + AND_GIVEN("add_child(g1, g2)") + { + g1.add_child(&g2); + AND_GIVEN("s <- sphere()") + { + Sphere s; + AND_GIVEN("set_transform(s, translation(5, 0, 0))") + { + s.set_transform(Matrix::translation(5, 0, 0)); + AND_GIVEN("add_child(g2, s)") + { + g2.add_child(&s); + WHEN("n <- normal_at(s, point(1.7321, 1.1547, -5.5774))") + { + Tuple n = s.normal_at(Tuple::Point(1.7321, 1.1547, -5.5774)); + THEN("n = vector(0.2857, 0.4286, -0.8571)") + { + REQUIRE(n == Tuple::Vector(0.2857, 0.4286, -0.8571)); + } + } + } + } + } + } + } + } + } + } +}