Browse Source

Adding support for pattern.

Still a bit more work to be done there.
Godzil 4 years ago
parent
commit
75cf59cc1a

+ 30 - 0
source/include/gradientpattern.h

@@ -0,0 +1,30 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Gradient Pattern header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#ifndef DORAYME_GRADIENTPATTERN_H
+#define DORAYME_GRADIENTPATTERN_H
+
+#include <pattern.h>
+
+class GradientPattern : public Pattern
+{
+public:
+    GradientPattern(Colour a, Colour b) : Pattern(a, b) { };
+
+    Colour patternAt(Tuple point)
+    {
+        Tuple distance = this->b - this->a;
+        double fraction = point.x - floor(point.x);
+
+        Tuple ret = this->a + distance * fraction;
+
+        return Colour(ret.x, ret.y, ret.z);
+    }
+};
+
+#endif //DORAYME_GRADIENTPATTERN_H

+ 7 - 2
source/include/material.h

@@ -11,8 +11,11 @@
 
 #include <tuple.h>
 #include <colour.h>
+#include <pattern.h>
 #include <light.h>
 
+class Shape;
+
 class Material
 {
 public:
@@ -22,10 +25,12 @@ public:
     double specular;
     double shininess;
 
+    Pattern *pattern;
+
 public:
-    Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200) {};
+    Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200), pattern(nullptr) {};
 
-    Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, bool inShadow = false);
+    Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow = false);
 
     bool operator==(const Material &b) const { return double_equal(this->ambient,   b.ambient) &&
                                                       double_equal(this->diffuse,   b.diffuse) &&

+ 36 - 0
source/include/pattern.h

@@ -0,0 +1,36 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Pattern header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#ifndef DORAYME_PATTERN_H
+#define DORAYME_PATTERN_H
+
+#include <colour.h>
+#include <tuple.h>
+#include <matrix.h>
+
+class Shape;
+
+class Pattern
+{
+public:
+    Colour a;
+    Colour b;
+
+    Matrix transformMatrix;
+    Matrix inverseTransform;
+
+public:
+    Pattern(Colour a, Colour b);
+
+    virtual Colour patternAt(Tuple point) = 0;
+
+    void setTransform(Matrix transform);
+    Colour patternAtObject(Shape *object, Tuple point);
+};
+
+#endif /* DORAYME_PATTERN_H */

+ 31 - 0
source/include/ringpattern.h

@@ -0,0 +1,31 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Ring Pattern header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#ifndef DORAYME_RINGSUPPORT_H
+#define DORAYME_RINGSUPPORT_H
+
+#include <pattern.h>
+
+class RingPattern : public Pattern
+{
+public:
+    RingPattern(Colour a, Colour b) : Pattern(a, b) { };
+
+    Colour patternAt(Tuple point)
+    {
+        Tuple distance = this->b - this->a;
+        double fraction = point.x - floor(point.x);
+
+        Tuple ret = this->a + distance * fraction;
+
+        return Colour(ret.x, ret.y, ret.z);
+    }
+};
+
+
+#endif //DORAYME_RINGSUPPORT_H

+ 32 - 0
source/include/strippattern.h

@@ -0,0 +1,32 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Strip Pattern header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+
+#ifndef DORAYME_STRIPPATTERN_H
+#define DORAYME_STRIPPATTERN_H
+
+#include <pattern.h>
+
+#include <stdio.h>
+
+class StripPattern : public Pattern
+{
+public:
+    StripPattern(Colour a, Colour b) : Pattern(a, b) { };
+
+    Colour patternAt(Tuple point)
+    {
+        if (fmod(floor(point.x), 2) == 0)
+        {
+            return this->a;
+        }
+        return this->b;
+    }
+};
+
+#endif /* DORAYME_STRIPPATTERN_H */

+ 27 - 0
source/include/testpattern.h

@@ -0,0 +1,27 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Strip Pattern header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#ifndef DORAYME_TESTPATTERN_H
+#define DORAYME_TESTPATTERN_H
+
+#include <pattern.h>
+
+#include <stdio.h>
+
+class TestPattern : public Pattern
+{
+public:
+    TestPattern() : Pattern(Colour(0, 0, 0), Colour(1, 1, 1)) { };
+
+    Colour patternAt(Tuple point)
+    {
+        return Colour(point.x, point.y, point.z);
+    }
+};
+
+#endif //DORAYME_TESTPATTERN_H

+ 31 - 0
source/pattern.cpp

@@ -0,0 +1,31 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Pattern implementation
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+
+#include <pattern.h>
+#include <shape.h>
+
+Pattern::Pattern(Colour a, Colour b): a(a), b(b)
+{
+    this->transformMatrix = Matrix4().identity();
+    this->inverseTransform = this->transformMatrix.inverse();
+};
+
+Colour Pattern::patternAtObject(Shape *object, Tuple worldPoint)
+{
+    Tuple objectPoint = object->inverseTransform * worldPoint;
+    Tuple patternPoint = this->inverseTransform * objectPoint;
+
+    return this->patternAt(patternPoint);
+}
+
+void Pattern::setTransform(Matrix transform)
+{
+    this->transformMatrix = transform;
+    this->inverseTransform = transform.inverse();
+}

+ 10 - 2
source/shapes/material.cpp

@@ -9,13 +9,21 @@
 #include <tuple.h>
 #include <material.h>
 #include <colour.h>
+#include <shape.h>
 
-Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, bool inShadow)
+Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow)
 {
+    Colour pointColor = this->colour;
+
+    if (this->pattern != nullptr)
+    {
+        pointColor = this->pattern->patternAtObject(hitObject, point);
+    }
+
     Tuple lightVector = (light.position - point).normalise();
     Tuple reflectVector = Tuple(0, 0, 0, 0);
 
-    Tuple effectiveColour = this->colour * light.intensity;
+    Tuple effectiveColour = pointColor * light.intensity;
     Tuple ambientColour = Colour(0, 0, 0);
     Tuple diffuseColour = Colour(0, 0, 0);
     Tuple specularColour = Colour(0, 0, 0);

+ 1 - 1
source/world.cpp

@@ -99,7 +99,7 @@ Tuple World::shadeHit(Computation comps)
     bool isThereAnObstacle = this->isShadowed(comps.overHitPoint);
 
     return comps.object->material.lighting(*this->lightList[0], comps.overHitPoint, comps.eyeVector,
-            comps.normalVector, isThereAnObstacle);
+            comps.normalVector, comps.object, isThereAnObstacle);
 }
 
 Tuple World::colourAt(Ray r)

