16 Commits

Author SHA1 Message Date
Godzil
2ea4abdce7 Boundingboxes should be ready.
Next step (later) would be to properly use them other than group to lower the number of intersection calculation per ray.
2020-02-25 18:03:12 +00:00
Godzil
831a096281 Continue working on bounding boxes. 2020-02-25 09:20:38 +00:00
Godzil
3011544e8f Started working on boundingboxes. 2020-02-24 18:03:25 +00:00
Godzil
d1965caf8d Add an option to run coverage locally 2020-02-24 17:26:36 +00:00
Godzil
7bbe5e843b Group should work now. 2020-02-24 17:25:54 +00:00
Godzil
7c794f0496 Working on groups 2020-02-24 09:25:52 +00:00
Godzil
80f59efa43 Add another hardcoded scene. Also made a test file for hw3 that should cover all the commands. 2020-02-23 19:37:47 +00:00
Godzil
f226664fe3 And cones ! 2020-02-23 02:31:30 +00:00
Godzil
0650ac7b44 There were a small copy mistake in ch12 test file. Update the render output 2020-02-22 23:01:06 +00:00
Godzil
d87bbb184e And now we have cylinders! 2020-02-22 22:58:57 +00:00
Godzil
b9bacd3ac9 Don't really understand why this code is marked as not being tested where it should. 2020-02-22 18:51:03 +00:00
Godzil
1d685de8fd Trying to identify why they say these lines are not tested 2020-02-22 18:29:47 +00:00
Godzil
9c35cfc4f3 Trying to fix coverage. 2020-02-22 18:21:30 +00:00
Godzil
56095169eb Add a test for hw3render 2020-02-22 18:00:07 +00:00
Godzil
60db274214 Trye to talk a bit more in the readme XD 2020-02-22 17:45:45 +00:00
Godzil
566be9bcf6 Add missing image 2020-02-22 17:40:48 +00:00
40 changed files with 2199 additions and 39 deletions

View File

@@ -10,12 +10,32 @@ option(COVERALLS "Generate coveralls data" OFF)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/external/coveralls-cmake/cmake)
option(PACKAGE_TESTS "Build the tests" ON)
option(ENABLE_COVERAGE "Build for code coverage" OFF)
if (ENABLE_COVERAGE AND COVERALLS)
message(FATAL_ERROR "You can't enable both ENABLE_COVERAGE and COVERALLS at the same time")
endif()
if (COVERALLS)
include(Coveralls)
coveralls_turn_on_coverage()
endif()
if (ENABLE_COVERAGE)
if("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" OR
"${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
message("Building with LLVM Code Coverage Tools")
set(CMAKE_CXX_FLAGS "-fprofile-instr-generate -fcoverage-mapping")
elseif(CMAKE_COMPILER_IS_GNUCXX)
message("Building with lcov Code Coverage Tools")
set(CMAKE_CXX_FLAGS "--coverage")
else()
message(FATAL_ERROR "Compiler ${CMAKE_C_COMPILER_ID} is not supported for code coverage")
endif()
endif()
# LodePNG don't make a .a or .so, so let's build a library here
add_library(LodePNG STATIC)

View File

@@ -14,31 +14,31 @@ It is writen in C++ with no STL and use [LodePNG](https://github.com/lvandeve/lo
Examples outputs
----------------
From chapter 05:
From chapter 05 - Sphere intersections:
![Chapter 5 rendering test](output/ch5_test.png)
From Chapter 06:
From Chapter 06 - Phong shading:
![Chapter 6 rendering test](output/ch6_test.png)
From Chapter 07:
From Chapter 07 - World / Camera / Scenes:
![Chapter 7 rendering test](output/ch7_test.png)
From Chapter 08:
From Chapter 08 - Shadows:
![Chapter 8 rendering test](output/ch8_test.png)
From Chapter 09:
From Chapter 09 - Planes:
![Chapter 9 rendering test](output/ch9_test.png)
From Chapter 10:
From Chapter 10 - Patterns:
![Chapter 10 rendering test](output/ch10_test.png)
From Chapter 11:
From Chapter 11 - Reflections, Transparency & Refractions
![Chapter 11 reflections rendering test](output/ch11_reflection.png)
@@ -46,6 +46,12 @@ From Chapter 11:
![Chapter 11 rendering test](output/ch11_test.png)
From Chapter 12:
From Chapter 12 - Cubes:
![Chapter 12 rendering test](output/ch12_test.png)
![Chapter 12 rendering test](output/ch12_test.png)
From Chapter 13 - Cylinders:
![Chapter 13 rendering test](output/ch13_test.png)
Bonus:
![Chapter 13 cone test](output/ch13_cone.png)

BIN
output/ch12_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
output/ch13_cone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
output/ch13_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -0,0 +1,106 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Bounding box header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_BOUNDINGBOX_H
#define DORAYME_BOUNDINGBOX_H
struct BoundingBox
{
private:
bool isReset;
public:
Tuple min;
Tuple max;
BoundingBox() : min(INFINITY, INFINITY, INFINITY, 1.0), max(-INFINITY, -INFINITY, -INFINITY, 1.0), isReset(true) { };
BoundingBox(Tuple min, Tuple max) : min(min), max(max), isReset(false) { };
void operator|(const BoundingBox &b) {
isReset = false;
if (this->min.x > b.min.x) { this->min.x = b.min.x; }
if (this->min.y > b.min.y) { this->min.y = b.min.y; }
if (this->min.z > b.min.z) { this->min.z = b.min.z; }
if (this->max.x < b.max.x) { this->max.x = b.max.x; }
if (this->max.y < b.max.y) { this->max.y = b.max.y; }
if (this->max.z < b.max.z) { this->max.z = b.max.z; }
}
bool haveFiniteBounds() { return this->min.isRepresentable() && this->max.isRepresentable(); };
bool fitsIn(const BoundingBox &other) {
bool fits = true;
if (this->min.x > other.min.x) { fits = false; }
if (this->min.y > other.min.y) { fits = false; }
if (this->min.z > other.min.z) { fits = false; }
if (this->max.x < other.max.x) { fits = false; }
if (this->max.y < other.max.y) { fits = false; }
if (this->max.z < other.max.z) { fits = false; }
return fits;
}
void checkAxis(double axeOrigin, double axeDirection, double xMin, double xMax, double *axeMin, double *axeMax)
{
double tMinNumerator = (xMin - axeOrigin);
double tMaxNumerator = (xMax - axeOrigin);
if (fabs(axeDirection) >= getEpsilon())
{
*axeMin = tMinNumerator / axeDirection;
*axeMax = tMaxNumerator / axeDirection;
}
else
{
*axeMin = tMinNumerator * INFINITY;
*axeMax = tMaxNumerator * INFINITY;
}
if (*axeMin > *axeMax)
{
double swap = *axeMax;
*axeMax = *axeMin;
*axeMin = swap;
}
}
void reset()
{
this->isReset = true;
min.x = min.y = min.z = INFINITY;
max.x = max.y = max.z = -INFINITY;
}
bool isEmpty() { return this->isReset; };
bool intesectMe(Ray r) {
double xtMin, xtMax, ytMin, ytMax, ztMin, ztMax;
double tMin, tMax;
this->checkAxis(r.origin.x, r.direction.x, this->min.x, this->max.x, &xtMin, &xtMax);
this->checkAxis(r.origin.y, r.direction.y, this->min.y, this->max.y, &ytMin, &ytMax);
this->checkAxis(r.origin.z, r.direction.z, this->min.z, this->max.z, &ztMin, &ztMax);
tMin = max3(xtMin, ytMin, ztMin);
tMax = min3(xtMax, ytMax, ztMax);
if (tMin <= tMax)
{
return true;
}
return false;
}
};
#endif //DORAYME_BOUNDINGBOX_H

35
source/include/cone.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cone header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CONE_H
#define DORAYME_CONE_H
#include <shape.h>
#include <ray.h>
#include <intersect.h>
class Cone : public Shape {
protected:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
bool checkCap(Ray r, double t, double y);
void intersectCaps(Ray r, Intersect &xs);
public:
bool isClosed;
double minCap;
double maxCap;
Cone() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(SHAPE_CONE) {};
BoundingBox getBounds();
bool haveFiniteBounds() { return !(isinf(this->minCap) || isinf(this->maxCap)); };
};
#endif /* DORAYME_CONE_H */

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

@@ -0,0 +1,36 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cylinder header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CYLINDER_H
#define DORAYME_CYLINDER_H
#include <shape.h>
#include <ray.h>
#include <intersect.h>
class Cylinder : public Shape {
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
bool checkCap(Ray r, double t);
void intersectCaps(Ray r, Intersect &xs);
public:
bool isClosed;
double minCap;
double maxCap;
Cylinder() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(SHAPE_CYLINDER) {};
BoundingBox getBounds();
bool haveFiniteBounds() { return !(isinf(this->minCap) || isinf(this->maxCap)); };
};
#endif //DORAYME_CYLINDER_H

48
source/include/group.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Group header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_GROUP_H
#define DORAYME_GROUP_H
#include <shape.h>
/* TODO: Add a way to force(?) material from group to be applied on childs */
class Group : public Shape
{
private:
uint32_t allocatedObjectCount;
Shape* *objectList;
uint32_t objectCount;
uint32_t allocatedUnboxableObjectCount;
Shape* *unboxableObjectList;
uint32_t unboxableObjectCount;
protected:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
BoundingBox bounds;
public:
bool isEmpty();
void addObject(Shape *s);
Shape *operator[](const int p) { return this->objectList[p]; }
Intersect intersect(Ray r);
BoundingBox getBounds();
void updateBoundingBox();
Group();
};
#endif /* DORAYME_GROUP_H */

View File

@@ -46,6 +46,7 @@ public:
double_equal(this->emissive, b.emissive) &&
double_equal(this->refractiveIndex, b.refractiveIndex) &&
(this->colour == b.colour); };
bool operator!=(const Material &b) const { return !(*this == b); };
};

