Browse Source

Adding shadows!

Godzil 4 years ago
parent
commit
cf5597ad6d

+ 4 - 2
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;
 

+ 1 - 1
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) &&

+ 1 - 0
source/include/math_helper.h

@@ -13,6 +13,7 @@
 #include <math.h>
 
 void set_equal_precision(double v);
+double getEpsilon();
 bool double_equal(double a, double b);
 
 double deg_to_rad(double deg);

+ 1 - 0
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);
 

+ 2 - 0
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);
 }

+ 5 - 0
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;

+ 19 - 17
source/shapes/material.cpp

@@ -10,7 +10,7 @@
 #include <material.h>
 #include <colour.h>
 
-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)
-    {
-        diffuseColour = Colour(0, 0, 0);
-        specularColour = Colour(0, 0, 0);
-    }
-    else
+    if (!inShadow)
     {
-        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);

+ 26 - 2
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));
     }
-}
+}
+
+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;
+}

+ 17 - 1
tests/intersect_test.cpp

@@ -9,9 +9,9 @@
 #include <intersect.h>
 #include <intersection.h>
 #include <sphere.h>
+#include <transformation.h>
 #include <gtest/gtest.h>
 
-
 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);
 }

+ 12 - 0
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));
 }

+ 57 - 1
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);
-}
+}
+
+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));
+};