+ 10 - 4
tests/CMakeLists.txt

@@ -5,7 +5,7 @@ find_package(Threads REQUIRED)
 
 set(TESTS_SRC tuple_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp ray_test.cpp
         intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_test.cpp
-        shape_test.cpp plane_test.cpp)
+        shape_test.cpp plane_test.cpp pattern_test.cpp)
 
 add_executable(testMyRays)
 target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
@@ -30,16 +30,22 @@ target_sources(ch6_test PRIVATE ch6_test.cpp)
 target_link_libraries(ch6_test rayonnement)
 
 add_executable(ch7_test)
-target_include_directories(ch6_test PUBLIC ../source/include)
+target_include_directories(ch7_test PUBLIC ../source/include)
 target_sources(ch7_test PRIVATE ch7_test.cpp)
 target_link_libraries(ch7_test rayonnement)
 
 add_executable(ch9_test)
-target_include_directories(ch6_test PUBLIC ../source/include)
+target_include_directories(ch9_test PUBLIC ../source/include)
 target_sources(ch9_test PRIVATE ch9_test.cpp)
 target_link_libraries(ch9_test rayonnement)
 
+add_executable(ch10_test)
+target_include_directories(ch10_test PUBLIC ../source/include)
+target_sources(ch10_test PRIVATE ch10_test.cpp)
+target_link_libraries(ch10_test rayonnement)
+
 add_test(NAME Chapter05_Test COMMAND $<TARGET_FILE:ch5_test>)
 add_test(NAME Chapter06_Test COMMAND $<TARGET_FILE:ch6_test>)
 add_test(NAME Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>)
-add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
+add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
+add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)

+ 84 - 0
tests/ch10_test.cpp

