5 Commits

Author SHA1 Message Date
Godzil
7687581e83 Finishing touch for patterns! 2020-02-21 12:05:30 +00:00
Godzil
75cf59cc1a Adding support for pattern.
Still a bit more work to be done there.
2020-02-21 09:36:34 +00:00
Godzil
9d0db6a635 Added planes! 2020-02-21 00:26:48 +00:00
Godzil
66c1582a5f Shape is now an abstract class and can't be instanciated.
Change derived shape to only deal with local calculation they don't need anymore to deal with how they've been transformed.
2020-02-21 00:02:30 +00:00
Godzil
2a8fe61388 Working on adding test for the shape object. 2020-02-20 18:06:29 +00:00
32 changed files with 956 additions and 42 deletions

View File

@@ -28,4 +28,12 @@ From Chapter 07:
From Chapter 08:
![Chapter 8 rendering test](output/ch8_test.png)
![Chapter 8 rendering test](output/ch8_test.png)
From Chapter 09:
![Chapter 9 rendering test](output/ch9_test.png)
From Chapter 10:
![Chapter 10 rendering test](output/ch10_test.png)

BIN
output/ch10_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
output/ch8_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
output/ch9_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -3,12 +3,12 @@
# First most is build as a library
add_library(rayonnement STATIC)
file(GLOB RAY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
file(GLOB RAY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h ${CMAKE_CURRENT_SOURCE_DIR}/pattern/*.h)
file(GLOB RAY_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shapes/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/worldbuilder/*.cpp)
target_include_directories(rayonnement PUBLIC include)
target_include_directories(rayonnement PUBLIC include pattern)
target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES})
target_link_libraries(rayonnement LodePNG)

View File

@@ -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;
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) &&

36
source/include/pattern.h Normal file
View File

@@ -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 */

22
source/include/plane.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_PLANE_H
#define DORAYME_PLANE_H
class Plane : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Plane() : Shape(SHAPE_PLANE) { };
};
#endif //DORAYME_PLANE_H

View File

@@ -21,6 +21,7 @@ enum ShapeType
{
SHAPE_NONE,
SHAPE_SPHERE,
SHAPE_PLANE,
};
/* Base class for all object that can be presented in the world */
@@ -29,6 +30,10 @@ class Shape
private:
ShapeType type;
private:
virtual Intersect localIntersect(Ray r) = 0;
virtual Tuple localNormalAt(Tuple point) = 0;
public:
Matrix transformMatrix;
Matrix inverseTransform;
@@ -37,8 +42,8 @@ public:
public:
Shape(ShapeType = SHAPE_NONE);
virtual Intersect intersect(Ray r);
virtual Tuple normalAt(Tuple point);
Intersect intersect(Ray r);
Tuple normalAt(Tuple point);
void setTransform(Matrix transform);
void setMaterial(Material material) { this->material = material; };

View File

@@ -15,11 +15,13 @@
class Sphere : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Sphere() : Shape(SHAPE_SPHERE) { };
/* 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 */

View File

@@ -0,0 +1,28 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Test shape header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_TESTSHAPE_H
#define DORAYME_TESTSHAPE_H
#include <shape.h>
#include <ray.h>
#include <tuple.h>
class TestShape : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Ray localRay;
TestShape();
};
#endif //DORAYME_TESTSHAPE_H

31
source/pattern.cpp Normal file
View File

@@ -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();
}

View File

@@ -0,0 +1,25 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Checkers Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CHECKERSPATTERN_H
#define DORAYME_CHECKERSPATTERN_H
class CheckersPattern : public Pattern
{
public:
CheckersPattern(Colour a, Colour b) : Pattern(a, b) { };
Colour patternAt(Tuple point)
{
double value = floor(point.x) + floor(point.y) + floor(point.z);
return (fmod(value, 2) == 0)?this->a:this->b;
}
};
#endif /* DORAYME_CHECKERSPATTERN_H */

View File

@@ -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 */

View File

@@ -0,0 +1,30 @@
/*
* 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)
{
double squared = (point.x * point.x) + (point.z * point.z);
double value = floor(sqrt(squared));
return (fmod(value, 2) == 0)?this->a:this->b;
}
};
#endif /* DORAYME_RINGSUPPORT_H */

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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);

36
source/shapes/plane.cpp Normal file
View File

@@ -0,0 +1,36 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <shape.h>
#include <plane.h>
#include <math_helper.h>
Intersect Plane::localIntersect(Ray r)
{
double t;
Intersect ret = Intersect();
if (fabs(r.direction.y) < getEpsilon())
{
/* With a direction == 0, the ray can't intersect the plane */
return ret;
}
t = -r.origin.y / r.direction.y;
ret.add(Intersection(t, this));
return ret;
}
Tuple Plane::localNormalAt(Tuple point)
{
return Vector(0, 1, 0);
}