View File

@@ -17,6 +17,8 @@ private:
public:
Plane() : Shape(SHAPE_PLANE) { };
BoundingBox getBounds();
bool haveFiniteBounds() { return false; };
};
#endif //DORAYME_PLANE_H

View File

@@ -16,6 +16,7 @@ class Shape;
#include <matrix.h>
#include <intersect.h>
#include <material.h>
#include <boundingbox.h>
enum ShapeType
{
@@ -23,7 +24,10 @@ enum ShapeType
SHAPE_SPHERE,
SHAPE_PLANE,
SHAPE_CUBE,
SHAPE_CYLINDER,
SHAPE_CONE,
SHAPE_GROUP,
};
/* Base class for all object that can be presented in the world */
@@ -31,23 +35,36 @@ class Shape
{
private:
ShapeType type;
private:
Matrix localTransformMatrix;
protected:
virtual Intersect localIntersect(Ray r) = 0;
virtual Tuple localNormalAt(Tuple point) = 0;
public:
Matrix transformMatrix;
Matrix inverseTransform;
Matrix transposedInverseTransform;
Material material;
bool dropShadow;
Shape *parent;
public:
Shape(ShapeType = SHAPE_NONE);
Intersect intersect(Ray r);
virtual Intersect intersect(Ray r);
virtual Intersect intersectOOB(Ray r) { return this->intersect(r); };
Tuple normalAt(Tuple point);
/* Bounding box points are always world value */
virtual BoundingBox getBounds();
virtual bool haveFiniteBounds() { return true; };
void updateTransform();
Tuple worldToObject(Tuple point) { return this->inverseTransform * point; };
Tuple objectToWorld(Tuple point) { return this->transformMatrix * point; };
Tuple normalToWorld(Tuple normalVector);
void setTransform(Matrix transform);
void setMaterial(Material material) { this->material = material; };
Ray transform(Ray r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); };

View File

@@ -15,7 +15,7 @@
class Sphere : public Shape
{
private:
protected:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);

View File

@@ -17,6 +17,7 @@ public:
double x, y, z, w;
public:
Tuple() : x(0), y(0), z(0), w(0.0) {};
Tuple(double x, double y, double z) : x(x), y(y), z(z), w(0.0) {};
Tuple(double x, double y, double z, double w) : x(x), y(y), z(z), w(w) {};
bool isPoint() { return (this->w == 1.0); };
@@ -26,6 +27,7 @@ public:
double_equal(this->y, b.y) &&
double_equal(this->z, b.z) &&
double_equal(this->w, b.w); };
bool operator!=(const Tuple &b) const { return !(*this == b); };
Tuple operator+(const Tuple &b) const { return Tuple(this->x + b.x, this->y + b.y,
this->z + b.z, this->w + b.w); };
@@ -38,6 +40,11 @@ public:
Tuple operator/(const double &b) const { return Tuple(this->x / b, this->y / b,
this->z / b, this->w / b); };
void fixPoint();
void fixVector();
bool isRepresentable();
void set(double nX, double nY, double nZ) { this->x = nX; this->y = nY; this->z = nZ; };
double magnitude();
Tuple normalise();
double dot(const Tuple &b);
@@ -48,12 +55,14 @@ public:
class Point: public Tuple
{
public:
Point() : Tuple(0, 0, 0, 1.0) {};
Point(double x, double y, double z) : Tuple(x, y, z, 1.0) {};
};
class Vector: public Tuple
{
public:
Vector() : Tuple(0, 0, 0, 0.0) {};
Vector(double x, double y, double z) : Tuple(x, y, z, 0.0) {};
};

View File

@@ -24,6 +24,7 @@ Intersect::Intersect()
Intersect::~Intersect()
{
/* Free stuff */
free(this->list);
}
void Intersect::add(Intersection i)

View File

@@ -25,6 +25,9 @@ double getEpsilon()
bool double_equal(double a, double b)
{
if (isinf(a) && isinf(b))
return true;
return fabs(a - b) < current_precision;
}
@@ -43,13 +46,8 @@ double min3(double a, double b, double c)
if (b <= a)
{
if (c < b) return c;
return b;
}
if (c <= a)
{
if (b < c) return b;
}
return c;
return b;
}
double max3(double a, double b, double c)
@@ -62,11 +60,6 @@ double max3(double a, double b, double c)
if (b >= a)
{
if (c > b) return c;
return b;
}
if (c >= a)
{
if (b > c) return b;
}
return c;
return b;
}

View File