@@ -0,0 +1,84 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Render test for chapter 5 "Put it together".
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#include <world.h>
+#include <light.h>
+#include <sphere.h>
+#include <plane.h>
+#include <material.h>
+#include <colour.h>
+#include <canvas.h>
+#include <camera.h>
+
+#include <pattern.h>
+#include <strippattern.h>
+#include <gradientpattern.h>
+
+#include <transformation.h>
+
+int main()
+{
+    /* First we need to construct the world */
+    Plane floor = Plane();
+    floor.material.specular = 0;
+    floor.material.pattern = new StripPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
+
+    Plane wall = Plane();
+    wall.material.specular = 0;
+    wall.material.pattern = new StripPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
+    wall.material.pattern->setTransform(translation(0, 0, 1) * rotationY(M_PI/4));
+    wall.setTransform(translation(0, 0, 5) * rotationX(M_PI/2));
+
+    Sphere middle = Sphere();
+    middle.setTransform(translation(-0.5, 1, 0.5));
+    middle.material.diffuse = 0.7;
+    middle.material.specular = 0.3;
+    middle.material.pattern = new StripPattern(Colour(0.1, 1, 0.5), Colour(0, 0.2, 0.2));
+    middle.material.pattern->setTransform((rotationZ(M_PI/4) * rotationY(M_PI/5) * scaling(0.2, 0.2, 0.2)));
+
+    Sphere right = Sphere();
+    right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
+    right.material.diffuse = 0.7;
+    right.material.specular = 0.3;
+    right.material.pattern = new StripPattern(Colour(0.5, 1, 0.1), Colour(0, 0, 0));
+    right.material.pattern->setTransform((scaling(0.1, 0.1, 0.1)));
+
+    Sphere left = Sphere();
+    left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
+    left.material.diffuse = 0.7;
+    left.material.specular = 0.3;
+    left.material.pattern = new GradientPattern(Colour(1, 0.8, 0.1), Colour(0.1, 0.1, 1));
+    left.material.pattern->setTransform(translation(1.5, 0, 0) * scaling(2.1, 2, 2) * rotationY(-M_PI/4));
+
+    World w = World();
+
+    w.addObject(&floor);
+    w.addObject(&wall);
+    w.addObject(&middle);
+    w.addObject(&left);
+    w.addObject(&right);
+
+    /* Add light */
+    Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
+
+    w.addLight(&light);
+
+    /* Set the camera */
+    Camera camera = Camera(1920, 1080, M_PI / 3);
+    camera.setTransform(viewTransform(Point(0, 1.5, -5),
+                                      Point(0, 1, 0),
+                                      Vector(0, 1, 0)));
+
+    /* Now render it */
+    Canvas image = camera.render(w);
+
+    image.SaveAsPNG("ch10_test.png");
+
+
+    return 0;
+}

+ 1 - 1
tests/ch6_test.cpp

@@ -45,7 +45,7 @@ int main()
                 Tuple hitPoint = r.position(hit.t);
                 Tuple hitNormalVector = hit.object->normalAt(hitPoint);
                 Tuple eye = -r.direction;
-                Colour pixelColour = hit.object->material.lighting(light, hitPoint, eye, hitNormalVector);
+                Colour pixelColour = hit.object->material.lighting(light, hitPoint, eye, hitNormalVector, hit.object);
 
                 c.putPixel(x, y, pixelColour);
             }

+ 13 - 6
tests/material_test.cpp

@@ -9,6 +9,7 @@
 #include <material.h>
 #include <math.h>
 #include <colour.h>
+#include <testshape.h>
 #include <gtest/gtest.h>
 
 TEST(MaterialTest, The_default_material)
@@ -28,33 +29,36 @@ static Point position = Point(0, 0, 0);
 
 TEST(MaterialTest, Lighting_with_the_eye_between_the_light_and_the_surface)
 {
+    TestShape t = TestShape();
     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));
 