View File

@@ -22,12 +22,21 @@ Shape::Shape(ShapeType type)
Intersect Shape::intersect(Ray r)
{
return Intersect();
return this->localIntersect(this->invTransform(r));
};
Tuple Shape::normalAt(Tuple point)
{
return Vector(0, 0, 0);
Tuple local_point = this->inverseTransform * point;
Tuple local_normal = this->localNormalAt(local_point);
Tuple world_normal = this->inverseTransform.transpose() * local_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();
}
void Shape::setTransform(Matrix transform)

View File

@@ -13,17 +13,15 @@
#include <tuple.h>
#include <intersect.h>
Intersect Sphere::intersect(Ray r)
Intersect Sphere::localIntersect(Ray r)
{
Intersect ret;
double a, b, c, discriminant;
Ray transRay = this->invTransform(r);
Tuple sphere_to_ray = r.origin - Point(0, 0, 0);
Tuple sphere_to_ray = transRay.origin - Point(0, 0, 0);
a = transRay.direction.dot(transRay.direction);
b = 2 * transRay.direction.dot(sphere_to_ray);
a = r.direction.dot(r.direction);
b = 2 * r.direction.dot(sphere_to_ray);
c = sphere_to_ray.dot(sphere_to_ray) - 1;
discriminant = b * b - 4 * a * c;
@@ -37,14 +35,7 @@ Intersect Sphere::intersect(Ray r)
return ret;
}
Tuple Sphere::normalAt(Tuple point)
Tuple Sphere::localNormalAt(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();
return (point - Point(0, 0, 0)).normalise();
}

View File

@@ -0,0 +1,25 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Test shape implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <shape.h>
#include <testshape.h>
TestShape::TestShape() : localRay(Point(0, 0, 0), Vector(0, 0, 0))
{
}
Intersect TestShape::localIntersect(Ray r)
{
this->localRay = r;
return Intersect();
}
Tuple TestShape::localNormalAt(Tuple point)
{
return Vector(point.x, point.y, point.z);
}

View File

@@ -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)

View File

@@ -4,7 +4,8 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
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)
intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_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})
@@ -29,10 +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(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 Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>)
add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)

94
tests/ch10_test.cpp Normal file
View File

@@ -0,0 +1,94 @@
/*
* 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 <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
/* First we need to construct the world */
Plane floor = Plane();
floor.material.specular = 0;
floor.material.pattern = new RingPattern(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.7, 1, 0.6));
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));
Sphere fourth = Sphere();
fourth.setTransform(translation(.5, 0.25, 0.4) * scaling(0.3, 0.3, 0.3));
fourth.material.diffuse = 0.7;
fourth.material.specular = 0.3;
fourth.material.pattern = new CheckersPattern(Colour(0.1, 0.8, 0.1), Colour(0.8, 1, 0.8));
fourth.material.pattern->setTransform( scaling(0.2, 0.2, 0.2));
World w = World();
w.addObject(&floor);
w.addObject(&wall);
w.addObject(&middle);
w.addObject(&left);
w.addObject(&right);
w.addObject(&fourth);
/* Add light */
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 50, 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;
}

View File

@@ -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);
}

69
tests/ch9_test.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* 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 <transformation.h>
int main()
{
/* First we need to construct the world */
Plane floor = Plane();
floor.material.colour = Colour(1, 0.9, 0.9);
floor.material.specular = 0;
Sphere middle = Sphere();
middle.setTransform(translation(-0.5, 1, 0.5));
middle.material.colour = Colour(0.1, 1, 0.5);
middle.material.diffuse = 0.7;
middle.material.specular = 0.3;
Sphere right = Sphere();
right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
right.material.colour = Colour(0.5, 1, 0.1);
right.material.diffuse = 0.7;
right.material.specular = 0.3;
Sphere left = Sphere();
left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
left.material.colour = Colour(1, 0.8, 0.1);
left.material.diffuse = 0.7;
left.material.specular = 0.3;
World w = World();
w.addObject(&floor);
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(100, 50, 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("ch9_test.png");
return 0;
}

View File

@@ -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));
}

211
tests/pattern_test.cpp Normal file
View File

@@ -0,0 +1,211 @@
/*
* 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 <checkerspattern.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);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(1, 0, 0)), black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 1)), black);
/* 0.708 is just bit more than sqrt(2)/2 */
ASSERT_EQ(pattern.patternAt(Point(0.708, 0, 0.708)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_x)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0.99, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(1.01, 0, 0)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_y)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0.99, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 1.01, 0)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_z)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0.99)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 1.01)), black);
}