@@ -18,7 +18,7 @@ Pattern::Pattern(Colour a, Colour b): a(a), b(b)
Colour Pattern::patternAtObject(Shape *object, Tuple worldPoint)
{
Tuple objectPoint = object->inverseTransform * worldPoint;
Tuple objectPoint = object->worldToObject(worldPoint);
Tuple patternPoint = this->inverseTransform * objectPoint;
return this->patternAt(patternPoint);

141
source/shapes/cone.cpp Normal file
View File

@@ -0,0 +1,141 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cone implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <shape.h>
#include <cone.h>
#include <math_helper.h>
bool Cone::checkCap(Ray r, double t, double y)
{
/* Helping function to reduce duplication.
* Checks to see if the intersection ot t is within a radius
* of 1 (the radius of our Cone from the y axis
*/
double x = r.origin.x + t * r.direction.x;
double z = r.origin.z + t * r.direction.z;
return (x * x + z * z) <= fabs(y);
}
void Cone::intersectCaps(Ray r, Intersect &xs)
{
/* Caps only mattter is the Cone is closed, and might possibly be
* intersected by the ray
*/
if ((this->isClosed) && (fabs(r.direction.y) > getEpsilon()))
{
double t;
/* Check for an intersection with the lower end cap by intersecting
* the ray with the plan at y = this->minCap
*/
t = (this->minCap - r.origin.y) / r.direction.y;
if (this->checkCap(r, t, this->minCap))
{
xs.add(Intersection(t, this));
}
/* Check for an intersection with the upper end cap by intersecting
* the ray with the plan at y = this->maxCap
*/
t = (this->maxCap - r.origin.y) / r.direction.y;
if (this->checkCap(r, t, this->maxCap))
{
xs.add(Intersection(t, this));
}
}
}
Intersect Cone::localIntersect(Ray r)
{
Intersect ret;
double A = pow(r.direction.x, 2) -
pow(r.direction.y, 2) +
pow(r.direction.z, 2);
double B = (2 * r.origin.x * r.direction.x) -
(2 * r.origin.y * r.direction.y) +
(2 * r.origin.z * r.direction.z);
double C = pow(r.origin.x, 2) -
pow(r.origin.y, 2) +
pow(r.origin.z, 2);
if ((fabs(A) <= getEpsilon()) && (fabs(B) >= getEpsilon()))
{
double t = -C / (2*B);
ret.add(Intersection(t, this));
}
else if (fabs(A) >= getEpsilon())
{
double disc = pow(B, 2) - 4 * A * C;
if (disc >= 0)
{
double t0 = (-B - sqrt(disc)) / (2 * A);
double t1 = (-B + sqrt(disc)) / (2 * A);
double y0 = r.origin.y + t0 * r.direction.y;
if ((this->minCap < y0) && (y0 < this->maxCap))
{
ret.add(Intersection(t0, this));
}
double y1 = r.origin.y + t1 * r.direction.y;
if ((this->minCap < y1) && (y1 < this->maxCap))
{
ret.add(Intersection(t1, this));
}
}
}
this->intersectCaps(r, ret);
return ret;
}
Tuple Cone::localNormalAt(Tuple point)
{
/* Compute the square of the distance from the Y axis */
double dist = point.x * point.x + point.z * point.z;
if ((dist < 1) && (point.y >= (this->maxCap - getEpsilon())))
{
return Vector(0, 1, 0);
}
if ((dist < 1) && (point.y <= this->minCap + getEpsilon()))
{
return Vector(0, -1, 0);
}
double y = sqrt(point.x * point.x + point.z * point.z);
if (point.y > 0)
{
y = -y;
}
return Vector(point.x, y, point.z);
}
BoundingBox Cone::getBounds()
{
BoundingBox ret;
double a = fabs(this->minCap);
double b = fabs(this->maxCap);
double limit = (a > b)?a:b;
ret.min = this->objectToWorld(Point(-limit, this->minCap, -limit));
ret.max = this->objectToWorld(Point(limit, this->maxCap, limit));
ret.min.fixPoint();
ret.max.fixPoint();
return ret;
}

122
source/shapes/cylinder.cpp Normal file
View File

@@ -0,0 +1,122 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cylinder implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <shape.h>
#include <cylinder.h>
#include <math_helper.h>
bool Cylinder::checkCap(Ray r, double t)
{
/* Helping function to reduce duplication.
* Checks to see if the intersection ot t is within a radius
* of 1 (the radius of our cylinder from the y axis
*/
double x = r.origin.x + t * r.direction.x;
double z = r.origin.z + t * r.direction.z;
return (x * x + z * z) <= 1;
}
void Cylinder::intersectCaps(Ray r, Intersect &xs)
{
/* Caps only mattter is the cylinder is closed, and might possibly be
* intersected by the ray
*/
if ((this->isClosed) && (fabs(r.direction.y) > getEpsilon()))
{
double t;
/* Check for an intersection with the lower end cap by intersecting
* the ray with the plan at y = this->minCap
*/
t = (this->minCap - r.origin.y) / r.direction.y;
if (this->checkCap(r, t))
{
xs.add(Intersection(t, this));
}
/* Check for an intersection with the upper end cap by intersecting
* the ray with the plan at y = this->maxCap
*/
t = (this->maxCap - r.origin.y) / r.direction.y;
if (this->checkCap(r, t))
{
xs.add(Intersection(t, this));
}
}
}
Intersect Cylinder::localIntersect(Ray r)
{
Intersect ret;
double A = r.direction.x * r.direction.x + r.direction.z * r.direction.z;
/* Ray is parallel to the Y axis */
if (A >= getEpsilon())
{
double B = 2 * r.origin.x * r.direction.x +
2 * r.origin.z * r.direction.z;
double C = r.origin.x * r.origin.x + r.origin.z * r.origin.z - 1;
double disc = B * B - 4 * A * C;
if (disc >= 0)
{
double t0 = (-B - sqrt(disc)) / (2 * A);
double t1 = (-B + sqrt(disc)) / (2 * A);
double y0 = r.origin.y + t0 * r.direction.y;
if ((this->minCap < y0) && (y0 < this->maxCap))
{
ret.add(Intersection(t0, this));
}
double y1 = r.origin.y + t1 * r.direction.y;
if ((this->minCap < y1) && (y1 < this->maxCap))
{
ret.add(Intersection(t1, this));
}
}
}
this->intersectCaps(r, ret);
return ret;
}
Tuple Cylinder::localNormalAt(Tuple point)
{
/* Compute the square of the distance from the Y axis */
double dist = point.x * point.x + point.z * point.z;
if ((dist < 1) && (point.y >= (this->maxCap - getEpsilon())))
{
return Vector(0, 1, 0);
}
if ((dist < 1) && (point.y <= this->minCap + getEpsilon()))
{
return Vector(0, -1, 0);
}
return Vector(point.x, 0, point.z);
}
BoundingBox Cylinder::getBounds()
{
BoundingBox ret;
ret.min = this->objectToWorld(Point(-1, this->minCap, -1));
ret.max = this->objectToWorld(Point(1, this->maxCap, 1));
ret.min.fixPoint();
ret.max.fixPoint();
return ret;
}

139
source/shapes/group.cpp Normal file
View File

@@ -0,0 +1,139 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Group implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <group.h>
#include <cone.h>
#include <math_helper.h>
#define MIN_ALLOC (2)
Group::Group() : Shape(SHAPE_GROUP)
{
this->allocatedObjectCount = MIN_ALLOC;
this->objectList = (Shape **)calloc(sizeof(Shape *), MIN_ALLOC);
this->objectCount = 0;
this->allocatedUnboxableObjectCount = MIN_ALLOC;
this->unboxableObjectList = (Shape **)calloc(sizeof(Shape *), MIN_ALLOC);
this->unboxableObjectCount = 0;
}
Intersect Group::intersect(Ray r)
{
Intersect ret;
int i, j;
if (this->objectCount > 0)
{
if (this->bounds.intesectMe(r))
{
for (i = 0 ; i < this->objectCount ; i++)
{
Intersect xs = this->objectList[i]->intersect(r);
if (xs.count() > 0)
{
for (j = 0 ; j < xs.count() ; j++)
{
ret.add(xs[j]);
}
}
}
}
}
/* We are force to do them all the time */
if (this->unboxableObjectCount > 0)
{
for(i = 0; i < this->unboxableObjectCount; i++)
{
Intersect xs = this->unboxableObjectList[i]->intersect(r);
if (xs.count() > 0)
{
for(j = 0; j < xs.count(); j++)
{
ret.add(xs[j]);
}
}
}
}
return ret;
}
Intersect Group::localIntersect(Ray r)
{
return Intersect();
}
Tuple Group::localNormalAt(Tuple point)
{
return Vector(1, 0, 0);
}
/* ONLY INSERT SHAPES THAT ARE NOT GOING TO CHANGE ELSE..! */
void Group::addObject(Shape *s)
{
if (s->haveFiniteBounds())
{
if ((this->objectCount + 1) > this->allocatedObjectCount)
{
this->allocatedObjectCount *= 2;
this->objectList = (Shape **)realloc(this->objectList, sizeof(Shape **) * this->allocatedObjectCount);
}
s->parent = this;
s->updateTransform();
this->objectList[this->objectCount++] = s;
this->bounds | s->getBounds();
}
else
{
if ((this->unboxableObjectCount + 1) > this->allocatedUnboxableObjectCount)
{
this->allocatedUnboxableObjectCount *= 2;
this->unboxableObjectList = (Shape **)realloc(this->unboxableObjectList, sizeof(Shape **) * this->allocatedUnboxableObjectCount);
}
s->parent = this;
s->updateTransform();
this->unboxableObjectList[this->unboxableObjectCount++] = s;
}
}
bool Group::isEmpty()
{
return (this->objectCount == 0) && (this->unboxableObjectCount == 0);
}
BoundingBox Group::getBounds()
{
if (this->bounds.isEmpty()) { this->updateBoundingBox(); }
return this->bounds;
}
void Group::updateBoundingBox()
{
this->bounds.reset();
if (this->objectCount > 0)
{
int i;
for(i = 0; i < this->objectCount; i++)
{
if (!this->objectList[i]->haveFiniteBounds())
{
BoundingBox objB = this->objectList[i]->getBounds();
this->bounds | objB;
}
}
}
}

View File

@@ -33,4 +33,17 @@ Intersect Plane::localIntersect(Ray r)
Tuple Plane::localNormalAt(Tuple point)
{
return Vector(0, 1, 0);
}
BoundingBox Plane::getBounds()
{
BoundingBox ret;
ret.min = this->objectToWorld(Point(-INFINITY, 0-getEpsilon(), -INFINITY));
ret.max = this->objectToWorld(Point(INFINITY, 0+getEpsilon(), INFINITY));
ret.min.fixPoint();
ret.max.fixPoint();
return ret;
}

View File

@@ -15,10 +15,11 @@
Shape::Shape(ShapeType type)
{
this->parent = nullptr;
this->dropShadow = true;
this->type = type;
this->transformMatrix = Matrix4().identity();
this->inverseTransform = this->transformMatrix.inverse();
this->localTransformMatrix = Matrix4().identity();
this->updateTransform();
}
Intersect Shape::intersect(Ray r)
@@ -26,22 +27,50 @@ Intersect Shape::intersect(Ray r)
return this->localIntersect(this->invTransform(r));
};
Tuple Shape::normalAt(Tuple point)
Tuple Shape::normalToWorld(Tuple normalVector)
{
Tuple local_point = this->inverseTransform * point;
Tuple local_normal = this->localNormalAt(local_point);
Tuple world_normal = this->inverseTransform.transpose() * local_normal;
Tuple world_normal = this->transposedInverseTransform * normalVector;
/* 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();
};
Tuple Shape::normalAt(Tuple point)
{
Tuple local_point = this->worldToObject(point);
Tuple local_normal = this->localNormalAt(local_point);
Tuple world_normal = this->normalToWorld(local_normal);
return world_normal;
}
void Shape::updateTransform()
{
this->transformMatrix = this->localTransformMatrix;
if (this->parent != nullptr)
{
this->transformMatrix = this->parent->transformMatrix * this->transformMatrix;
}
this->inverseTransform = this->transformMatrix.inverse();
this->transposedInverseTransform = this->inverseTransform.transpose();
}
void Shape::setTransform(Matrix transform)
{
this->transformMatrix = transform;
this->inverseTransform = transform.inverse();
this->localTransformMatrix = transform;
this->updateTransform();
}
BoundingBox Shape::getBounds()
{
BoundingBox ret;
ret.min = this->objectToWorld(Point(-1, -1, -1));
ret.max = this->objectToWorld(Point(1, 1, 1));
return ret;
}

View File

@@ -19,6 +19,11 @@ double Tuple::magnitude()
Tuple Tuple::normalise()
{
double mag = this->magnitude();
if (mag == 0)
{
return Tuple(0, 0, 0, 0);
}
return Tuple(this->x / mag, this->y / mag, this->z / mag, this->w / mag);
}
@@ -38,4 +43,28 @@ Tuple Tuple::cross(const Tuple &b) const
Tuple Tuple::reflect(const Tuple &normal)
{
return *this - normal * 2 * this->dot(normal);
}
void Tuple::fixPoint()
{
if (isnan(this->x) || isnan(this->y) || isnan(this->z))
{
/* w is probably broken, so fix it */
this->w = 1;
}
}
void Tuple::fixVector()
{
if (isnan(this->x) || isnan(this->y) || isnan(this->z))
{
/* w is probably broken, so fix it */
this->w = 0;
}
}
bool Tuple::isRepresentable()
{
return !(isnan(this->x) || isnan(this->y) || isnan(this->z));
}

