Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea4abdce7 | ||
|
|
831a096281 | ||
|
|
3011544e8f | ||
|
|
d1965caf8d | ||
|
|
7bbe5e843b | ||
|
|
7c794f0496 | ||
|
|
80f59efa43 |
@@ -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)
|
||||
|
||||
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
|
||||
@@ -28,6 +28,8 @@ public:
|
||||
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 */
|
||||
|
||||
@@ -28,6 +28,9 @@ public:
|
||||
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 */
|
||||
@@ -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
|
||||
{
|
||||
@@ -25,6 +26,8 @@ enum ShapeType
|
||||
SHAPE_CUBE,
|
||||
SHAPE_CYLINDER,
|
||||
SHAPE_CONE,
|
||||
SHAPE_GROUP,
|
||||
|
||||
};
|
||||
|
||||
/* Base class for all object that can be presented in the world */
|
||||
@@ -32,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); };
|
||||
@@ -39,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);
|
||||
@@ -49,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -122,3 +122,20 @@ Tuple Cone::localNormalAt(Tuple point)
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -107,3 +107,16 @@ Tuple Cylinder::localNormalAt(Tuple point)
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,3 +34,16 @@ 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;
|
||||
}
|
||||
@@ -44,3 +44,27 @@ 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 cylinder_test.cpp cone_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)
|
||||
@@ -90,4 +96,6 @@ add_test(NAME Chapter11_Test COMMAND $<TARGET_FILE:ch11_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));
|
||||
}
|
||||
@@ -130,3 +130,41 @@ TEST(ConeTest, Computing_the_normal_vector_on_a_cone)
|
||||
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());
|
||||
}
|
||||
@@ -115,3 +115,21 @@ 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());
|
||||
}
|
||||
@@ -221,3 +221,41 @@ TEST(CylinderTest, The_normal_on_a_cylinder_end_cap)
|
||||
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);
|
||||
}
|
||||
@@ -69,3 +69,20 @@ TEST(PlaneTest, A_ray_intersecting_a_plane_from_below)
|
||||
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>
|
||||
|
||||
@@ -96,3 +98,106 @@ TEST(ShapeTest, Computing_the_normal_on_a_tranformed_shape)
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -208,3 +208,21 @@ TEST(SphereTest, A_helper_for_producing_a_sphere_with_a_glassy_material)
|
||||
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());
|
||||
}
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user