-    Colour result = m.lighting(light, position, eyev, normalv);
+    Colour result = m.lighting(light, position, eyev, normalv, &t);
 
     ASSERT_EQ(result, Colour(1.9, 1.9, 1.9));
 }
 
 TEST(MaterialTest, Lighting_with_the_eye_between_the_light_and_the_surface_eye_offset_by_45)
 {
+    TestShape t = TestShape();
     Vector eyev = Vector(0, -sqrt(2)/2, -sqrt(2)/2);
     Vector normalv = Vector(0, 0, -1);
     Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
 
-    Colour result = m.lighting(light, position, eyev, normalv);
+    Colour result = m.lighting(light, position, eyev, normalv, &t);
 
     ASSERT_EQ(result, Colour(1.0, 1.0, 1.0));
 }
 
 TEST(MaterialTest, Lighting_with_the_eye_opposite_surface_light_offset_45)
 {
+    TestShape t = TestShape();
     Vector eyev = Vector(0, 0, -1);
     Vector normalv = Vector(0, 0, -1);
     Light light = Light(POINT_LIGHT, Point(0, 10, -10), Colour(1, 1, 1));
 
-    Colour result = m.lighting(light, position, eyev, normalv);
+    Colour result = m.lighting(light, position, eyev, normalv, &t);
 
     set_equal_precision(0.0001);
 
@@ -65,11 +69,12 @@ TEST(MaterialTest, Lighting_with_the_eye_opposite_surface_light_offset_45)
 
 TEST(MaterialTest, Lighting_with_the_eye_in_the_path_of_the_reflection_vector)
 {
+    TestShape t = TestShape();
     Vector eyev = Vector(0, -sqrt(2)/2, -sqrt(2)/2);
     Vector normalv = Vector(0, 0, -1);
     Light light = Light(POINT_LIGHT, Point(0, 10, -10), Colour(1, 1, 1));
 
-    Colour result = m.lighting(light, position, eyev, normalv);
+    Colour result = m.lighting(light, position, eyev, normalv, &t);
 
     set_equal_precision(0.0001);
 
@@ -80,23 +85,25 @@ TEST(MaterialTest, Lighting_with_the_eye_in_the_path_of_the_reflection_vector)
 
 TEST(MaterialTest, Lighting_with_the_light_behind_the_surface)
 {
+    TestShape t = TestShape();
     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));
 
-    Colour result = m.lighting(light, position, eyev, normalv);
+    Colour result = m.lighting(light, position, eyev, normalv, &t);
 
     ASSERT_EQ(result, Colour(0.1, 0.1, 0.1));
 }
 
 TEST(MaterialTest, Lighting_with_the_surface_in_shadow)
 {
+    TestShape t = TestShape();
     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);
+    Colour result = m.lighting(light, position, eyev, normalv, &t, inShadow);
 
     ASSERT_EQ(result, Colour(0.1, 0.1, 0.1));
 }

+ 177 - 0
tests/pattern_test.cpp