71
tests/plane_test.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <shape.h>
#include <plane.h>
#include <material.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(PlaneTest, The_normal_of_a_plane_is_constant_everywhere)
{
Plane p = Plane();
Tuple n1 = p.normalAt(Point(0, 0, 0));
Tuple n2 = p.normalAt(Point(10, 0, -10));
Tuple n3 = p.normalAt(Point(-5, 0, 0150));
ASSERT_EQ(n1, Vector(0, 1, 0));
ASSERT_EQ(n2, Vector(0, 1, 0));
ASSERT_EQ(n3, Vector(0, 1, 0));
}
TEST(PlaneTest, Intersect_with_a_ray_parallel_to_the_plane)
{
Plane p = Plane();
Ray r = Ray(Point(0, 10, 0), Vector(0, 0, 1));
Intersect xs = p.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
TEST(PlaneTest, Intersect_with_a_coplanar_ray)
{
Plane p = Plane();
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Intersect xs = p.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
TEST(PlaneTest, A_ray_intersecting_a_plane_from_above)
{
Plane p = Plane();
Ray r = Ray(Point(0, 1, 0), Vector(0, -1, 0));
Intersect xs = p.intersect(r);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[0].object, &p);
}
TEST(PlaneTest, A_ray_intersecting_a_plane_from_below)
{
Plane p = Plane();
Ray r = Ray(Point(0, -1, 0), Vector(0, 1, 0));
Intersect xs = p.intersect(r);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[0].object, &p);
}

View File

@@ -9,6 +9,7 @@
#include <ray.h>
#include <transformation.h>
#include <shape.h>
#include <testshape.h>
#include <gtest/gtest.h>
@@ -38,7 +39,7 @@ TEST(RayTest, Translating_a_ray)
Ray r = Ray(Point(1, 2, 3), Vector(0, 1, 0));
Matrix m = translation(3, 4, 5);
Shape o = Shape();
TestShape o = TestShape();
o.setTransform(m);
@@ -53,7 +54,7 @@ TEST(RayTest, Scaling_a_ray)
Ray r = Ray(Point(1, 2, 3), Vector(0, 1, 0));
Matrix m = scaling(2, 3, 4);
Shape o = Shape();
TestShape o = TestShape();
o.setTransform(m);

98
tests/shape_test.cpp Normal file
View File

@@ -0,0 +1,98 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Shape unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <shape.h>
#include <testshape.h>
#include <matrix.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(ShapeTest, The_default_transformation)
{
TestShape s = TestShape();
ASSERT_EQ(s.transformMatrix, Matrix4().identity());
}
TEST(ShapeTest, Assigning_a_transformation)
{
TestShape s = TestShape();
s.setTransform(translation(2, 3, 4));
ASSERT_EQ(s.transformMatrix, translation(2, 3, 4));
}
TEST(ShapeTest, The_default_material)
{
TestShape s = TestShape();
ASSERT_EQ(s.material, Material());
}
TEST(ShapeTest, Assigning_a_material)
{
TestShape s = TestShape();
Material m = Material();
m.ambient = 1;
s.material = m;
ASSERT_EQ(s.material, m);
}
TEST(ShapeTest, Intersecting_a_scaled_shape_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
TestShape s = TestShape();
s.setTransform(scaling(2, 2, 2));
Intersect xs = s.intersect(r);
ASSERT_EQ(s.localRay.origin, Point(0, 0, -2.5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 0.5));
}
TEST(ShapeTest, Intersecting_a_translated_shape_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
TestShape s = TestShape();
s.setTransform(translation(5, 0, 0));
Intersect xs = s.intersect(r);
ASSERT_EQ(s.localRay.origin, Point(-5, 0, -5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 1));
}
TEST(ShapeTest, Computing_the_normal_on_a_translated_shape)
{
TestShape s = TestShape();
s.setTransform(translation(0, 1, 0));
Tuple n = s.normalAt(Point(0, 1.70711, -0.70711));
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(n, Vector(0, 0.70711, -0.70711));
set_equal_precision(FLT_EPSILON);
}
TEST(ShapeTest, Computing_the_normal_on_a_tranformed_shape)
{
TestShape s = TestShape();
s.setTransform(scaling(1, 0.5, 1) * rotationZ(M_PI / 5));
Tuple n = s.normalAt(Point(0, sqrt(2)/2, -sqrt(2)/2));
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(n, Vector(0, 0.97014, -0.24254));
set_equal_precision(FLT_EPSILON);
}