Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea4abdce7 | ||
|
|
831a096281 | ||
|
|
3011544e8f | ||
|
|
d1965caf8d | ||
|
|
7bbe5e843b | ||
|
|
7c794f0496 | ||
|
|
80f59efa43 | ||
|
|
f226664fe3 | ||
|
|
0650ac7b44 | ||
|
|
d87bbb184e | ||
|
|
b9bacd3ac9 | ||
|
|
1d685de8fd | ||
|
|
9c35cfc4f3 | ||
|
|
56095169eb | ||
|
|
60db274214 | ||
|
|
566be9bcf6 |
@@ -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)
|
||||
|
||||
24
README.md
24
README.md
@@ -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:
|
||||
|
||||

|
||||
|
||||
From Chapter 06:
|
||||
From Chapter 06 - Phong shading:
|
||||
|
||||

|
||||
|
||||
From Chapter 07:
|
||||
From Chapter 07 - World / Camera / Scenes:
|
||||
|
||||

|
||||
|
||||
From Chapter 08:
|
||||
From Chapter 08 - Shadows:
|
||||
|
||||

|
||||
|
||||
From Chapter 09:
|
||||
From Chapter 09 - Planes:
|
||||
|
||||

|
||||
|
||||
From Chapter 10:
|
||||
From Chapter 10 - Patterns:
|
||||
|
||||

|
||||
|
||||
From Chapter 11:
|
||||
From Chapter 11 - Reflections, Transparency & Refractions
|
||||
|
||||

|
||||
|
||||
@@ -46,6 +46,12 @@ From Chapter 11:
|
||||
|
||||

|
||||
|
||||
From Chapter 12:
|
||||
From Chapter 12 - Cubes:
|
||||
|
||||

|
||||

|
||||
|
||||
From Chapter 13 - Cylinders:
|
||||
|
||||

|
||||
Bonus:
|
||||

|
||||
BIN
output/ch12_test.png
Normal file
BIN
output/ch12_test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
BIN
output/ch13_cone.png
Normal file
BIN
output/ch13_cone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
BIN
output/ch13_test.png
Normal file
BIN
output/ch13_test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
106
source/include/boundingbox.h
Normal file
106
source/include/boundingbox.h
Normal 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
35
source/include/cone.h
Normal 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
36
source/include/cylinder.h
Normal 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
48
source/include/group.h
Normal 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 */
|
||||
@@ -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); };
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ private:
|
||||
|
||||
public:
|
||||
Plane() : Shape(SHAPE_PLANE) { };
|
||||
BoundingBox getBounds();
|
||||
bool haveFiniteBounds() { return false; };
|
||||
};
|
||||
|
||||
#endif //DORAYME_PLANE_H
|
||||
|
||||
@@ -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); };
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
class Sphere : public Shape
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
Intersect localIntersect(Ray r);
|
||||
Tuple localNormalAt(Tuple point);
|
||||
|
||||
|
||||
@@ -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) {};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ Intersect::Intersect()
|
||||
Intersect::~Intersect()
|
||||
{
|
||||
/* Free stuff */
|
||||
free(this->list);
|
||||
}
|
||||
|
||||
void Intersect::add(Intersection i)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
141
source/shapes/cone.cpp
Normal 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
122
source/shapes/cylinder.cpp
Normal 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
139
source/shapes/group.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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)
|
||||
81
tests/boundingbox_test.cpp
Normal file
81
tests/boundingbox_test.cpp
Normal 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));
|
||||
}
|
||||
@@ -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
204
tests/ch13_cone.cpp
Normal 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
191
tests/ch13_test.cpp
Normal 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
170
tests/cone_test.cpp
Normal 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());
|
||||
}
|
||||
@@ -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
261
tests/cylinder_test.cpp
Normal 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
100
tests/group_test.cpp
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
9
tests/test.hw3scene
Normal 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
26
tests/test_keys.hw3scene
Normal 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
133
tests/test_render.cpp
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user