View File

@@ -5,7 +5,8 @@ find_package(Threads REQUIRED)
set(TESTS_SRC math_test.cpp 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 pattern_test.cpp cube_test.cpp)
shape_test.cpp plane_test.cpp pattern_test.cpp cube_test.cpp cylinder_test.cpp cone_test.cpp group_test.cpp
boundingbox_test.cpp)
add_executable(testMyRays)
target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
@@ -24,6 +25,11 @@ target_include_directories(hw3render PUBLIC ../source/include)
target_sources(hw3render PRIVATE hw3render.cpp)
target_link_libraries(hw3render rayonnement)
add_executable(test_render)
target_include_directories(test_render PUBLIC ../source/include)
target_sources(test_render PRIVATE test_render.cpp)
target_link_libraries(test_render rayonnement)
add_executable(ch5_test)
target_include_directories(ch5_test PUBLIC ../source/include)
target_sources(ch5_test PRIVATE ch5_test.cpp)
@@ -69,6 +75,16 @@ target_include_directories(ch12_test PUBLIC ../source/include)
target_sources(ch12_test PRIVATE ch12_test.cpp)
target_link_libraries(ch12_test rayonnement)
add_executable(ch13_test)
target_include_directories(ch13_test PUBLIC ../source/include)
target_sources(ch13_test PRIVATE ch13_test.cpp)
target_link_libraries(ch13_test rayonnement)
add_executable(ch13_cone)
target_include_directories(ch13_cone PUBLIC ../source/include)
target_sources(ch13_cone PRIVATE ch13_cone.cpp)
target_link_libraries(ch13_cone 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>)
@@ -77,4 +93,9 @@ add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)
add_test(NAME Chapter11_Reflection COMMAND $<TARGET_FILE:ch11_reflection>)
add_test(NAME Chapter11_Refraction COMMAND $<TARGET_FILE:ch11_refraction>)
add_test(NAME Chapter11_Test COMMAND $<TARGET_FILE:ch11_test>)
add_test(NAME Chapter12_Test COMMAND $<TARGET_FILE:ch12_test>)
add_test(NAME Chapter12_Test COMMAND $<TARGET_FILE:ch12_test>)
add_test(NAME Chapter13_Test COMMAND $<TARGET_FILE:ch13_test>)
add_test(NAME Chapter13_ConeBonus COMMAND $<TARGET_FILE:ch13_cone>)
add_test(NAME Test_Rendering COMMAND $<TARGET_FILE:test_render>)
add_test(NAME Hw3Render COMMAND $<TARGET_FILE:hw3render> ${CMAKE_CURRENT_SOURCE_DIR}/test.hw3scene)
add_test(NAME Hw3RenderAllCmds COMMAND $<TARGET_FILE:hw3render> ${CMAKE_CURRENT_SOURCE_DIR}/test_keys.hw3scene)

View File

