diff --git a/source/include/intersection.h b/source/include/intersection.h index 8491efc..d4ebc6a 100644 --- a/source/include/intersection.h +++ b/source/include/intersection.h @@ -18,14 +18,16 @@ class Intersect; struct Computation { Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP, - bool inside, Tuple reflectV = Vector(0, 0, 0), double n1 = 1.0, double n2 = 1.0) : + bool inside, Tuple reflectV = Vector(0, 0, 0), double n1 = 1.0, double n2 = 1.0, + Tuple underHitP = Point(0, 0, 0)) : object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), - overHitPoint(overHitP), reflectVector(reflectV), n1(n1), n2(n2) { }; + overHitPoint(overHitP), underHitPoint(underHitP), reflectVector(reflectV), n1(n1), n2(n2) { }; Shape *object; double t; Tuple hitPoint; Tuple overHitPoint; + Tuple underHitPoint; Tuple eyeVector; Tuple normalVector; Tuple reflectVector; diff --git a/source/include/list.h b/source/include/list.h index b0a0e00..b3ad96d 100644 --- a/source/include/list.h +++ b/source/include/list.h @@ -44,13 +44,31 @@ public: { ChainList *p = this->head; if (p == nullptr) { return; } + + if ((p->next == nullptr) && (p->shape == s)) + { + /* First element */ + this->tail = nullptr; + free(this->head); + this->head = nullptr; + this->count = 0; + + return; + } + while(p->next != nullptr) { if (p->next->shape == s) { - this->count --; + ChainList *found = p->next; + p->next = p->next->next; - free(p->next); + + free(found); + + if (p->next == NULL) { this->tail = p; } + + this->count --; return; } p = p->next; @@ -64,10 +82,10 @@ public: theNew->shape = s; ChainList *p = this->tail; - tail = theNew; + this->tail = theNew; if (p != nullptr) { p->next = theNew; } - else { head = theNew; } /* If the tail is empty, it mean the list IS empty. */ + else { this->head = theNew; } /* If the tail is empty, it mean the list IS empty. */ this->count ++; } diff --git a/source/include/world.h b/source/include/world.h index e5a82ee..cf4f31c 100644 --- a/source/include/world.h +++ b/source/include/world.h @@ -47,6 +47,7 @@ public: bool isShadowed(Tuple point); Colour reflectColour(Computation comps, uint32_t depthCount = 4); + Colour refractedColour(Computation comps, uint32_t depthCount = 0); Intersect intersect(Ray r); diff --git a/source/intersection.cpp b/source/intersection.cpp index 0bbff19..1ad20cf 100644 --- a/source/intersection.cpp +++ b/source/intersection.cpp @@ -27,18 +27,18 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) } Tuple overHitP = hitP + normalV * getEpsilon(); + Tuple underHitP = hitP - normalV * getEpsilon(); Tuple reflectV = r.direction.reflect(normalV); if (xs != nullptr) { List containers; int j, k; - Intersection hit = xs->hit(); for(j = 0; j < xs->count(); j++) { Intersection i = (*xs)[j]; - if (hit == i) + if (*this == i) { if (!containers.isEmpty()) { @@ -55,12 +55,11 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) containers.append(i.object); } - if (hit == i) + if (*this == i) { if (!containers.isEmpty()) { - Shape *cur = containers.last(); - n2 = cur->material.refractiveIndex; + n2 = containers.last()->material.refractiveIndex; } /* End the loop */ @@ -78,5 +77,6 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) inside, reflectV, n1, - n2); + n2, + underHitP); } \ No newline at end of file diff --git a/source/world.cpp b/source/world.cpp index 7e28720..103724f 100644 --- a/source/world.cpp +++ b/source/world.cpp @@ -102,8 +102,9 @@ Tuple World::shadeHit(Computation comps, uint32_t depthCount) comps.normalVector, comps.object, isThereAnObstacle); Tuple reflected = this->reflectColour(comps, depthCount); + Tuple refracted = this->refractedColour(comps, depthCount); - return surface + reflected; + return surface + reflected + refracted; } Tuple World::colourAt(Ray r, uint32_t depthCount) @@ -156,4 +157,24 @@ Colour World::reflectColour(Computation comps, uint32_t depthCount) return Colour(hitColour.x, hitColour.y, hitColour.z); } +Colour World::refractedColour(Computation comps, uint32_t depthCount) +{ + double nRatio = comps.n1 / comps.n2; + double cos_i = comps.eyeVector.dot(comps.normalVector); + double sin2_t = (nRatio*nRatio) * (1 - cos_i * cos_i); + if ((sin2_t > 1 ) || (depthCount == 0) || (comps.object->material.transparency == 0)) + { + return Colour(0, 0, 0); + } + + double cos_t = sqrt(1.0 - sin2_t); + Tuple direction = comps.normalVector * (nRatio * cos_i - cos_t) - comps.eyeVector * nRatio; + + Ray refractedRay = Ray(comps.underHitPoint, direction); + + Tuple hitColour = this->colourAt(refractedRay, depthCount - 1) * comps.object->material.transparency; + + + return Colour(hitColour.x, hitColour.y, hitColour.z); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7053c7a..fd5a7e6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,9 +49,15 @@ target_include_directories(ch11_reflection PUBLIC ../source/include) target_sources(ch11_reflection PRIVATE ch11_reflection.cpp) target_link_libraries(ch11_reflection rayonnement) +add_executable(ch11_refractiontest) +target_include_directories(ch11_refractiontest PUBLIC ../source/include) +target_sources(ch11_refractiontest PRIVATE ch11_refractiontest.cpp) +target_link_libraries(ch11_refractiontest rayonnement) + add_test(NAME Chapter05_Test COMMAND $) add_test(NAME Chapter06_Test COMMAND $) add_test(NAME Chapter07_Test COMMAND $) add_test(NAME Chapter09_Test COMMAND $) add_test(NAME Chapter10_Test COMMAND $) add_test(NAME Chapter11_Reflection COMMAND $) +add_test(NAME Chapter11_RefractionTest COMMAND $) diff --git a/tests/ch11_refractiontest.cpp b/tests/ch11_refractiontest.cpp new file mode 100644 index 0000000..03728e6 --- /dev/null +++ b/tests/ch11_refractiontest.cpp @@ -0,0 +1,76 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Render test for reflection in chapter 11. + * + * Created by Manoƫl Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +int main() +{ + World w = World(); + + /* First we need to construct the world */ + Plane floor = Plane(); + floor.material.specular = 0; + floor.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0, 0, 0)); + floor.material.pattern->setTransform(scaling(0.7, 0.7, 0.7)); + floor.setTransform(translation(0, -3, 0)); + w.addObject(&floor); + + /* Add some more reflective spheres */ + Sphere glassBall = Sphere(); + glassBall.setTransform(scaling(1.5, 1.5, 1.5)); + glassBall.material.refractiveIndex = 1.5; + glassBall.material.transparency = 1.0; + w.addObject(&glassBall); + + Sphere airBubble = Sphere(); + airBubble.setTransform( scaling(0.9, 0.9, 0.9)); + airBubble.material.specular = 0; + airBubble.material.ambient = 0; + airBubble.material.refractiveIndex = 1.00029; + airBubble.material.transparency = 1.0; + w.addObject(&airBubble); + + /* Add light */ + Light light = Light(POINT_LIGHT, Point(-10, 20, -10), Colour(1, 1, 1)); + + w.addLight(&light); + + /* Set the camera */ + Camera camera = Camera(1000, 1000, M_PI / 3); +#if 0 + camera.setTransform(viewTransform(Point(0, 1.5, -5), + Point(0, 1, 0), + Vector(0, 1, 0))); +#else + camera.setTransform(viewTransform(Point(0, 3.6, 0), + Point(0, 0, 0), + Vector(0, 0, 1))); +#endif + /* Now render it */ + Canvas image = camera.render(w); + + image.SaveAsPNG("ch11_refractiontest.png"); + + + return 0; +} \ No newline at end of file diff --git a/tests/intersect_test.cpp b/tests/intersect_test.cpp index 9f1228c..2beed0c 100644 --- a/tests/intersect_test.cpp +++ b/tests/intersect_test.cpp @@ -205,7 +205,6 @@ TEST(IntersectTest, Precomputing_the_reflection_vector) TEST(IntersectTest, Finding_n1_and_n2_at_various_intersections) { -#if 0 int i; double n1_res[6] = { 1.0, 1.5, 2.0, 2.5, 2.5, 1.5 }; double n2_res[6] = { 1.5, 2.0, 2.5, 2.5, 1.5, 1.0 }; @@ -233,9 +232,25 @@ TEST(IntersectTest, Finding_n1_and_n2_at_various_intersections) for(i = 0; i < xs.count(); i++) { - Computation comps = xs[i].prepareComputation(r, &xs); + Intersection inter = xs[i]; + Computation comps = inter.prepareComputation(r, &xs); ASSERT_EQ(comps.n1, n1_res[i]); ASSERT_EQ(comps.n2, n2_res[i]); } -#endif } + +TEST(IntersectTest, The_under_point_is_offset_below_the_surface) +{ + Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1)); + GlassSphere shape = GlassSphere(); + shape.setTransform(translation(0, 0, 1)); + + Intersection i = Intersection(5, &shape); + Intersect xs = Intersect(); + xs.add(i); + + Computation comps = i.prepareComputation(r, &xs); + + ASSERT_TRUE(double_equal(comps.underHitPoint.z, getEpsilon() / 2)); + ASSERT_LT(comps.hitPoint.z, comps.underHitPoint.z); +} \ No newline at end of file diff --git a/tests/world_test.cpp b/tests/world_test.cpp index dff79a9..2752709 100644 --- a/tests/world_test.cpp +++ b/tests/world_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,8 @@ TEST(WorldTest, Colour_at_with_mutually_reflective_surfaces) /* It should just exit, we don't care about the actual colour */ w.colourAt(r); + + SUCCEED(); } TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth) @@ -273,4 +276,120 @@ TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth) /* Temporary lower the precision */ ASSERT_EQ(colour, Colour(0, 0, 0)); +} + +TEST(WorldTest, The_refracted_colour_with_an_opaque_surface) +{ + World w = DefaultWorld(); + Shape *shape = w.getObject(0); + + Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1)); + Intersect xs = Intersect(); + xs.add(Intersection(4, shape)); + xs.add(Intersection(6, shape)); + + Computation comps = xs[0].prepareComputation(r, &xs); + Colour c = w.refractedColour(comps, 5); + + ASSERT_EQ(c, Colour(0, 0, 0)); +} + +TEST(WorldTest, The_refracted_colour_at_the_maximum_recursive_depth) +{ + World w = DefaultWorld(); + Shape *shape = w.getObject(0); + + shape->material.transparency = 1.0; + shape->material.refractiveIndex = 1.5; + + Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1)); + Intersect xs = Intersect(); + xs.add(Intersection(4, shape)); + xs.add(Intersection(6, shape)); + + Computation comps = xs[0].prepareComputation(r, &xs); + Colour c = w.refractedColour(comps, 0); + + ASSERT_EQ(c, Colour(0, 0, 0)); +} + +TEST(WorldTest, The_refracted_colour_under_total_internal_reflection) +{ + World w = DefaultWorld(); + Shape *shape = w.getObject(0); + + shape->material.transparency = 1.0; + shape->material.refractiveIndex = 1.5; + + Ray r = Ray(Point(0, 0, sqrt(2)/2), Vector(0, 1, 0)); + Intersect xs = Intersect(); + xs.add(Intersection(-sqrt(2)/2, shape)); + xs.add(Intersection(sqrt(2)/2, shape)); + + Computation comps = xs[1].prepareComputation(r, &xs); + Colour c = w.refractedColour(comps, 5); + + ASSERT_EQ(c, Colour(0, 0, 0)); +} + +TEST(WorldTest, The_refracted_coloud_with_a_refracted_ray) +{ + World w = DefaultWorld(); + + Shape *A = w.getObject(0); + A->material.ambient = 1.0; + A->material.pattern = new TestPattern(); + + Shape *B = w.getObject(1); + B->material.transparency = 1.0; + B->material.refractiveIndex = 1.5; + + Ray r = Ray(Point(0, 0, 0.1), Vector(0, 1, 0)); + Intersect xs = Intersect(); + xs.add(Intersection(-0.9899, A)); + xs.add(Intersection(-0.4899, B)); + xs.add(Intersection(0.4899, B)); + xs.add(Intersection(0.9899, A)); + + Computation comps = xs[2].prepareComputation(r, &xs); + Colour c = w.refractedColour(comps, 5); + + /* Temporary lower the precision */ + set_equal_precision(0.00005); + + ASSERT_EQ(c, Colour(0, 0.99888, 0.04725)); + + set_equal_precision(FLT_EPSILON); +} + +TEST(WorldTest, Shade_hit_with_a_transparent_material) +{ + World w = DefaultWorld(); + + Plane floor = Plane(); + floor.setTransform(translation(0, -1, 0)); + floor.material.transparency = 0.5; + floor.material.refractiveIndex = 1.5; + w.addObject(&floor); + + Sphere ball = Sphere(); + ball.material.colour = Colour(1, 0, 0); + ball.material.ambient = 0.5; + ball.setTransform(translation(0, -3.5, -0.5)); + w.addObject(&ball); + + Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2)); + Intersect xs = Intersect(); + xs.add(Intersection(sqrt(2), &floor)); + + Computation comps = xs[0].prepareComputation(r, &xs); + + Tuple c = w.shadeHit(comps, 5); + + /* Temporary lower the precision */ + set_equal_precision(0.00001); + + ASSERT_EQ(c, Colour(0.93642, 0.68642, 0.68642)); + + set_equal_precision(FLT_EPSILON); } \ No newline at end of file