diff --git a/source/include/intersection.h b/source/include/intersection.h index cee7c2c..17447fb 100644 --- a/source/include/intersection.h +++ b/source/include/intersection.h @@ -16,11 +16,13 @@ class Shape; struct Computation { - Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, bool inside) : - object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside) { }; + Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP, bool inside) : + object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside), overHitPoint(overHitP) { }; + Shape *object; double t; Tuple hitPoint; + Tuple overHitPoint; Tuple eyeVector; Tuple normalVector; diff --git a/source/include/material.h b/source/include/material.h index 8ff0d44..549358e 100644 --- a/source/include/material.h +++ b/source/include/material.h @@ -25,7 +25,7 @@ public: public: Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200) {}; - Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector); + Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, bool inShadow = false); bool operator==(const Material &b) const { return double_equal(this->ambient, b.ambient) && double_equal(this->diffuse, b.diffuse) && diff --git a/source/include/math_helper.h b/source/include/math_helper.h index 1e386b3..ff2f0e3 100644 --- a/source/include/math_helper.h +++ b/source/include/math_helper.h @@ -13,6 +13,7 @@ #include void set_equal_precision(double v); +double getEpsilon(); bool double_equal(double a, double b); double deg_to_rad(double deg); diff --git a/source/include/world.h b/source/include/world.h index 9c1697e..c1cca04 100644 --- a/source/include/world.h +++ b/source/include/world.h @@ -43,6 +43,7 @@ public: Tuple shadeHit(Computation comps);; Tuple colourAt(Ray r); + bool isShadowed(Tuple point); Intersect intersect(Ray r); diff --git a/source/intersection.cpp b/source/intersection.cpp index 9cc7cec..be4b705 100644 --- a/source/intersection.cpp +++ b/source/intersection.cpp @@ -22,11 +22,13 @@ Computation Intersection::prepareComputation(Ray r) normalV = -normalV; } + Tuple overHitP = hitP + normalV * getEpsilon(); return Computation(this->object, this->t, hitP, eyeV, normalV, + overHitP, inside); } \ No newline at end of file diff --git a/source/math_helper.cpp b/source/math_helper.cpp index 478e305..b5619d6 100644 --- a/source/math_helper.cpp +++ b/source/math_helper.cpp @@ -18,6 +18,11 @@ void set_equal_precision(double v) current_precision = v; } +double getEpsilon() +{ + return current_precision; +} + bool double_equal(double a, double b) { return fabs(a - b) < current_precision; diff --git a/source/shapes/material.cpp b/source/shapes/material.cpp index 2a62b25..c694e69 100644 --- a/source/shapes/material.cpp +++ b/source/shapes/material.cpp @@ -10,7 +10,7 @@ #include #include -Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector) +Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, bool inShadow) { Tuple lightVector = (light.position - point).normalise(); Tuple reflectVector = Tuple(0, 0, 0, 0); @@ -25,31 +25,33 @@ Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple norma ambientColour = effectiveColour * this->ambient; - lightDotNormal = lightVector.dot(normalVector); - - if (lightDotNormal < 0) + if (!inShadow) { - diffuseColour = Colour(0, 0, 0); - specularColour = Colour(0, 0, 0); - } - else - { - diffuseColour = effectiveColour * this->diffuse * lightDotNormal; - reflectVector = -lightVector.reflect(normalVector); + lightDotNormal = lightVector.dot(normalVector); - reflectDotEye = reflectVector.dot(eyeVector); - - if (reflectDotEye < 0) + if (lightDotNormal < 0) { + diffuseColour = Colour(0, 0, 0); specularColour = Colour(0, 0, 0); } else { - double factor = pow(reflectDotEye, this->shininess); - specularColour = light.intensity * this->specular * factor; + diffuseColour = effectiveColour * this->diffuse * lightDotNormal; + reflectVector = -lightVector.reflect(normalVector); + + reflectDotEye = reflectVector.dot(eyeVector); + + if (reflectDotEye < 0) + { + specularColour = Colour(0, 0, 0); + } + else + { + double factor = pow(reflectDotEye, this->shininess); + specularColour = light.intensity * this->specular * factor; + } } } - finalColour = ambientColour + diffuseColour + specularColour; return Colour(finalColour.x, finalColour.y, finalColour.z); diff --git a/source/world.cpp b/source/world.cpp index cdfdcb2..2f57e72 100644 --- a/source/world.cpp +++ b/source/world.cpp @@ -94,7 +94,12 @@ Intersect World::intersect(Ray r) Tuple World::shadeHit(Computation comps) { - return comps.object->material.lighting(*this->lightList[0], comps.hitPoint, comps.eyeVector, comps.normalVector); + /* TODO: Add support for more than one light */ + + bool isThereAnObstacle = this->isShadowed(comps.overHitPoint); + + return comps.object->material.lighting(*this->lightList[0], comps.overHitPoint, comps.eyeVector, + comps.normalVector, isThereAnObstacle); } Tuple World::colourAt(Ray r) @@ -109,4 +114,23 @@ Tuple World::colourAt(Ray r) { return this->shadeHit(hit.prepareComputation(r)); } -} \ No newline at end of file +} + +bool World::isShadowed(Tuple point) +{ + /* TODO: Add support for more than one light */ + + Tuple v = this->lightList[0]->position - point; + double distance = v.magnitude(); + Tuple direction = v.normalise(); + + Ray r = Ray(point, direction); + Intersection h = this->intersect(r).hit(); + + if (!h.nothing() && h.t < distance) + { + return true; + } + + return false; +} diff --git a/tests/intersect_test.cpp b/tests/intersect_test.cpp index 223558a..4f8b128 100644 --- a/tests/intersect_test.cpp +++ b/tests/intersect_test.cpp @@ -9,9 +9,9 @@ #include #include #include +#include #include - TEST(IntersectTest, Creating_an_intersect_and_do_some_check) { Intersect i; @@ -173,4 +173,20 @@ TEST(IntersectTest, The_hit_when_an_intersection_occurs_on_the_inside) /* Normal vector would have been (0, 0, 1); but is inverted ! */ ASSERT_EQ(comps.normalVector, Vector(0, 0, -1)); +} + +TEST(IntersectTest, The_hit_should_offset_the_point) +{ + Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1)); + Sphere shape = Sphere(); + shape.setTransform(translation(0, 0, 1)); + + Intersection i = Intersection(5, &shape); + + Computation comps = i.prepareComputation(r); + + /* Normal vector would have been (0, 0, 1); but is inverted ! */ + + ASSERT_LT(comps.overHitPoint.z, -getEpsilon() / 2); + ASSERT_GT(comps.hitPoint.z, comps.overHitPoint.z); } \ No newline at end of file diff --git a/tests/material_test.cpp b/tests/material_test.cpp index 08ba89e..e79d4b3 100644 --- a/tests/material_test.cpp +++ b/tests/material_test.cpp @@ -86,5 +86,17 @@ TEST(MaterialTest, Lighting_with_the_light_behind_the_surface) Colour result = m.lighting(light, position, eyev, normalv); + ASSERT_EQ(result, Colour(0.1, 0.1, 0.1)); +} + +TEST(MaterialTest, Lighting_with_the_surface_in_shadow) +{ + Vector eyev = Vector(0, 0, -1); + Vector normalv = Vector(0, 0, -1); + Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1)); + bool inShadow = true; + + Colour result = m.lighting(light, position, eyev, normalv, inShadow); + ASSERT_EQ(result, Colour(0.1, 0.1, 0.1)); } \ No newline at end of file diff --git a/tests/world_test.cpp b/tests/world_test.cpp index 2c7e744..6022fbc 100644 --- a/tests/world_test.cpp +++ b/tests/world_test.cpp @@ -111,4 +111,60 @@ TEST(WorldTest, The_colour_with_an_intersection_behind_the_ray) Tuple c = w.colourAt(r); ASSERT_EQ(c, inner->material.colour); -} \ No newline at end of file +} + +TEST(WorldTest, There_is_no_shadow_when_nothing_is_collinear_with_point_and_light) +{ + World w = DefaultWorld(); + Tuple p = Point(0, 10, 0); + + ASSERT_FALSE(w.isShadowed(p)); +} + +TEST(WorldTest, The_shadow_when_an_object_is_between_the_point_and_the_light) +{ + World w = DefaultWorld(); + Tuple p = Point(10, -10, 10); + + ASSERT_TRUE(w.isShadowed(p)); +} + +TEST(WorldTest, There_is_no_shadow_whne_an_object_is_behing_the_light) +{ + World w = DefaultWorld(); + Tuple p = Point(-20, 20, -20); + + ASSERT_FALSE(w.isShadowed(p)); +} + +TEST(WorldTest, There_is_no_shadow_when_an_object_is_behing_the_point) +{ + World w = DefaultWorld(); + Tuple p = Point(-2, 2, -2); + + ASSERT_FALSE(w.isShadowed(p)); +} + +TEST(WorldTest, Shade_hit_is_given_an_intersection_in_shadow) +{ + World w = World(); + + Light l = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1)); + w.addLight(&l); + + Sphere s1 = Sphere(); + + w.addObject(&s1); + + Sphere s2 = Sphere(); + s2.setTransform(translation(0, 0, 10)); + + w.addObject(&s2); + + Ray r = Ray(Point(0, 0, 5), Vector(0, 0, 1)); + Intersection i = Intersection(4, &s2); + Computation comps = i.prepareComputation(r); + Tuple c = w.shadeHit(comps); + + ASSERT_EQ(c, Colour(0.1, 0.1, 0.1)); +}; \ No newline at end of file