diff --git a/source/include/object.h b/source/include/object.h index 6f71cce..64f1da6 100644 --- a/source/include/object.h +++ b/source/include/object.h @@ -27,6 +27,8 @@ public: Object(); virtual Intersect intersect(Ray r); + virtual Tuple normalAt(Tuple point); + void setTransform(Matrix transform); Ray transform(Ray r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); }; Ray invTransform(Ray r) { return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); }; diff --git a/source/include/sphere.h b/source/include/sphere.h index b6cf710..7e3aa1b 100644 --- a/source/include/sphere.h +++ b/source/include/sphere.h @@ -18,6 +18,7 @@ class Sphere : public Object public: /* All sphere are at (0, 0, 0) and radius 1 in the object space */ virtual Intersect intersect(Ray r); + virtual Tuple normalAt(Tuple point); }; #endif //DORAYME_SPHERE_H diff --git a/source/objects/object.cpp b/source/objects/object.cpp index b944a76..bdb8294 100644 --- a/source/objects/object.cpp +++ b/source/objects/object.cpp @@ -24,6 +24,11 @@ Intersect Object::intersect(Ray r) return Intersect(); }; +Tuple Object::normalAt(Tuple point) +{ + return Vector(0, 0, 0); +} + void Object::setTransform(Matrix transform) { this->transformMatrix = transform; diff --git a/source/objects/sphere.cpp b/source/objects/sphere.cpp index 3c3b620..9f2c526 100644 --- a/source/objects/sphere.cpp +++ b/source/objects/sphere.cpp @@ -35,4 +35,16 @@ Intersect Sphere::intersect(Ray r) } return ret; +} + +Tuple Sphere::normalAt(Tuple point) +{ + Tuple object_point = this->inverseTransform * point; + Tuple object_normal = (object_point - Point(0, 0, 0)).normalise(); + Tuple world_normal = this->inverseTransform.transpose() * object_normal; + + /* W may get wrong, so hack it. This is perfectly normal as we are using a 4x4 matrix instead of a 3x3 */ + world_normal.w = 0; + + return world_normal.normalise(); } \ No newline at end of file diff --git a/tests/sphere_test.cpp b/tests/sphere_test.cpp index 9988e73..689c4c3 100644 --- a/tests/sphere_test.cpp +++ b/tests/sphere_test.cpp @@ -105,4 +105,76 @@ TEST(SphereTest, Intersecting_a_translated_sphere_with_a_ray) Intersect xs = s.intersect(r); ASSERT_EQ(xs.count(), 0); +} + +TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_X_axis) +{ + Sphere s = Sphere(); + Tuple n = s.normalAt(Point(1, 0, 0)); + + ASSERT_EQ(n, Vector(1, 0, 0)); +} + +TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_Y_axis) +{ + Sphere s = Sphere(); + Tuple n = s.normalAt(Point(0, 1, 0)); + + ASSERT_EQ(n, Vector(0, 1, 0)); +} + +TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_Z_axis) +{ + Sphere s = Sphere(); + Tuple n = s.normalAt(Point(0, 0, 1)); + + ASSERT_EQ(n, Vector(0, 0, 1)); +} + +TEST(SphereTest, The_normal_on_a_sphere_at_a_nonaxial_point) +{ + Sphere s = Sphere(); + Tuple n = s.normalAt(Point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)); + + ASSERT_EQ(n, Vector(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)); +} + +TEST(SphereTest, The_normal_is_a_normalise_vector) +{ + Sphere s = Sphere(); + Tuple n = s.normalAt(Point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)); + + ASSERT_EQ(n, n.normalise()); +} + +TEST(SphereTest, Computing_the_normal_on_a_translated_sphere) +{ + Sphere s = Sphere(); + + s.setTransform(translation(0, 1, 0)); + + Tuple n = s.normalAt(Point(0, 1.70711, -0.70711)); + + set_equal_precision(0.0001); + + ASSERT_EQ(n, Vector(0, 0.70711, -0.70711)); + + /* Revert to default */ + set_equal_precision(FLT_EPSILON); +} + +TEST(SphereTest, Computing_the_normal_on_a_tranformed_sphere) +{ + Sphere s = Sphere(); + + s.setTransform(scaling(1, 0.5, 1) * rotation_z(M_PI / 5)); + + Tuple n = s.normalAt(Point(0, sqrt(2)/2, -sqrt(2)/2)); + + set_equal_precision(0.0001); + + ASSERT_EQ(n, Vector(0, 0.97014, -0.24254)); + + /* Revert to default */ + set_equal_precision(FLT_EPSILON); } \ No newline at end of file