From 75cf59cc1a1054bd3134b8bf4d9a743aef18360e Mon Sep 17 00:00:00 2001 From: Godzil Date: Fri, 21 Feb 2020 09:36:34 +0000 Subject: [PATCH] Adding support for pattern. Still a bit more work to be done there. --- source/include/gradientpattern.h | 30 ++++++ source/include/material.h | 11 +- source/include/pattern.h | 36 +++++++ source/include/ringpattern.h | 31 ++++++ source/include/strippattern.h | 32 ++++++ source/include/testpattern.h | 27 +++++ source/pattern.cpp | 31 ++++++ source/shapes/material.cpp | 12 ++- source/world.cpp | 2 +- tests/CMakeLists.txt | 14 ++- tests/ch10_test.cpp | 84 +++++++++++++++ tests/ch6_test.cpp | 2 +- tests/material_test.cpp | 19 ++-- tests/pattern_test.cpp | 177 +++++++++++++++++++++++++++++++ 14 files changed, 491 insertions(+), 17 deletions(-) create mode 100644 source/include/gradientpattern.h create mode 100644 source/include/pattern.h create mode 100644 source/include/ringpattern.h create mode 100644 source/include/strippattern.h create mode 100644 source/include/testpattern.h create mode 100644 source/pattern.cpp create mode 100644 tests/ch10_test.cpp create mode 100644 tests/pattern_test.cpp diff --git a/source/include/gradientpattern.h b/source/include/gradientpattern.h new file mode 100644 index 0000000..348473e --- /dev/null +++ b/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 + +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 diff --git a/source/include/material.h b/source/include/material.h index 549358e..41924ae 100644 --- a/source/include/material.h +++ b/source/include/material.h @@ -11,8 +11,11 @@ #include #include +#include #include +class Shape; + class Material { public: @@ -22,10 +25,12 @@ public: double specular; double shininess; -public: - Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200) {}; + Pattern *pattern; - Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, bool inShadow = false); +public: + 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, Shape *hitObject, 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/pattern.h b/source/include/pattern.h new file mode 100644 index 0000000..73a3421 --- /dev/null +++ b/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 +#include +#include + +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 */ diff --git a/source/include/ringpattern.h b/source/include/ringpattern.h new file mode 100644 index 0000000..df23943 --- /dev/null +++ b/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 + +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 diff --git a/source/include/strippattern.h b/source/include/strippattern.h new file mode 100644 index 0000000..6175c0e --- /dev/null +++ b/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 + +#include + +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 */ diff --git a/source/include/testpattern.h b/source/include/testpattern.h new file mode 100644 index 0000000..17afdfd --- /dev/null +++ b/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 + +#include + +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 diff --git a/source/pattern.cpp b/source/pattern.cpp new file mode 100644 index 0000000..ceaeafb --- /dev/null +++ b/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 +#include + +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(); +} \ No newline at end of file diff --git a/source/shapes/material.cpp b/source/shapes/material.cpp index c694e69..3dbb30d 100644 --- a/source/shapes/material.cpp +++ b/source/shapes/material.cpp @@ -9,13 +9,21 @@ #include #include #include +#include -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); diff --git a/source/world.cpp b/source/world.cpp index 1198484..4af2bca 100644 --- a/source/world.cpp +++ b/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) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d4b55d8..90ff541 100644 --- a/tests/CMakeLists.txt +++ b/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 $) add_test(NAME Chapter06_Test COMMAND $) add_test(NAME Chapter07_Test COMMAND $) -add_test(NAME Chapter09_Test COMMAND $) \ No newline at end of file +add_test(NAME Chapter09_Test COMMAND $) +add_test(NAME Chapter10_Test COMMAND $) diff --git a/tests/ch10_test.cpp b/tests/ch10_test.cpp new file mode 100644 index 0000000..6e7926d --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +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; +} \ No newline at end of file diff --git a/tests/ch6_test.cpp b/tests/ch6_test.cpp index e80db5e..796d84a 100644 --- a/tests/ch6_test.cpp +++ b/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); } diff --git a/tests/material_test.cpp b/tests/material_test.cpp index e79d4b3..2226a46 100644 --- a/tests/material_test.cpp +++ b/tests/material_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include 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)); } \ No newline at end of file diff --git a/tests/pattern_test.cpp b/tests/pattern_test.cpp new file mode 100644 index 0000000..8a4d7f9 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); + +}