@@ -0,0 +1,177 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Pattern unit tests
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+
+#include <pattern.h>
+#include <strippattern.h>
+#include <gradientpattern.h>
+#include <ringpattern.h>
+#include <testpattern.h>
+#include <transformation.h>
+#include <colour.h>
+#include <sphere.h>
+#include <gtest/gtest.h>
+#include <material.h>
+
+Colour black = Colour(0, 0, 0);
+Colour white = Colour(1, 1, 1);
+
+TEST(PatternTest, Creating_a_stripe_pattern)
+{
+    StripPattern p = StripPattern(white, black);
+
+    ASSERT_EQ(p.a, white);
+    ASSERT_EQ(p.b, black);
+}
+
+TEST(PatternTest, A_strip_pattern_is_constant_in_y)
+{
+    StripPattern p = StripPattern(white, black);
+
+    ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
+    ASSERT_EQ(p.patternAt(Point(0, 1, 0)), white);
+    ASSERT_EQ(p.patternAt(Point(0, 2, 0)), white);
+}
+
+TEST(PatternTest, A_strip_pattern_is_constant_in_z)
+{
+    StripPattern p = StripPattern(white, black);
+
+    ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
+    ASSERT_EQ(p.patternAt(Point(0, 0, 1)), white);
+    ASSERT_EQ(p.patternAt(Point(0, 0, 2)), white);
+}
+
+TEST(PatternTest, A_strip_pattern_alternate_in_x)
+{
+    StripPattern p = StripPattern(white, black);
+
+    ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
+    ASSERT_EQ(p.patternAt(Point(0.9, 0, 0)), white);
+    ASSERT_EQ(p.patternAt(Point(1, 0, 0)), black);
+    ASSERT_EQ(p.patternAt(Point(-0.1, 0, 0)), black);
+    ASSERT_EQ(p.patternAt(Point(-1, 0, 0)), black);
+    ASSERT_EQ(p.patternAt(Point(-1.1, 0, 0)), white);
+}
+
+TEST(PatternTest, Lightning_with_a_pattern_applied)
+{
+    Sphere s = Sphere();
+    Material m;
+    StripPattern p = StripPattern(white, black);
+    m.pattern = &p;
+    m.ambient = 1;
+    m.diffuse = 0;
+    m.specular = 0;
+
+    Tuple eyev = Vector(0, 0, -1);
+    Tuple normalv = Vector(0, 0, -1);
+    Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
+
+    Colour c1 = m.lighting(light, Point(0, 9, 0), eyev, normalv, &s, false);
+    Colour c2 = m.lighting(light, Point(1, 1, 0), eyev, normalv, &s, false);
+
+    ASSERT_EQ(c1, Colour(1, 1, 1));
+    ASSERT_EQ(c2, Colour(0, 0, 0));
+}
+
+TEST(PatternTest, Stripe_with_an_object_transformation)
+{
+    Sphere s = Sphere();
+    s.setTransform(scaling(2, 2, 2));
+    StripPattern pattern = StripPattern(white, black);
+    Colour c = pattern.patternAtObject(&s, Point(1.5, 0, 0));
+
+    ASSERT_EQ(c, white);
+}
+
+TEST(PatternTest, Stripe_with_a_pattern_transformation)
+{
+    Sphere s = Sphere();
+    StripPattern pattern = StripPattern(white, black);
+    pattern.setTransform(scaling(2, 2, 2));
+    Colour c = pattern.patternAtObject(&s, Point(1.5, 0, 0));
+
+    ASSERT_EQ(c, white);
+}
+
+TEST(PatternTest, Stripe_with_both_an_object_and_a_pattern_transformation)
+{
+    Sphere s = Sphere();
+    s.setTransform(scaling(2, 2, 2));
+    StripPattern pattern = StripPattern(white, black);
+    pattern.setTransform(translation(0.5, 0, 0));
+
+    Colour c = pattern.patternAtObject(&s, Point(2.5, 0, 0));
+
+    ASSERT_EQ(c, white);
+}
+
+TEST(PatternTest, The_default_pattern_transformation)
+{
+    TestPattern pattern = TestPattern();
+
+    ASSERT_EQ(pattern.transformMatrix, Matrix4().identity());
+}
+
+TEST(PatternTest, Assigning_a_transformation)
+{
+    TestPattern pattern = TestPattern();
+    pattern.setTransform(translation(1, 2, 3));
+
+    ASSERT_EQ(pattern.transformMatrix, translation(1, 2, 3));
+}
+
+TEST(PatternTest, A_pattern_with_an_object_transformation)
+{
+    Sphere s = Sphere();
+    s.setTransform(scaling(2, 2, 2));
+    TestPattern pattern = TestPattern();
+
+    Colour c = pattern.patternAtObject(&s, Point(2, 3, 4));
+
+    ASSERT_EQ(c, Colour(1, 1.5, 2));
+}
+
+TEST(PatternTest, A_pattern_with_a_pattern_transformation)
+{
+    Sphere s = Sphere();
+    TestPattern pattern = TestPattern();
+    pattern.setTransform(scaling(2, 2, 2));
+
+    Colour c = pattern.patternAtObject(&s, Point(2, 3, 4));
+
+    ASSERT_EQ(c, Colour(1, 1.5, 2));
+}
+
+TEST(PatternTest, A_pattern_with_an_object_and_a_pattern_transformation)
+{
+    Sphere s = Sphere();
+    s.setTransform(scaling(2, 2, 2));
+    TestPattern pattern = TestPattern();
+    pattern.setTransform(translation(0.5, 1, 1.5));
+
+    Colour c = pattern.patternAtObject(&s, Point(2.5, 3, 3.5));
+
+    ASSERT_EQ(c, Colour(0.75, 0.5, 0.25));
+}
+
+TEST(PatternTest, A_gradient_linearly_interpolates_betweens_colors)
+{
+    GradientPattern pattern = GradientPattern(white, black);
+
+    ASSERT_EQ(pattern.patternAt(Point(0.25, 0, 0)), Colour(0.75, 0.75, 0.75));
+    ASSERT_EQ(pattern.patternAt(Point(0.5, 0, 0)), Colour(0.5, 0.5, 0.5));
+    ASSERT_EQ(pattern.patternAt(Point(0.75, 0, 0)), Colour(0.25, 0.25, 0.25));
+}
+
+TEST(PatternTest, A_ring_should_extend_in_both_x_and_z)
+{
+    RingPattern pattern = RingPattern(white, black);
+
+}