@@ -0,0 +1,81 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Boundingbox unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
/*
* DoRayMe - a quick and dirty Raytracer
* Camera unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <math.h>
#include <math_helper.h>
#include <ray.h>
#include <transformation.h>
#include <stdint.h>
#include <boundingbox.h>
#include <gtest/gtest.h>
TEST(BoundingBox, Default_boundingbox_is_not_set)
{
BoundingBox bb;
ASSERT_TRUE(bb.isEmpty());
ASSERT_EQ(bb.min, Point(INFINITY, INFINITY, INFINITY));
ASSERT_EQ(bb.max, Point(-INFINITY, -INFINITY, -INFINITY));
}
TEST(BoundingBox, Bounding_box_can_be_created_with_values)
{
BoundingBox bb = BoundingBox(Point(-1, -1, -1), Point(1, 1, 1));
ASSERT_FALSE(bb.isEmpty());
ASSERT_EQ(bb.min, Point(-1, -1, -1));
ASSERT_EQ(bb.max, Point(1, 1, 1));
}
TEST(BoundingBox, Cating_a_bb_to_an_empty_bb_reset_the_original_one)
{
BoundingBox bb;
bb | BoundingBox(Point(-1, -1, -1), Point(1, 1, 1));
ASSERT_FALSE(bb.isEmpty());
ASSERT_EQ(bb.min, Point(-1, -1, -1));
ASSERT_EQ(bb.max, Point(1, 1, 1));
}
TEST(BoundingBox, Cating_a_bb_to_another_bb_expand_the_original_one_if_needed)
{
BoundingBox bb(Point(-1, -1, -1), Point(1, 1, 1));
bb | BoundingBox(Point(-2, 0, -5), Point(4, 5, 0.5));
ASSERT_FALSE(bb.isEmpty());
ASSERT_EQ(bb.min, Point(-2, -1, -5));
ASSERT_EQ(bb.max, Point(4, 5, 1));
}
TEST(BoundingBox, A_smaller_bb_should_fit_in_a_bigger)
{
BoundingBox bigBb = BoundingBox(Point(-10, -10, -10), Point(10, 10, 10));
BoundingBox smallBb = BoundingBox(Point(-2, -2, -2), Point(2, 2, 2));
ASSERT_TRUE(bigBb.fitsIn(smallBb));
}
TEST(BoundingBox, A_big_bb_should_not_fit_in_a_smaller)
{
BoundingBox bigBb = BoundingBox(Point(-10, -10, -10), Point(10, 10, 10));
BoundingBox smallBb = BoundingBox(Point(-2, -2, -2), Point(2, 2, 2));
ASSERT_FALSE(smallBb.fitsIn(bigBb));
}

View File

@@ -120,7 +120,7 @@ int main()
/* Little cube 1 */
Cube lilCube1 = Cube();
lilCube1.setTransform(translation(1, 3.25, -0.9) *
lilCube1.setTransform(translation(1, 3.35, -0.9) *
rotationY(-0.4) *
scaling(0.15, 0.15, 0.15));
lilCube1.material.colour = Colour(1, 0.5, 0.5);

204
tests/ch13_cone.cpp Normal file
View File

@@ -0,0 +1,204 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 13.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <cube.h>
#include <cylinder.h>
#include <cone.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()
{
World w = World();
/* Add light */
Light light = Light(POINT_LIGHT, Point(1, 6.9, -4.9), Colour(1, 1, 1));
w.addLight(&light);
/* ----------------------------- */
/* The floor */
Plane floor = Plane();
floor.material.pattern = new CheckersPattern(Colour(0.5, 0.5, 0.5), Colour(0.75, 0.75, 0.75));
floor.material.pattern->setTransform(rotationY(0.3) * scaling(0.25, 0.25, 0.25));
w.addObject(&floor);
Plane ceiling = Plane();
ceiling.material.pattern = new CheckersPattern(Colour(0.5, 0.2, 0.5), Colour(0.75, 0.4, 0.75));
ceiling.material.pattern->setTransform(rotationY(0.3));
ceiling.setTransform(translation(0, 8, 0));
w.addObject(&ceiling);
/* ----------------------------- */
Cone cyl1 = Cone();
cyl1.minCap = -1;
cyl1.maxCap = 0;
cyl1.isClosed = true;
cyl1.setTransform(translation(-1, 0, 1) * scaling(0.5, 1, 0.5) * translation(0, 1, 0));
cyl1.material.colour = Colour(0, 1, 0.6);
cyl1.material.diffuse = 0.1;
cyl1.material.specular = 0.9;
cyl1.material.shininess = 300;
cyl1.material.reflective = 0.3;
w.addObject(&cyl1);
/* ----------------------------- */
/* Concentrics */
Cylinder cons1 = Cylinder();
cons1.minCap = 0;
cons1.maxCap = 0.2;
cons1.isClosed = false;
cons1.setTransform(translation(1, 0, 0) * scaling(0.8, 1, 0.8));
cons1.material.colour = Colour(1, 1, 0.3);
cons1.material.ambient = 0.1;
cons1.material.diffuse = 0.8;
cons1.material.specular = 0.9;
cons1.material.shininess = 300;
cons1.material.reflective = 0.2;
w.addObject(&cons1);
Cylinder cons2 = Cylinder();
cons2.minCap = 0;
cons2.maxCap = 0.3;
cons2.isClosed = false;
cons2.setTransform(translation(1, 0, 0) * scaling(0.6, 1, 0.6));
cons2.material.colour = Colour(1, 0.9, 0.4);
cons2.material.ambient = 0.1;
cons2.material.diffuse = 0.8;
cons2.material.specular = 0.9;
cons2.material.shininess = 300;
cons2.material.reflective = 0.2;
w.addObject(&cons2);
Cylinder cons3 = Cylinder();
cons3.minCap = 0;
cons3.maxCap = 0.4;
cons3.isClosed = false;
cons3.setTransform(translation(1, 0, 0) * scaling(0.4, 1, 0.4));
cons3.material.colour = Colour(1, 0.8, 0.5);
cons3.material.ambient = 0.1;
cons3.material.diffuse = 0.8;
cons3.material.specular = 0.9;
cons3.material.shininess = 300;
cons3.material.reflective = 0.2;
w.addObject(&cons3);
Cylinder cons4 = Cylinder();
cons4.minCap = 0;
cons4.maxCap = 0.5;
cons4.isClosed = true;
cons4.setTransform(translation(1, 0, 0) * scaling(0.2, 1, 0.2));
cons4.material.colour = Colour(1, 0.7, 0.6);
cons4.material.ambient = 0.1;
cons4.material.diffuse = 0.8;
cons4.material.specular = 0.9;
cons4.material.shininess = 300;
cons4.material.reflective = 0.2;
w.addObject(&cons4);
/* ----------------------------- */
/* decoratives cylinders */
Cylinder deco1 = Cylinder();
deco1.minCap = 0;
deco1.maxCap = 0.3;
deco1.isClosed = true;
deco1.setTransform(translation(0, 0, -0.75) * scaling(0.05, 1, 0.05));
deco1.material.colour = Colour(1, 0, 0);
deco1.material.ambient = 0.1;
deco1.material.diffuse = 0.9;
deco1.material.specular = 0.9;
deco1.material.shininess = 300;
deco1.material.reflective = 0.5;
w.addObject(&deco1);
Cylinder deco2 = Cylinder();
deco2.minCap = 0;
deco2.maxCap = 0.3;
deco2.isClosed = true;
deco2.setTransform(translation(0, 0, -2.25) * rotationY(-0.15) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco2.material.colour = Colour(1, 1, 0);
deco2.material.ambient = 0.1;
deco2.material.diffuse = 0.9;
deco2.material.specular = 0.9;
deco2.material.shininess = 300;
deco2.material.reflective = 0.5;
w.addObject(&deco2);
Cylinder deco3 = Cylinder();
deco3.minCap = 0;
deco3.maxCap = 0.3;
deco3.isClosed = true;
deco3.setTransform(translation(0, 0, -2.25) * rotationY(-0.3) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco3.material.colour = Colour(0, 1, 0);
deco3.material.ambient = 0.1;
deco3.material.diffuse = 0.9;
deco3.material.specular = 0.9;
deco3.material.shininess = 300;
deco3.material.reflective = 0.5;
w.addObject(&deco3);
Cylinder deco4 = Cylinder();
deco4.minCap = 0;
deco4.maxCap = 0.3;
deco4.isClosed = true;
deco4.setTransform(translation(0, 0, -2.25) * rotationY(-0.45) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco4.material.colour = Colour(0, 1, 1);
deco4.material.ambient = 0.1;
deco4.material.diffuse = 0.9;
deco4.material.specular = 0.9;
deco4.material.shininess = 300;
deco4.material.reflective = 0.5;
w.addObject(&deco4);
/* ----------------------------- */
Cone glassCylinder = Cone();
glassCylinder.minCap = 0.001;
glassCylinder.maxCap = 1;
glassCylinder.isClosed = true;
glassCylinder.dropShadow = false;
glassCylinder.setTransform(translation(0, 0, -1.5) * scaling(0.33, 1, 0.33));
glassCylinder.material.colour = Colour(0.2, 0.63, 0.24);
glassCylinder.material.diffuse = 0.1;
glassCylinder.material.specular = 0.9;
glassCylinder.material.shininess = 300;
glassCylinder.material.reflective = 0.3;
w.addObject(&glassCylinder);
/* ----------------------------- */
/* Set the camera */
Camera camera = Camera(400, 200, 0.314);
camera.setTransform(viewTransform(Point(8, 3.5, -9),
Point(0, 0.3, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w, 20);
image.SaveAsPNG("ch13_cone.png");
return 0;
}

191
tests/ch13_test.cpp Normal file
View File

@@ -0,0 +1,191 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 13.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <cube.h>
#include <cylinder.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()
{
World w = World();
/* Add light */
Light light = Light(POINT_LIGHT, Point(1, 6.9, -4.9), Colour(1, 1, 1));
w.addLight(&light);
/* ----------------------------- */
/* The floor / ceiling */
Plane floor = Plane();
floor.material.pattern = new CheckersPattern(Colour(0.5, 0.5, 0.5), Colour(0.75, 0.75, 0.75));
floor.material.pattern->setTransform(rotationY(0.3) * scaling(0.25, 0.25, 0.25));
w.addObject(&floor);
/* ----------------------------- */
Cylinder cyl1 = Cylinder();
cyl1.minCap = 0;
cyl1.maxCap = 0.75;
cyl1.isClosed = true;
cyl1.setTransform(translation(-1, 0, 1) * scaling(0.5, 1, 0.5));
cyl1.material.colour = Colour(0, 0, 0.6);
cyl1.material.diffuse = 0.1;
cyl1.material.specular = 0.9;
cyl1.material.shininess = 300;
cyl1.material.reflective = 0.9;
w.addObject(&cyl1);
/* ----------------------------- */
/* Concentrics */
Cylinder cons1 = Cylinder();
cons1.minCap = 0;
cons1.maxCap = 0.2;
cons1.isClosed = false;
cons1.setTransform(translation(1, 0, 0) * scaling(0.8, 1, 0.8));
cons1.material.colour = Colour(1, 1, 0.3);
cons1.material.ambient = 0.1;
cons1.material.diffuse = 0.8;
cons1.material.specular = 0.9;
cons1.material.shininess = 300;
w.addObject(&cons1);
Cylinder cons2 = Cylinder();
cons2.minCap = 0;
cons2.maxCap = 0.3;
cons2.isClosed = false;
cons2.setTransform(translation(1, 0, 0) * scaling(0.6, 1, 0.6));
cons2.material.colour = Colour(1, 0.9, 0.4);
cons2.material.ambient = 0.1;
cons2.material.diffuse = 0.8;
cons2.material.specular = 0.9;
cons2.material.shininess = 300;
w.addObject(&cons2);
Cylinder cons3 = Cylinder();
cons3.minCap = 0;
cons3.maxCap = 0.4;
cons3.isClosed = false;
cons3.setTransform(translation(1, 0, 0) * scaling(0.4, 1, 0.4));
cons3.material.colour = Colour(1, 0.8, 0.5);
cons3.material.ambient = 0.1;
cons3.material.diffuse = 0.8;
cons3.material.specular = 0.9;
cons3.material.shininess = 300;
w.addObject(&cons3);
Cylinder cons4 = Cylinder();
cons4.minCap = 0;
cons4.maxCap = 0.5;
cons4.isClosed = true;
cons4.setTransform(translation(1, 0, 0) * scaling(0.2, 1, 0.2));
cons4.material.colour = Colour(1, 0.7, 0.6);
cons4.material.ambient = 0.1;
cons4.material.diffuse = 0.8;
cons4.material.specular = 0.9;
cons4.material.shininess = 300;
w.addObject(&cons4);
/* ----------------------------- */
/* decoratives cylinders */
Cylinder deco1 = Cylinder();
deco1.minCap = 0;
deco1.maxCap = 0.3;
deco1.isClosed = true;
deco1.setTransform(translation(0, 0, -0.75) * scaling(0.05, 1, 0.05));
deco1.material.colour = Colour(1, 0, 0);
deco1.material.ambient = 0.1;
deco1.material.diffuse = 0.9;
deco1.material.specular = 0.9;
deco1.material.shininess = 300;
w.addObject(&deco1);
Cylinder deco2 = Cylinder();
deco2.minCap = 0;
deco2.maxCap = 0.3;
deco2.isClosed = true;
deco2.setTransform(translation(0, 0, -2.25) * rotationY(-0.15) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco2.material.colour = Colour(1, 1, 0);
deco2.material.ambient = 0.1;
deco2.material.diffuse = 0.9;
deco2.material.specular = 0.9;
deco2.material.shininess = 300;
w.addObject(&deco2);
Cylinder deco3 = Cylinder();
deco3.minCap = 0;
deco3.maxCap = 0.3;
deco3.isClosed = true;
deco3.setTransform(translation(0, 0, -2.25) * rotationY(-0.3) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco3.material.colour = Colour(0, 1, 0);
deco3.material.ambient = 0.1;
deco3.material.diffuse = 0.9;
deco3.material.specular = 0.9;
deco3.material.shininess = 300;
w.addObject(&deco3);
Cylinder deco4 = Cylinder();
deco4.minCap = 0;
deco4.maxCap = 0.3;
deco4.isClosed = true;
deco4.setTransform(translation(0, 0, -2.25) * rotationY(-0.45) * translation(0, 0, 1.5) * scaling(0.05, 1, 0.05));
deco4.material.colour = Colour(0, 1, 1);
deco4.material.ambient = 0.1;
deco4.material.diffuse = 0.9;
deco4.material.specular = 0.9;
deco4.material.shininess = 300;
w.addObject(&deco4);
/* ----------------------------- */
/* glass cylinder */
Cylinder glassCylinder = Cylinder();
glassCylinder.minCap = 0.0001;
glassCylinder.maxCap = 0.5;
glassCylinder.isClosed = true;
glassCylinder.setTransform(translation(0, 0, -1.5) * scaling(0.33, 1, 0.33));
glassCylinder.material.colour = Colour(0.25, 0, 0);
glassCylinder.material.diffuse = 0.1;
glassCylinder.material.specular = 0.9;
glassCylinder.material.shininess = 300;
glassCylinder.material.reflective = 0.9;
glassCylinder.material.transparency = 0.9;
glassCylinder.material.refractiveIndex = 1.5;
w.addObject(&glassCylinder);
/* ----------------------------- */
/* Set the camera */
Camera camera = Camera(400, 200, 0.314);
camera.setTransform(viewTransform(Point(8, 3.5, -9),
Point(0, 0.3, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w, 20);
image.SaveAsPNG("ch13_test.png");
return 0;
}

170
tests/cone_test.cpp Normal file
View File

@@ -0,0 +1,170 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cone unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersect.h>
#include <intersection.h>
#include <cone.h>
#include <transformation.h>
#include <gtest/gtest.h>
class ConeTest : public Cone
{
public:
Tuple doLocalNormalAt(Tuple point)
{
return localNormalAt(point);
}
};
TEST(ConeTest, Intersecting_a_cone_with_a_ray)
{
Cone cone = Cone();
Point Origins[] = {
Point(0, 0, -5),
Point(0, 0, -5),
Point(1, 1, -5),
};
Vector Directions[] = {
Vector(0, 0, 1),
Vector(1, 1, 1),
Vector(-0.5, -1, 1),
};
double t0s[] = { 5, 8.66025, 4.55006 };
double t1s[] = { 5, 8.66025, 49.44994 };
int i;
for(i = 0; i < 3; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cone.intersect(r);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(xs.count(), 2);
EXPECT_TRUE(double_equal(xs[0].t, t0s[i]));
EXPECT_TRUE(double_equal(xs[1].t, t1s[i]));
set_equal_precision(FLT_EPSILON);
}
}
TEST(ConeTest, Intersecting_a_cone_with_a_ray_parall_to_one_of_its_halves)
{
Cone cone = Cone();
Tuple direction = Vector(0, 1, 1).normalise();
Ray r = Ray(Point(0, 0, -1), direction);
Intersect xs = cone.intersect(r);
ASSERT_EQ(xs.count(), 1);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_TRUE(double_equal(xs[0].t, 0.35355));
set_equal_precision(FLT_EPSILON);
}
TEST(ConeTest, Intersecting_a_cone_end_cap)
{
Point Origins[] = {
Point(0, 0, -5),
Point(0, 0, -0.25),
Point(0, 0, -0.25),
};
Vector Directions[] = {
Vector(0, 1, 0),
Vector(0, 1, 1),
Vector(0, 1, 0),
};
uint32_t Counts[] = { 0, 2, 4 };
Cone cone = Cone();
cone.minCap = -0.5;
cone.maxCap = 0.5;
cone.isClosed = true;
int i;
for(i = 0; i < 3; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cone.intersect(r);
ASSERT_EQ(xs.count(), Counts[i]);
}
}
TEST(ConeTest, Computing_the_normal_vector_on_a_cone)
{
ConeTest cone = ConeTest();
Point HitPointss[] = {
Point(0, 0, 0),
Point(1, 1, 1),
Point(-1, -1, 0),
};
Vector Normals[] = {
Vector(0, 0, 0),
Vector(1, -sqrt(2), 1),
Vector(-1, 1, 0),
};
int i;
for(i = 0; i < 3; i++)
{
ASSERT_EQ(cone.doLocalNormalAt(HitPointss[i]), Normals[i]);
}
}
TEST(ConeTest, The_bounding_box_of_a_cut_cone)
{
Cone t = Cone();
BoundingBox b = BoundingBox(Point(-8, -5, -8), Point(8, 8, 8));
t.minCap = -5;
t.maxCap = 8;
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(ConeTest, The_bounding_box_of_a_uncut_cone)
{
/* This one is tricky. Infinite size don't cope well with transformations */
Cone t = Cone();
BoundingBox res = t.getBounds();
ASSERT_FALSE(res.min.isRepresentable());
ASSERT_FALSE(res.max.isRepresentable());
}
TEST(ConeTest, An_uncut_cone_have_infinite_bounds)
{
Cone t = Cone();
ASSERT_FALSE(t.haveFiniteBounds());
}
TEST(ConeTest, A_cut_cone_have_finite_bounds)
{
Cone t = Cone();
t.minCap = -1;
t.maxCap = 1;
ASSERT_TRUE(t.haveFiniteBounds());
}

View File

@@ -114,4 +114,22 @@ TEST(CubeTest, The_normal_on_the_surface_of_a_cube)
{
ASSERT_EQ(c.normalAt(HitPoints[i]), ExpectedNormals[i]);
}
}
TEST(CubeTest, The_bounding_box_of_a_cube)
{
Cube t = Cube();
BoundingBox b = BoundingBox(Point(-1, -1, -1), Point(1, 1, 1));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(CubeTest, A_cube_have_finite_bounds)
{
Cube t = Cube();
ASSERT_TRUE(t.haveFiniteBounds());
}

261
tests/cylinder_test.cpp Normal file
View File

@@ -0,0 +1,261 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cylinder unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersect.h>
#include <intersection.h>
#include <cylinder.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(CylinderTest, A_ray_miss_a_cylinder)
{
Cylinder cyl = Cylinder();
Point Origins[] = {
Point(1, 0, 0),
Point(0, 0, 0),
Point(0, 0, -5),
};
Vector Directions[] = {
Vector(0, 1, 0),
Vector(0, 1, 0),
Vector(1, 1, 1),
};
int i;
for(i = 0; i < 3; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
}
TEST(CylinderTest, A_ray_hit_a_cylinder)
{
Cylinder cyl = Cylinder();
Point Origins[] = {
Point(1, 0, -5),
Point(0, 0, -5),
Point(0.5, 0, -5),
};
Vector Directions[] = {
Vector(0, 0, 1),
Vector(0, 0, 1),
Vector(0.1, 1, 1),
};
double t0s[] = { 5, 4, 6.80798 };
double t1s[] = { 5, 6, 7.08872 };
int i, j;
for(i = 0; i < 3; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(xs.count(), 2);
EXPECT_TRUE(double_equal(xs[0].t, t0s[i]));
EXPECT_TRUE(double_equal(xs[1].t, t1s[i]));
set_equal_precision(FLT_EPSILON);
}
}
TEST(CylinderTest, Normal_vector_on_a_cylinder)
{
Cylinder cyl = Cylinder();
Point HitPointss[] = {
Point(1, 0, 0),
Point(0, 5, -1),
Point(0, -2, 1),
Point(-1, 1, 0),
};
Vector Normals[] = {
Vector(1, 0, 0),
Vector(0, 0, -1),
Vector(0, 0, 1),
Vector(-1, 0, 0),
};
int i;
for(i = 0; i < 4; i++)
{
ASSERT_EQ(cyl.normalAt(HitPointss[i]), Normals[i]);
}
}
TEST(CylinderTest, The_default_minimum_and_maximum_for_a_cylinder)
{
Cylinder cyl = Cylinder();
ASSERT_EQ(cyl.minCap, -INFINITY);
ASSERT_EQ(cyl.maxCap, INFINITY);
}
TEST(CylinderTest, Intersecting_a_constrained_cylinder)
{
Point Origins[] = {
Point(0, 1.5, 0),
Point(0, 3, -5),
Point(0, 0, -5),
Point(0, 2, -5),
Point(0, 1, -5),
Point(0, 1.5, -2),
};
Vector Directions[] = {
Vector(0.1, 1, 0),
Vector(0, 0, 1),
Vector(0, 0, 1),
Vector(0, 0, 1),
Vector(0, 0, 1),
Vector(0., 0, 1),
};
uint32_t Counts[] = { 0, 0, 0, 0, 0, 2 };
Cylinder cyl = Cylinder();
cyl.minCap = 1;
cyl.maxCap = 2;
int i;
for(i = 0; i < 6; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
ASSERT_EQ(xs.count(), Counts[i]);
}
}
TEST(CylinderTest, The_default_closed_value_for_a_cylinder)
{
Cylinder cyl = Cylinder();
ASSERT_EQ(cyl.isClosed, false);
}
TEST(CylinderTest, Intersecting_the_caps_of_a_close_cylinder)
{
Point Origins[] = {
Point(0, 3, 0),
Point(0, 3, -2),
Point(0, 4, -2), /* Edge case */
Point(0, 0, -5),
Point(0, -1, -2), /* Edge case */
};
Vector Directions[] = {
Vector(0, -1, 0),
Vector(0, -1, 2),
Vector(0, -1, 1),
Vector(0, 1, 2),
Vector(0, 1, 1),
};
uint32_t Counts[] = { 2, 2, 2, 2, 2 };
Cylinder cyl = Cylinder();
cyl.minCap = 1;
cyl.maxCap = 2;
cyl.isClosed = true;
int i;
for(i = 0; i < 5; i++)
{
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
ASSERT_EQ(xs.count(), Counts[i]);
}
}
TEST(CylinderTest, The_normal_on_a_cylinder_end_cap)
{
Cylinder cyl = Cylinder();
Point HitPointss[] = {
Point(0, 1, 0),
Point(0.5, 1, 0),
Point(0, 1, 0.5),
Point(0, 2, 0),
Point(0.5, 2, 0),
Point(0, 2, 0.5),
};
Vector Normals[] = {
Vector(0, -1, 0),
Vector(0, -1, 0),
Vector(0, -1, 0),
Vector(0, 1, 0),
Vector(0, 1, 0),
Vector(0, 1, 0),
};
cyl.minCap = 1;
cyl.maxCap = 2;
cyl.isClosed = true;
int idx;
for(idx = 0; idx < 6; idx++)
{
ASSERT_EQ(cyl.normalAt(HitPointss[idx]), Normals[idx]);
}
}
TEST(CylinderTest, The_bounding_box_of_a_cut_cylinder)
{
Cylinder t = Cylinder();
BoundingBox b = BoundingBox(Point(-1, -10000, -1), Point(1, 10000, 1));
t.minCap = -10000;
t.maxCap = 10000;
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(CylinderTest, The_bounding_box_of_a_uncut_cylinder)
{
/* This one is tricky. Infinite size don't cope well with transformations */
Cylinder t = Cylinder();
BoundingBox res = t.getBounds();
ASSERT_FALSE(res.min.isRepresentable());
ASSERT_FALSE(res.max.isRepresentable());
}
TEST(CylinderTest, An_uncut_cylinder_have_infinite_bounds)
{
Cylinder t = Cylinder();
ASSERT_FALSE(t.haveFiniteBounds());
}
TEST(CylinderTest, A_cut_cylinder_have_finite_bounds)
{
Cylinder t = Cylinder();
t.minCap = -1;
t.maxCap = 1;
ASSERT_TRUE(t.haveFiniteBounds());
}

100
tests/group_test.cpp Normal file
View File

@@ -0,0 +1,100 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Group unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersect.h>
#include <intersection.h>
#include <group.h>
#include <testshape.h>
#include <sphere.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(GroupTest, Creating_a_new_group)
{
Group g = Group();
ASSERT_EQ(g.transformMatrix, Matrix4().identity());
ASSERT_TRUE(g.isEmpty());
}
TEST(GroupTest, Adding_a_child_to_a_group)
{
Group g = Group();
TestShape s = TestShape();
g.addObject(&s);
ASSERT_FALSE(g.isEmpty());
ASSERT_EQ(s.parent, &g);
ASSERT_EQ(g[0], &s);
}
TEST(GroupTest, Intersecting_a_ray_with_an_empty_group)
{
Group g = Group();
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
TEST(GroupTest, Intersecting_a_ray_with_an_nonempty_group)
{
Group g = Group();
Sphere s1 = Sphere();
Sphere s2 = Sphere();
Sphere s3 = Sphere();
s2.setTransform(translation(0, 0, -3));
s3.setTransform(translation(5, 0, 0));
g.addObject(&s1);
g.addObject(&s2);
g.addObject(&s3);
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
ASSERT_EQ(xs.count(), 4);
EXPECT_EQ(xs[0].object, &s2);
EXPECT_EQ(xs[1].object, &s2);
EXPECT_EQ(xs[2].object, &s1);
EXPECT_EQ(xs[3].object, &s1);
}
TEST(GroupTest, Intersecting_a_transformed_group)
{
Group g = Group();
Sphere s = Sphere();
g.setTransform(scaling(2, 2, 2));
s.setTransform(translation(5, 0, 0));
g.addObject(&s);
Ray r = Ray(Point(10, 0, -50), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
ASSERT_EQ(xs.count(), 2);
}
TEST(GroupTest, Group_bounding_box)
{
Group g = Group();
Sphere s = Sphere();
g.setTransform(scaling(2, 2, 2));
s.setTransform(translation(5, 0, 0));
g.addObject(&s);
BoundingBox b = BoundingBox(Point(8, -2, -2), Point(12, 2, 2));
BoundingBox res = g.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}

View File

@@ -121,4 +121,47 @@ TEST(MaterialTest, Transparency_and_refractive_index_for_the_default_material)
ASSERT_EQ(m.transparency, 0.0);
ASSERT_EQ(m.refractiveIndex, 1.0);
}
TEST(MaterialTest, Equality_tests)
{
Material m = Material();
Material m2 = Material();
ASSERT_EQ(m, m2);
m.ambient = 42;
ASSERT_NE(m, m2);
m.ambient = m2.ambient;
m.diffuse = 42;
ASSERT_NE(m, m2);
m.diffuse = m2.diffuse;
m.specular = 42;
ASSERT_NE(m, m2);
m.specular = m2.specular;
m.shininess = 42;
ASSERT_NE(m, m2);
m.shininess = m2.shininess;
m.reflective = 42;
ASSERT_NE(m, m2);
m.reflective = m2.reflective;
m.transparency = 42;
ASSERT_NE(m, m2);
m.transparency = m2.transparency;
m.emissive = 42;
ASSERT_NE(m, m2);
m.emissive = m2.emissive;
m.refractiveIndex = 42;
ASSERT_NE(m, m2);
m.refractiveIndex = m2.refractiveIndex;
m.colour = Colour(32, 32, 32);
ASSERT_NE(m, m2);
}

View File

@@ -68,4 +68,21 @@ TEST(PlaneTest, A_ray_intersecting_a_plane_from_below)
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[0].object, &p);
}
TEST(PlaneTest, The_bounding_box_of_a_plane)
{
Plane t = Plane();
BoundingBox b = BoundingBox(Point(-8, -5, -8), Point(8, 8, 8));
BoundingBox res = t.getBounds();
ASSERT_FALSE(res.min.isRepresentable());
ASSERT_FALSE(res.max.isRepresentable());
}
TEST(PlaneTest, A_plane_have_infinite__bounds)
{
Plane t = Plane();
ASSERT_FALSE(t.haveFiniteBounds());
}

View File

@@ -9,6 +9,8 @@
#include <shape.h>
#include <testshape.h>
#include <matrix.h>
#include <group.h>
#include <sphere.h>
#include <transformation.h>
#include <gtest/gtest.h>
@@ -95,4 +97,107 @@ TEST(ShapeTest, Computing_the_normal_on_a_tranformed_shape)
ASSERT_EQ(n, Vector(0, 0.97014, -0.24254));
set_equal_precision(FLT_EPSILON);
}
TEST(ShapeTest, A_shape_has_a_parent_attribute)
{
TestShape s = TestShape();
ASSERT_EQ(s.parent, nullptr);
}
TEST(ShapeTest, Converting_a_point_from_world_to_object_space)
{
Group g1 = Group();
g1.setTransform(rotationY(M_PI / 2));
Group g2 = Group();
g2.setTransform(scaling(2, 2, 2));
g1.addObject(&g2);
Sphere s = Sphere();
s.setTransform(translation(5, 0, 0));
g2.addObject(&s);
Tuple p = s.worldToObject(Point(-2, 0, -10));
ASSERT_EQ(p, Point(0, 0, -1));
}
TEST(ShapeTest, Converting_a_normal_form_object_to_world_space)
{
Group g1 = Group();
g1.setTransform(rotationY(M_PI / 2));
Group g2 = Group();
g2.setTransform(scaling(1, 2, 3));
g1.addObject(&g2);
Sphere s = Sphere();
s.setTransform(translation(5, 0, 0));
g2.addObject(&s);
Tuple p = s.normalToWorld(Vector(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3));
/* Temporary lower the precision */
set_equal_precision(0.0001);
ASSERT_EQ(p, Vector(0.2857, 0.4286, -0.8571));
set_equal_precision(FLT_EPSILON);
}
TEST(ShapeTest, Finding_the_normal_on_a_child_object)
{
Group g1 = Group();
g1.setTransform(rotationY(M_PI / 2));
Group g2 = Group();
g2.setTransform(scaling(1, 2, 3));
g1.addObject(&g2);
Sphere s = Sphere();
s.setTransform(translation(5, 0, 0));
g2.addObject(&s);
Tuple p = s.normalAt(Point(1.7321, 1.1547, -5.5774));
/* Temporary lower the precision */
set_equal_precision(0.0001);
ASSERT_EQ(p, Vector(0.2857, 0.4286, -0.8571));
set_equal_precision(FLT_EPSILON);
}
TEST(ShapeTest, Test_the_bouding_box_of_the_test_shape)
{
TestShape t = TestShape();
BoundingBox b = BoundingBox(Point(-1, -1, -1), Point(1, 1, 1));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(ShapeTest, Test_the_bouding_box_of_the_scaled_shape)
{
TestShape t = TestShape();
t.setTransform(scaling(3, 3, 3));
BoundingBox b = BoundingBox(Point(-3, -3, -3), Point(3, 3, 3));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(ShapeTest, Test_the_bouding_box_of_the_translated_shape)
{
TestShape t = TestShape();
t.setTransform(translation(10, 0, 0));
BoundingBox b = BoundingBox(Point(9, -1, -1), Point(11, 1, 1));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}

View File

@@ -207,4 +207,22 @@ TEST(SphereTest, A_helper_for_producing_a_sphere_with_a_glassy_material)
ASSERT_EQ(s.transformMatrix, Matrix4().identity());
ASSERT_EQ(s.material.transparency, 1.0);
ASSERT_EQ(s.material.refractiveIndex, 1.5);
}
TEST(SphereTest, The_bounding_box_of_a_sphere)
{
Sphere t = Sphere();
BoundingBox b = BoundingBox(Point(-1, -1, -1), Point(1, 1, 1));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(SphereTest, A_sphere_have_finite_bounds)
{
Sphere t = Sphere();
ASSERT_TRUE(t.haveFiniteBounds());
}

9
tests/test.hw3scene Normal file
View File

@@ -0,0 +1,9 @@
# A ball lit by 3 lights
camera 0 0 -1 0 0 0 0 1 0 45
point 0 4 0 .7 .2 .2
#point 4 0 4 .5 .5 .5
point 4 -4 0 .2 .7 .2
point -4 -4 0 .2 .2 .7
sphere 0 0 0 .3

26
tests/test_keys.hw3scene Normal file
View File

@@ -0,0 +1,26 @@
# line with a comment
camera 0 0 -1 0 0 0 0 1 0 50
point 0 4 0 .7 .2 .2
point 4 -4 0 .2 .7 .2
point -4 -4 0 .2 .2 .7
color 1 1 1
ambient 1 1 1
sphere 0 0 0 .3
color 1 0 0
diffuse 0.4 0.4 0.4
specular 0.4 0.4 0.4
shininess 1
emission 0 0 0
pushTransform
scale 0.1 0.1 0.1
rotate 0 1 0 45
popTransform
pushTransform
translate .4 0 0
sphere 0 0 0 .1
popTransform

133
tests/test_render.cpp Normal file
View File

@@ -0,0 +1,133 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 13.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <cube.h>
#include <cylinder.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()
{
World w = World();
/* Add light */
Light light = Light(POINT_LIGHT, Point(-5, 5, -5), Colour(1, 1, 1));
w.addLight(&light);
/* ----------------------------- */
/* Environment */
Plane p1 = Plane();
p1.setTransform(translation(0, 0, 5) * rotationX(-M_PI/2));
p1.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0.1, 0.1, 0.1));
w.addObject(&p1);
Plane p2 = Plane();
p2.setTransform( translation(0, 0, -6) * rotationX(-M_PI/2) );
p2.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0.1, 0.1, 0.1));
w.addObject(&p2);
Plane p3 = Plane();
p3.setTransform(translation(-6, 0, 0) * rotationZ(M_PI/2) );
p3.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0.1, 0.1, 0.1));
w.addObject(&p3);
Plane p4 = Plane();
p3.setTransform(translation(6, 0, 0) * rotationZ(M_PI/2) );
p3.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0.1, 0.1, 0.1));
w.addObject(&p3);
Plane p5 = Plane();
p5.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0.1, 0.1, 0.1));
w.addObject(&p5);
/* ----------------------------- */
/* Big Sphere */
Sphere bigS = Sphere();
bigS.setTransform(translation(-0.5, 1, 2) * scaling(2, 2, 2));
bigS.material.colour = Colour(0, 0.4, 0.4);
bigS.material.shininess = 50;
bigS.material.specular = 1;
bigS.material.reflective = 0.8;
w.addObject(&bigS);
/* Small Sphere */
Sphere smaS = Sphere();
smaS.setTransform(translation(2.2, 1, -1));
smaS.material.colour = Colour(0.4, 0, 0.4);
smaS.material.shininess = 50;
smaS.material.specular = 1;
smaS.material.reflective = 0.2;
w.addObject(&smaS);
/* Cylinder */
Cylinder cyl = Cylinder();
cyl.setTransform(translation(-3.5, 0, 1));
cyl.isClosed = true;
cyl.minCap = -1;
cyl.maxCap = 3;
cyl.material.colour = Colour(0.4, 1, 0.4);
cyl.material.shininess = 20;
cyl.material.specular = 1;
cyl.material.reflective = 0.01;
cyl.material.pattern = new GradientPattern(Colour(0.4, 1, 0.4), Colour(1.0, 0.2, 1.0));
cyl.material.pattern->setTransform(scaling(1.1, 1.1, 1.1) * rotationY(M_PI / 2));
w.addObject(&cyl);
/* Cube */
Cube cub = Cube();
cub.setTransform(translation(0.6, 0.4, -1) * scaling(0.4, 0.4, 0.4) * rotationY(deg_to_rad(60)));
cub.material.colour = Colour(1, 0, 0.4);
cub.material.shininess = 50;
cub.material.specular = 1;
cub.material.reflective = 0.3;
w.addObject(&cub);
/* Glass cube */
Cube gcub = Cube();
gcub.setTransform(translation(-0.5, 0.6, -0.8) * scaling(0.6, 0.6, 0.6) * rotationY(deg_to_rad(40)));
gcub.dropShadow = false;
gcub.material.colour = Colour(0, 0.4, 0.1);
gcub.material.specular = 1;
gcub.material.shininess = 400;
gcub.material.ambient = 0.1;
gcub.material.diffuse = 0.3;
gcub.material.reflective = 1;
gcub.material.transparency = 1;
gcub.material.refractiveIndex = 0.3;
w.addObject(&gcub);
/* ----------------------------- */
/* Set the camera */
Camera camera = Camera(1280, 900, deg_to_rad(90));
camera.setTransform(viewTransform(Point(-2, 2.5, -3.5),
Point(2, 0, 3),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w, 20);
image.SaveAsPNG("test_renter.png");
return 0;
}

View File

@@ -22,6 +22,22 @@ TEST(TupleTest, Tuple_With_w_equal_1_and_is_point)
ASSERT_FALSE(a.isVector());
}
TEST(TupleTest, Two_tuples_are_equal)
{
Tuple a = Tuple(1, 2, 3, 4);
Tuple b = Tuple(1, 2, 3, 4);
Tuple c = Tuple(4, 3, 2, 1);
Tuple d = Tuple(1, 2, 3, 5);
Tuple e = Tuple(1, 2, 5, 5);
Tuple f = Tuple(1, 5, 5, 5);
ASSERT_EQ(a, b);
ASSERT_NE(a, c);
ASSERT_NE(a, d);
ASSERT_NE(a, e);
ASSERT_NE(a, f);
}
TEST(TupleTest, Tuple_With_w_equal_0_and_is_vector)
{
Tuple a = Tuple(4.3, -4.2, 3.1, 0.0);