diff --git a/README.md b/README.md index 66fe933..db89468 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,12 @@ DoRayMe A Quick and dirty raytracer. -This raytracer is made following the book "[The Ray Tracer Challenge](https://pragprog.com/book/jbtracer/the-ray-tracer-challenge)" by Jamis Buck. +This raytracer is made following the book +"[The Ray Tracer Challenge](https://pragprog.com/book/jbtracer/the-ray-tracer-challenge)" by Jamis Buck. -It is writen in C++ with no STL and use [LodePNG](https://github.com/lvandeve/lodepng) to output PNG file. +It is writen in C++ with no STL and use [LodePNG](https://github.com/lvandeve/lodepng) to output PNG file and use them +as texture, also use [NanoJPEG](https://keyj.emphy.de/nanojpeg/) to use jpeg file as texture, and can use use +[Lua 5.3](https://www.lua.org/) for 3D pattern definition and more to come on the Lua side later.. Examples outputs @@ -65,6 +68,8 @@ Bonus: **From Chapter 14 - Groups & Bounding boxes:** ![Chapter 14 rendering test](output/ch14_test.png) +**From Chapter 15 - Triangles, Wavefrom OBJ files - Smooth trianges:** +![Chapter 15 Triangles and teapots](output/ch15_teapot_objfile.png) **Bonus (from the forum):** diff --git a/external/teapot-lowtri.obj b/external/teapot-lowtri.obj new file mode 100644 index 0000000..560dd14 --- /dev/null +++ b/external/teapot-lowtri.obj @@ -0,0 +1,274 @@ +# +# object Teapot001 +# + +v 7.0000 0.0000 12.0000 +v 4.9700 -4.9700 12.0000 +v 4.9811 -4.9811 12.4922 +v 7.0156 0.0000 12.4922 +v 5.3250 -5.3250 12.0000 +v 7.5000 0.0000 12.0000 +v 0.0000 -7.0000 12.0000 +v 0.0000 -7.0156 12.4922 +v 0.0000 -7.5000 12.0000 +v -5.1387 -4.9700 12.0000 +v -5.0022 -4.9811 12.4922 +v -5.3250 -5.3250 12.0000 +v -7.0000 0.0000 12.0000 +v -7.0156 0.0000 12.4922 +v -7.5000 0.0000 12.0000 +v -4.9700 4.9700 12.0000 +v -4.9811 4.9811 12.4922 +v -5.3250 5.3250 12.0000 +v 0.0000 7.0000 12.0000 +v 0.0000 7.0156 12.4922 +v 0.0000 7.5000 12.0000 +v 4.9700 4.9700 12.0000 +v 4.9811 4.9811 12.4922 +v 5.3250 5.3250 12.0000 +v 6.5453 -6.5453 8.1094 +v 9.2188 0.0000 8.1094 +v 7.1000 -7.1000 4.5000 +v 10.0000 0.0000 4.5000 +v 0.0000 -9.2188 8.1094 +v 0.0000 -10.0000 4.5000 +v -6.5453 -6.5453 8.1094 +v -7.1000 -7.1000 4.5000 +v -9.2188 0.0000 8.1094 +v -10.0000 0.0000 4.5000 +v -6.5453 6.5453 8.1094 +v -7.1000 7.1000 4.5000 +v 0.0000 9.2188 8.1094 +v 0.0000 10.0000 4.5000 +v 6.5453 6.5453 8.1094 +v 7.1000 7.1000 4.5000 +v 6.2125 -6.2125 1.9219 +v 8.7500 0.0000 1.9219 +v 5.3250 -5.3250 0.7500 +v 7.5000 0.0000 0.7500 +v 0.0000 -8.7500 1.9219 +v 0.0000 -7.5000 0.7500 +v -6.2125 -6.2125 1.9219 +v -5.3250 -5.3250 0.7500 +v -8.7500 0.0000 1.9219 +v -7.5000 0.0000 0.7500 +v -6.2125 6.2125 1.9219 +v -5.3250 5.3250 0.7500 +v 0.0000 8.7500 1.9219 +v 0.0000 7.5000 0.7500 +v 6.2125 6.2125 1.9219 +v 5.3250 5.3250 0.7500 +v 4.5595 -4.5595 0.2344 +v 6.4219 0.0000 0.2344 +v 0.0000 0.0000 0.0000 +v 0.0000 -6.4219 0.2344 +v -4.5595 -4.5595 0.2344 +v -6.4219 0.0000 0.2344 +v -4.5595 4.5595 0.2344 +v 0.0000 6.4219 0.2344 +v 4.5595 4.5595 0.2344 +v -8.0000 0.0000 10.1250 +v -7.7500 -1.1250 10.6875 +v -12.5938 -1.1250 10.4766 +v -12.0625 0.0000 9.9844 +v -14.2500 -1.1250 9.0000 +v -13.5000 0.0000 9.0000 +v -7.5000 0.0000 11.2500 +v -13.1250 0.0000 10.9688 +v -15.0000 0.0000 9.0000 +v -7.7500 1.1250 10.6875 +v -12.5938 1.1250 10.4766 +v -14.2500 1.1250 9.0000 +v -13.1719 -1.1250 6.2695 +v -12.6875 0.0000 6.7500 +v -9.7500 -1.1250 3.7500 +v -13.6563 0.0000 5.7891 +v -9.5000 0.0000 3.0000 +v -13.1719 1.1250 6.2695 +v -9.7500 1.1250 3.7500 +v 8.5000 0.0000 7.1250 +v 8.5000 -2.4750 5.0625 +v 12.6875 -1.7062 8.1094 +v 11.9375 0.0000 9.0000 +v 15.0000 -0.9375 12.0000 +v 13.5000 0.0000 12.0000 +v 8.5000 0.0000 3.0000 +v 13.4375 0.0000 7.2187 +v 16.5000 0.0000 12.0000 +v 8.5000 2.4750 5.0625 +v 12.6875 1.7062 8.1094 +v 15.0000 0.9375 12.0000 +v 15.6328 -0.7500 12.3340 +v 14.1250 0.0000 12.2813 +v 15.0000 -0.5625 12.0000 +v 14.0000 0.0000 12.0000 +v 17.1406 0.0000 12.3867 +v 16.0000 0.0000 12.0000 +v 15.6328 0.7500 12.3340 +v 15.0000 0.5625 12.0000 +v 1.1552 -1.1552 14.9063 +v 1.6250 0.0000 14.9063 +v 0.0000 0.0000 15.7500 +v 0.7100 -0.7100 13.5000 +v 1.0000 0.0000 13.5000 +v 0.0000 -1.6250 14.9063 +v 0.0000 -1.0000 13.5000 +v -1.1552 -1.1552 14.9063 +v -0.7100 -0.7100 13.5000 +v -1.6250 0.0000 14.9063 +v -1.0000 0.0000 13.5000 +v -1.1552 1.1552 14.9063 +v -0.7100 0.7100 13.5000 +v 0.0000 1.6250 14.9063 +v 0.0000 1.0000 13.5000 +v 1.1552 1.1552 14.9063 +v 0.7100 0.7100 13.5000 +v 2.9288 -2.9288 12.7500 +v 4.1250 0.0000 12.7500 +v 4.6150 -4.6150 12.0000 +v 6.5000 0.0000 12.0000 +v 0.0000 -4.1250 12.7500 +v 0.0000 -6.5000 12.0000 +v -2.9288 -2.9288 12.7500 +v -4.6150 -4.6150 12.0000 +v -4.1250 0.0000 12.7500 +v -6.5000 0.0000 12.0000 +v -2.9288 2.9288 12.7500 +v -4.6150 4.6150 12.0000 +v 0.0000 4.1250 12.7500 +v 0.0000 6.5000 12.0000 +v 2.9288 2.9288 12.7500 +v 4.6150 4.6150 12.0000 +# 137 vertices + +g Teapot001 +f 1 2 3 4 +f 4 3 5 6 +f 2 7 8 3 +f 3 8 9 5 +f 7 10 11 8 +f 8 11 12 9 +f 10 13 14 11 +f 11 14 15 12 +f 13 16 17 14 +f 14 17 18 15 +f 16 19 20 17 +f 17 20 21 18 +f 19 22 23 20 +f 20 23 24 21 +f 22 1 4 23 +f 23 4 6 24 +f 6 5 25 26 +f 26 25 27 28 +f 5 9 29 25 +f 25 29 30 27 +f 9 12 31 29 +f 29 31 32 30 +f 12 15 33 31 +f 31 33 34 32 +f 15 18 35 33 +f 33 35 36 34 +f 18 21 37 35 +f 35 37 38 36 +f 21 24 39 37 +f 37 39 40 38 +f 24 6 26 39 +f 39 26 28 40 +f 28 27 41 42 +f 42 41 43 44 +f 27 30 45 41 +f 41 45 46 43 +f 30 32 47 45 +f 45 47 48 46 +f 32 34 49 47 +f 47 49 50 48 +f 34 36 51 49 +f 49 51 52 50 +f 36 38 53 51 +f 51 53 54 52 +f 38 40 55 53 +f 53 55 56 54 +f 40 28 42 55 +f 55 42 44 56 +f 44 43 57 58 +f 58 57 59 +f 43 46 60 57 +f 57 60 59 +f 46 48 61 60 +f 60 61 59 +f 48 50 62 61 +f 61 62 59 +f 50 52 63 62 +f 62 63 59 +f 52 54 64 63 +f 63 64 59 +f 54 56 65 64 +f 64 65 59 +f 56 44 58 65 +f 65 58 59 +f 66 67 68 69 +f 69 68 70 71 +f 67 72 73 68 +f 68 73 74 70 +f 72 75 76 73 +f 73 76 77 74 +f 75 66 69 76 +f 76 69 71 77 +f 71 70 78 79 +f 79 78 80 34 +f 70 74 81 78 +f 78 81 82 80 +f 74 77 83 81 +f 81 83 84 82 +f 77 71 79 83 +f 83 79 34 84 +f 85 86 87 88 +f 88 87 89 90 +f 86 91 92 87 +f 87 92 93 89 +f 91 94 95 92 +f 92 95 96 93 +f 94 85 88 95 +f 95 88 90 96 +f 90 89 97 98 +f 98 97 99 100 +f 89 93 101 97 +f 97 101 102 99 +f 93 96 103 101 +f 101 103 104 102 +f 96 90 98 103 +f 103 98 100 104 +f 105 106 107 +f 106 105 108 109 +f 110 105 107 +f 105 110 111 108 +f 112 110 107 +f 110 112 113 111 +f 114 112 107 +f 112 114 115 113 +f 116 114 107 +f 114 116 117 115 +f 118 116 107 +f 116 118 119 117 +f 120 118 107 +f 118 120 121 119 +f 106 120 107 +f 120 106 109 121 +f 109 108 122 123 +f 123 122 124 125 +f 108 111 126 122 +f 122 126 127 124 +f 111 113 128 126 +f 126 128 129 127 +f 113 115 130 128 +f 128 130 131 129 +f 115 117 132 130 +f 130 132 133 131 +f 117 119 134 132 +f 132 134 135 133 +f 119 121 136 134 +f 134 136 137 135 +f 121 109 123 136 +f 136 123 125 137 +# 112 polygons - 16 triangles + diff --git a/output/ch15_teapot_objfile.png b/output/ch15_teapot_objfile.png new file mode 100644 index 0000000..57e5647 Binary files /dev/null and b/output/ch15_teapot_objfile.png differ diff --git a/source/include/cone.h b/source/include/cone.h index c004838..20aa223 100644 --- a/source/include/cone.h +++ b/source/include/cone.h @@ -19,7 +19,7 @@ class Cone : public Shape { protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); bool checkCap(Ray r, double t, double y); void intersectCaps(Ray r, Intersect &xs); diff --git a/source/include/cube.h b/source/include/cube.h index b3f85da..31ba770 100644 --- a/source/include/cube.h +++ b/source/include/cube.h @@ -16,12 +16,12 @@ #include class Cube : public Shape { -private: +protected: void checkAxis(double axeOrigin, double axeDirection, double *axeMin, double *axeMax); Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: Cube() : Shape(SHAPE_CUBE) { stats.addCube(); }; diff --git a/source/include/cylinder.h b/source/include/cylinder.h index 33576aa..c8455d3 100644 --- a/source/include/cylinder.h +++ b/source/include/cylinder.h @@ -16,10 +16,11 @@ #include class Cylinder : public Shape { -private: + +protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); bool checkCap(Ray r, double t); void intersectCaps(Ray r, Intersect &xs); diff --git a/source/include/group.h b/source/include/group.h index fefacad..a389cf0 100644 --- a/source/include/group.h +++ b/source/include/group.h @@ -27,7 +27,7 @@ private: protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); BoundingBox bounds; diff --git a/source/include/intersection.h b/source/include/intersection.h index 3786e71..d691799 100644 --- a/source/include/intersection.h +++ b/source/include/intersection.h @@ -74,8 +74,10 @@ public: double t; Shape *object; + double u, v; + public: - Intersection(double t, Shape *object) : t(t), object(object) { stats.addIntersection(); }; + Intersection(double t, Shape *object, double u = NAN, double v = NAN) : t(t), object(object), u(u), v(v) { stats.addIntersection(); }; bool nothing() { return (this->object == nullptr); }; Computation prepareComputation(Ray r, Intersect *xs = nullptr); diff --git a/source/include/objfile.h b/source/include/objfile.h index 47a4bcd..1920ae7 100644 --- a/source/include/objfile.h +++ b/source/include/objfile.h @@ -26,9 +26,13 @@ private: Point* *vertexList; uint32_t vertexCount; + uint32_t allocatedVertexNormalCount; + Vector* *vertexNormalList; + uint32_t vertexNormalCount; + private: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: /* Some stats */ @@ -37,6 +41,7 @@ public: protected: void addGroup(Group *group); void addVertex(Point *vertex); + void addVertexNormal(Vector *vertexNormal); void parseLine(char *line, uint32_t currentLine); int execLine(int argc, char *argv[], uint32_t currentLine); @@ -50,6 +55,7 @@ public: /* OBJ file expect the first vertice to be 1 and not 0 */ Point vertices(uint32_t i) { return *this->vertexList[i - 1]; }; + Vector verticesNormal(uint32_t i) { return *this->vertexNormalList[i - 1]; }; Group *groups(uint32_t i) { return this->faceGroupList[i]; }; Intersect intersect(Ray r); BoundingBox getLocalBounds(); diff --git a/source/include/plane.h b/source/include/plane.h index 638b2da..278aa92 100644 --- a/source/include/plane.h +++ b/source/include/plane.h @@ -13,9 +13,9 @@ class Plane : public Shape { -private: +protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: Plane() : Shape(SHAPE_PLANE) { stats.addPlane(); }; diff --git a/source/include/renderstat.h b/source/include/renderstat.h index 2611f89..b485541 100644 --- a/source/include/renderstat.h +++ b/source/include/renderstat.h @@ -14,15 +14,16 @@ class RenderStats { private: - uint64_t coneCount; /* Total number of cones */ - uint64_t cylinderCount; /* Total number of cylinder */ - uint64_t cubeCount; /* Total number of cubes */ - uint64_t groupCount; /* Total number of groups */ - uint64_t lightCount; /* Total number of light */ - uint64_t planeCount; /* Total number of plane */ - uint64_t sphereCount; /* Total number of sphere */ - uint64_t triangleCount; /* Total number of triangle */ - uint64_t objfileCount; /* Total number of OBJ File */ + uint64_t coneCount; /* Total number of cones */ + uint64_t cylinderCount; /* Total number of cylinder */ + uint64_t cubeCount; /* Total number of cubes */ + uint64_t groupCount; /* Total number of groups */ + uint64_t lightCount; /* Total number of light */ + uint64_t planeCount; /* Total number of plane */ + uint64_t sphereCount; /* Total number of sphere */ + uint64_t triangleCount; /* Total number of triangle */ + uint64_t smoothTriangleCount; /* Total number of smooth triangle */ + uint64_t objfileCount; /* Total number of OBJ File */ uint64_t pixelCount; /* Total number of rendered pixels */ uint64_t rayCount; /* Total number of rays */ @@ -40,7 +41,7 @@ private: public: RenderStats() : coneCount(0), cylinderCount(0), cubeCount(0), groupCount(0), lightCount(0), planeCount(0), sphereCount(0), triangleCount(0), pixelCount(0), rayCount(0), lightRayEmitedCount(0), reflectionRayCount(0), refractedRayCount(0), - intersectCount(0), intersectionCount(0), reallocCallCount(0), mallocCallCount(0), + intersectCount(0), intersectionCount(0), reallocCallCount(0), mallocCallCount(0), smoothTriangleCount(0), discardedIntersectCount(0), maxDepthAttained(UINT64_MAX), maxIntersectOnARay(0), objfileCount(0) {}; #ifdef RENDER_STATS void addCone(); @@ -52,6 +53,7 @@ public: void addSphere(); void addOBJFile(); void addTriangle(); + void addSmoothTriangle(); void printStats(); void addPixel(); void addRay(); @@ -74,6 +76,7 @@ public: static void addPlane() {}; static void addSphere() {}; static void addTriangle() {}; + static void addSmoothTriangle() {}; static void printStats() {}; static void addPixel() {}; static void addRay() {}; diff --git a/source/include/shape.h b/source/include/shape.h index 5087d4d..072996d 100644 --- a/source/include/shape.h +++ b/source/include/shape.h @@ -30,17 +30,19 @@ enum ShapeType SHAPE_GROUP, SHAPE_TRIANGLE, SHAPE_OBJFILE, + SHAPE_SMOOTHTRIANGLE, }; /* Base class for all object that can be presented in the world */ class Shape { -private: +protected: ShapeType type; Matrix localTransformMatrix; + protected: virtual Intersect localIntersect(Ray r) = 0; - virtual Tuple localNormalAt(Tuple point) = 0; + virtual Tuple localNormalAt(Tuple point, Intersection *hit) = 0; public: Matrix transformMatrix; @@ -50,13 +52,14 @@ public: Material material; bool dropShadow; Shape *parent; + bool materialSet; public: Shape(ShapeType = SHAPE_NONE); virtual Intersect intersect(Ray r); virtual Intersect intersectOOB(Ray r) { return this->intersect(r); }; - Tuple normalAt(Tuple point); + Tuple normalAt(Tuple point, Intersection *hit = nullptr); /* Bounding box points are always world value */ virtual BoundingBox getLocalBounds(); diff --git a/source/include/smoothtriangle.h b/source/include/smoothtriangle.h new file mode 100644 index 0000000..3549702 --- /dev/null +++ b/source/include/smoothtriangle.h @@ -0,0 +1,28 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Smooth Triangle header + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#ifndef DORAYME_SMOOTHTRIANGLE_H +#define DORAYME_SMOOTHTRIANGLE_H + +#include + +class SmoothTriangle : public Triangle +{ +public: + Vector n1; + Vector n2; + Vector n3; + +protected: + Tuple localNormalAt(Tuple point, Intersection *hit); + +public: + SmoothTriangle(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3); +}; + +#endif /* DORAYME_SMOOTHTRIANGLE_H */ diff --git a/source/include/sphere.h b/source/include/sphere.h index cf69822..da00c9e 100644 --- a/source/include/sphere.h +++ b/source/include/sphere.h @@ -19,7 +19,7 @@ class Sphere : public Shape { protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: Sphere() : Shape(SHAPE_SPHERE) { stats.addSphere(); }; diff --git a/source/include/testshape.h b/source/include/testshape.h index b441f83..42598a9 100644 --- a/source/include/testshape.h +++ b/source/include/testshape.h @@ -17,7 +17,7 @@ class TestShape : public Shape { private: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: Ray localRay; diff --git a/source/include/triangle.h b/source/include/triangle.h index f813853..ff642b9 100644 --- a/source/include/triangle.h +++ b/source/include/triangle.h @@ -14,8 +14,9 @@ class Triangle : public Shape { +protected: Intersect localIntersect(Ray r); - Tuple localNormalAt(Tuple point); + Tuple localNormalAt(Tuple point, Intersection *hit = nullptr); public: Tuple p1, p2, p3; diff --git a/source/intersect.cpp b/source/intersect.cpp index 2fc6f2d..c90b011 100644 --- a/source/intersect.cpp +++ b/source/intersect.cpp @@ -60,7 +60,7 @@ void Intersect::add(Intersection i) this->list = (Intersection **)realloc(this->list, sizeof(Intersection *) * this->allocated); } - this->list[this->num++] = new Intersection(i.t, i.object); + this->list[this->num++] = new Intersection(i.t, i.object, i.u, i.v); stats.setMaxIntersect(this->num); diff --git a/source/intersection.cpp b/source/intersection.cpp index cd04c4c..20d0249 100644 --- a/source/intersection.cpp +++ b/source/intersection.cpp @@ -16,7 +16,18 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) double n2 = 1.0; Tuple hitP = r.position(this->t); - Tuple normalV = this->object->normalAt(hitP); + Tuple normalV; + + if (xs != nullptr) + { + Intersection hit = xs->hit(); + normalV = this->object->normalAt(hitP, &hit); + } + else + { + normalV = this->object->normalAt(hitP, nullptr); + } + Tuple eyeV = -r.direction; bool inside = false; @@ -70,8 +81,9 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs) } Shape *s = this->object; + /* For now don't get root group material */ - //while(s->parent != nullptr) { s = s->parent; } + while((!s->materialSet) && (s->parent != nullptr)) { s = s->parent; } return Computation(this->object, this->t, diff --git a/source/renderstat.cpp b/source/renderstat.cpp index 0ecb8f3..1c1113f 100644 --- a/source/renderstat.cpp +++ b/source/renderstat.cpp @@ -61,6 +61,12 @@ void RenderStats::addTriangle() this->triangleCount++; }; +void RenderStats::addSmoothTriangle() +{ +#pragma omp atomic + this->smoothTriangleCount++; +}; + void RenderStats::addOBJFile() { #pragma omp atomic @@ -154,6 +160,8 @@ void RenderStats::printStats() printf("Planes : %lld\n", this->planeCount); printf("Spheres : %lld\n", this->sphereCount); printf("Triangles : %lld\n", this->triangleCount); + printf("Smooth Triangles : %lld\n", this->smoothTriangleCount); + printf("OBJ File : %lld\n", this->objfileCount); printf("==================================================\n"); printf("Pixel rendered : %lld\n", this->pixelCount); printf("Ray casted : %lld\n", this->rayCount); diff --git a/source/shapes/cone.cpp b/source/shapes/cone.cpp index 7172584..3f755c6 100644 --- a/source/shapes/cone.cpp +++ b/source/shapes/cone.cpp @@ -99,7 +99,7 @@ Intersect Cone::localIntersect(Ray r) return ret; } -Tuple Cone::localNormalAt(Tuple point) +Tuple Cone::localNormalAt(Tuple point, Intersection *hit) { /* Compute the square of the distance from the Y axis */ double dist = point.x * point.x + point.z * point.z; diff --git a/source/shapes/cube.cpp b/source/shapes/cube.cpp index 6ee6fa7..a1847e8 100644 --- a/source/shapes/cube.cpp +++ b/source/shapes/cube.cpp @@ -59,7 +59,7 @@ Intersect Cube::localIntersect(Ray r) return ret; } -Tuple Cube::localNormalAt(Tuple point) +Tuple Cube::localNormalAt(Tuple point, Intersection *hit) { double maxC = max3(fabs(point.x), fabs(point.y), fabs(point.z)); diff --git a/source/shapes/cylinder.cpp b/source/shapes/cylinder.cpp index 07f3509..e41f879 100644 --- a/source/shapes/cylinder.cpp +++ b/source/shapes/cylinder.cpp @@ -90,7 +90,7 @@ Intersect Cylinder::localIntersect(Ray r) return ret; } -Tuple Cylinder::localNormalAt(Tuple point) +Tuple Cylinder::localNormalAt(Tuple point, Intersection *hit) { /* Compute the square of the distance from the Y axis */ double dist = point.x * point.x + point.z * point.z; diff --git a/source/shapes/group.cpp b/source/shapes/group.cpp index 32e0a92..7ce62ff 100644 --- a/source/shapes/group.cpp +++ b/source/shapes/group.cpp @@ -72,7 +72,7 @@ Intersect Group::localIntersect(Ray r) return Intersect(); } -Tuple Group::localNormalAt(Tuple point) +Tuple Group::localNormalAt(Tuple point, Intersection *hit) { return Vector(1, 0, 0); } diff --git a/source/shapes/objfile.cpp b/source/shapes/objfile.cpp index 7db6aa5..50896c4 100644 --- a/source/shapes/objfile.cpp +++ b/source/shapes/objfile.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #define MIN_ALLOC (2) #define DEFAULT_GROUP (0) @@ -33,6 +34,11 @@ OBJFile::OBJFile() : Shape(SHAPE_OBJFILE), ignoredLines(0) this->vertexList = (Point **)calloc(sizeof(Point **), MIN_ALLOC); this->vertexCount = 0; + + this->allocatedVertexNormalCount = MIN_ALLOC; + this->vertexNormalList = (Vector **)calloc(sizeof(Vector **), MIN_ALLOC); + this->vertexNormalCount = 0; + /* There is always a default group */ this->addGroup(new Group()); }; @@ -89,13 +95,24 @@ void OBJFile::addVertex(Point *vertex) this->vertexList[this->vertexCount++] = vertex; } +void OBJFile::addVertexNormal(Vector *vertexNormal) +{ + if ((this->vertexNormalCount + 1) > this->allocatedVertexNormalCount) + { + this->allocatedVertexNormalCount *= 2; + this->vertexNormalList = (Vector **)realloc(this->vertexNormalList, sizeof(Vector **) * this->allocatedVertexNormalCount); + } + + this->vertexNormalList[this->vertexNormalCount++] = vertexNormal; +} + Intersect OBJFile::intersect(Ray r) { Intersect ret; int i, j; if (this->faceGroupCount > 0) { - //if (this->bounds.intesectMe(r)) + if (this->bounds.intesectMe(r)) { for (i = 0 ; i < this->faceGroupCount ; i++) { @@ -118,7 +135,7 @@ Intersect OBJFile::localIntersect(Ray r) return Intersect(); } -Tuple OBJFile::localNormalAt(Tuple point) +Tuple OBJFile::localNormalAt(Tuple point, Intersection *hit) { return Vector(0, 1, 0); } @@ -262,11 +279,51 @@ void OBJFile::parseLine(char *line, uint32_t currentLine) } } +static int parseFaceVertex(char *buf, uint32_t &v, uint32_t &vt, uint32_t &vn) +{ + uint32_t bufPos = 0; + uint32_t lineLength = strlen(buf); + char *tmp = buf; + vt = INT32_MAX; + vn = INT32_MAX; + int ret = 0; + int token = 0; + + while(bufPos < lineLength) + { + char *next = strchr(buf, '/'); + if (next != nullptr) + { + *next = '\0'; + bufPos = next - buf; + } + else + { + bufPos = lineLength; + } + + if (strlen(buf) > 0) + { + switch(token) + { + case 0: v = atol(buf); break; + case 1: vt = atol(buf); break; + case 2: vn = atol(buf); break; + default: printf("ERROR: Too many entry for a face vertice!"); ret = 1; + } + } + buf = next + 1; + token++; + } + + return ret; +} + /* Actually execute the line */ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine) { int ret = 1; - if (strncmp(argv[0], "v", 1) == 0) + if (strncmp(argv[0], "v", 2) == 0) { /* Vertice entry */ if (argc != 4) @@ -279,25 +336,71 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine) ret = 0; } } - else if (strncmp(argv[0], "f", 1) == 0) + else if (strncmp(argv[0], "vn", 3) == 0) { - /* Vertice entry */ + /* Vertice Normal entry */ + if (argc != 4) + { + printf("ERROR: Malformed file at line %d: Vertices normal expect 3 parameters!\n", currentLine); + } + else + { + this->addVertexNormal(new Vector(atof(argv[1]), atof(argv[2]), atof(argv[3]))); + ret = 0; + } + } + else if (strncmp(argv[0], "f", 2) == 0) + { + /* Faces entry */ + int i; + uint32_t v[MAX_ARGS], vt[MAX_ARGS], vn[MAX_ARGS]; + for(i = 1; i < argc; i++) + { + parseFaceVertex(argv[i], v[i], vt[i], vn[i]); + } + if (argc == 4) { - Shape *t = new Triangle(this->vertices(atoi(argv[1])), - this->vertices(atoi(argv[2])), - this->vertices(atoi(argv[3]))); + Shape *t; + if (vn[1] == INT32_MAX) + { + t = new Triangle(this->vertices(v[1]), + this->vertices(v[2]), + this->vertices(v[3])); + } + else + { + t = new SmoothTriangle(this->vertices(v[1]), + this->vertices(v[2]), + this->vertices(v[3]), + this->verticesNormal(vn[1]), + this->verticesNormal(vn[2]), + this->verticesNormal(vn[3])); + } this->faceGroupList[this->faceGroupCount - 1]->addObject(t); ret = 0; } else if (argc > 4) { - int i; for(i = 2; i < (argc - 1); i++) { - Shape *t = new Triangle(this->vertices(atoi(argv[1])), - this->vertices(atoi(argv[i])), - this->vertices(atoi(argv[i+1]))); + + Shape *t; + if (vn[1] == INT32_MAX) + { + t = new Triangle(this->vertices(v[1]), + this->vertices(v[i]), + this->vertices(v[i + 1])); + } + else + { + t = new SmoothTriangle(this->vertices(v[1]), + this->vertices(v[i]), + this->vertices(v[i + 1]), + this->verticesNormal(vn[1]), + this->verticesNormal(vn[i]), + this->verticesNormal(vn[i + 1])); + } this->faceGroupList[this->faceGroupCount - 1]->addObject(t); } ret = 0; @@ -307,7 +410,7 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine) printf("ERROR: Malformed file at line %d: Too few/many parameters!\n", currentLine); } } - else if (strncmp(argv[0], "g", 1) == 0) + else if (strncmp(argv[0], "g", 2) == 0) { if (argc == 2) { diff --git a/source/shapes/plane.cpp b/source/shapes/plane.cpp index 74f37ca..bf65cf2 100644 --- a/source/shapes/plane.cpp +++ b/source/shapes/plane.cpp @@ -30,7 +30,7 @@ Intersect Plane::localIntersect(Ray r) return ret; } -Tuple Plane::localNormalAt(Tuple point) +Tuple Plane::localNormalAt(Tuple point, Intersection *hit) { return Vector(0, 1, 0); } diff --git a/source/shapes/shape.cpp b/source/shapes/shape.cpp index 5259b27..04b7be7 100644 --- a/source/shapes/shape.cpp +++ b/source/shapes/shape.cpp @@ -20,6 +20,7 @@ Shape::Shape(ShapeType type) this->type = type; this->localTransformMatrix = Matrix4().identity(); this->updateTransform(); + this->materialSet = false; } Intersect Shape::intersect(Ray r) @@ -37,11 +38,11 @@ Tuple Shape::normalToWorld(Tuple normalVector) return world_normal.normalise(); }; -Tuple Shape::normalAt(Tuple point) +Tuple Shape::normalAt(Tuple point, Intersection *hit) { Tuple local_point = this->worldToObject(point); - Tuple local_normal = this->localNormalAt(local_point); + Tuple local_normal = this->localNormalAt(local_point, hit); Tuple world_normal = this->normalToWorld(local_normal); diff --git a/source/shapes/smoothtriangle.cpp b/source/shapes/smoothtriangle.cpp new file mode 100644 index 0000000..9991d80 --- /dev/null +++ b/source/shapes/smoothtriangle.cpp @@ -0,0 +1,28 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Smooth Triangle implementation + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include +#include +#include +#include + +SmoothTriangle::SmoothTriangle(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3) : Triangle(p1, p2, p3), + n1(n1), n2(n2), n3(n3) +{ + this->type = SHAPE_SMOOTHTRIANGLE; + stats.addSmoothTriangle(); +} + +Tuple SmoothTriangle::localNormalAt(Tuple point, Intersection *hit) +{ + return (this->n2 * hit->u + + this->n3 * hit->v + + this->n1 * (1 - hit->u - hit->v)).normalise(); +} \ No newline at end of file diff --git a/source/shapes/sphere.cpp b/source/shapes/sphere.cpp index b294fc6..e141d2a 100644 --- a/source/shapes/sphere.cpp +++ b/source/shapes/sphere.cpp @@ -35,7 +35,7 @@ Intersect Sphere::localIntersect(Ray r) return ret; } -Tuple Sphere::localNormalAt(Tuple point) +Tuple Sphere::localNormalAt(Tuple point, Intersection *hit) { return (point - Point(0, 0, 0)).normalise(); } diff --git a/source/shapes/testshape.cpp b/source/shapes/testshape.cpp index 6334818..23210d2 100644 --- a/source/shapes/testshape.cpp +++ b/source/shapes/testshape.cpp @@ -19,7 +19,7 @@ Intersect TestShape::localIntersect(Ray r) return Intersect(); } -Tuple TestShape::localNormalAt(Tuple point) +Tuple TestShape::localNormalAt(Tuple point, Intersection *hit) { return Vector(point.x, point.y, point.z); } \ No newline at end of file diff --git a/source/shapes/triangle.cpp b/source/shapes/triangle.cpp index 0e31514..01f7a1d 100644 --- a/source/shapes/triangle.cpp +++ b/source/shapes/triangle.cpp @@ -1,6 +1,6 @@ /* * DoRayMe - a quick and dirty Raytracer - * Cone implementation + * Triangle implementation * * Created by Manoël Trapier * Copyright (c) 2020 986-Studio. @@ -49,12 +49,12 @@ Intersect Triangle::localIntersect(Ray r) } double t = f * this->e2.dot(originCrossE1); - ret.add(Intersection(t, this)); + ret.add(Intersection(t, this, u, v)); return ret; } -Tuple Triangle::localNormalAt(Tuple point) +Tuple Triangle::localNormalAt(Tuple point, Intersection *hit) { return this->normal; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index da12918..a1cb74d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ link_libraries(rayonnement) 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 group_test.cpp - boundingbox_test.cpp triangle_test.cpp sequence_test.cpp objfile_test.cpp) + boundingbox_test.cpp triangle_test.cpp sequence_test.cpp objfile_test.cpp smoothtriangle_test.cpp) add_executable(testMyRays) target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) @@ -147,6 +147,7 @@ add_test(NAME Chapter12_Test COMMAND $) add_test(NAME Chapter13_Test COMMAND $) add_test(NAME Chapter13_ConeBonus COMMAND $) add_test(NAME Chapter14_Test COMMAND $) +add_test(NAME Chapter15_Teapots COMMAND $) add_test(NAME AreaLight_Test COMMAND $) add_test(NAME UVMap_CheckeredSphere COMMAND $) add_test(NAME UVMap_CheckeredPlane COMMAND $) diff --git a/tests/arealight_test.cpp b/tests/arealight_test.cpp index b692558..e534a96 100644 --- a/tests/arealight_test.cpp +++ b/tests/arealight_test.cpp @@ -80,7 +80,7 @@ int main() /* ----------------------------- */ /* Set the camera */ - Camera camera = Camera(400, 160, 0.7854); + Camera camera = Camera(40, 16, 0.7854); camera.setTransform(viewTransform(Point(-3, 1, 2.5), Point(0, 0.5, 0), Vector(0, 1, 0))); diff --git a/tests/ch11_test.cpp b/tests/ch11_test.cpp index d40642c..3900cf7 100644 --- a/tests/ch11_test.cpp +++ b/tests/ch11_test.cpp @@ -104,7 +104,7 @@ int main() bg4.material.shininess = 50; w.addObject(&bg4); - /* Forground balls */ + /* Foreground balls */ /* Red sphere */ Sphere redBall = Sphere(); diff --git a/tests/ch15_teapot_objfile.cpp b/tests/ch15_teapot_objfile.cpp index cd6289c..59ebb7e 100644 --- a/tests/ch15_teapot_objfile.cpp +++ b/tests/ch15_teapot_objfile.cpp @@ -28,9 +28,9 @@ int main() World w = World(); /* Add lights */ - Light light1 = Light(POINT_LIGHT, Point(0, 20, 2), Colour(1, 1, 1)); + Light light1 = Light(POINT_LIGHT, Point(50, 100, 20), Colour(.5, .5, .5)); w.addLight(&light1); - Light light2 = Light(POINT_LIGHT, Point(0, 2, 20), Colour(1, 1, 1)); + Light light2 = Light(POINT_LIGHT, Point(2, 50, 100), Colour(.5, .5, .5)); w.addLight(&light2); /* ----------------------------- */ @@ -42,6 +42,7 @@ int main() p.material.ambient = 1; p.material.diffuse = 0; p.material.specular = 0; + p.material.reflective = 0.1; w.addObject(&p); Plane p2 = Plane(); @@ -53,26 +54,31 @@ int main() w.addObject(&p2); OBJFile teapot = OBJFile("teapot-low.obj"); - teapot.setTransform(rotationY(M_PI) * rotationX(-M_PI/2) * scaling(0.4, 0.4, 0.4)); - teapot.material.colour = Colour(1, 0.2, 0.1); - teapot.material.ambient = 0.2; - teapot.material.specular = 0.2; - teapot.material.diffuse = 20; + teapot.setTransform(translation(7, 0, 3) * rotationY(M_PI*23/22) * rotationX(-M_PI/2) * scaling(0.3, 0.3, 0.3)); + teapot.material.colour = Colour(1, 0.3, 0.2); + teapot.material.shininess = 5; + teapot.material.specular = 0.4; w.addObject(&teapot); - /* ----------------------------- */ + OBJFile teapot2 = OBJFile("teapot-lowtri.obj"); + teapot2.setTransform(translation(-7, 0, 3) * rotationY(-M_PI*46/22) * rotationX(-M_PI/2) * scaling(0.3, 0.3, 0.3)); + teapot2.material.colour = Colour(1, 0.3, 0.2); + teapot2.material.shininess = 5; + teapot2.material.specular = 0.4; + w.addObject(&teapot2); - FILE *fpOut = fopen("teapot_worlddump.json", "wt"); - if (fpOut) - { - w.dumpMe(fpOut); - fclose(fpOut); - } + OBJFile teapot3= OBJFile("teapot.obj"); + teapot3.setTransform(translation(0, 0, -5) * rotationY(-M_PI) * rotationX(-M_PI/2) * scaling(0.4, 0.4, 0.4)); + teapot3.material.colour = Colour(0.3, 1, 0.2); + teapot3.material.shininess = 5; + teapot3.material.specular = 0.4; + teapot3.material.reflective = 0.5; + w.addObject(&teapot3); /* ----------------------------- */ /* Set the camera */ - Camera camera = Camera(800, 400, M_PI/2); + Camera camera = Camera(80, 40, M_PI/2); camera.setTransform(viewTransform(Point(0, 7, 13), Point(0, 1, 0), Vector(0, 1, 0))); diff --git a/tests/intersect_test.cpp b/tests/intersect_test.cpp index 0998b36..989bb4e 100644 --- a/tests/intersect_test.cpp +++ b/tests/intersect_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -302,4 +303,13 @@ TEST(IntersectTest, The_Schlick_approximation_with_small_angle_and_n2_gt_n1) ASSERT_TRUE(double_equal(reflectance, 0.48873)); set_equal_precision(FLT_EPSILON); +} + +TEST(IntersectTest, An_intersection_can_encapsulage_u_and_v) +{ + Triangle s = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0)); + Intersection i = Intersection(3.5, &s, 0.2, 0.4); + + ASSERT_EQ(i.u, 0.2); + ASSERT_EQ(i.v, 0.4); } \ No newline at end of file diff --git a/tests/objfile_test.cpp b/tests/objfile_test.cpp index 31a0cd7..49e2eef 100644 --- a/tests/objfile_test.cpp +++ b/tests/objfile_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include TEST(OBJFileTest, Ignoring_unrecognised_lines) { @@ -118,4 +119,58 @@ TEST(OBJFileTest, Triangle_in_groups) ASSERT_EQ(t2->p1, parser.vertices(1)); ASSERT_EQ(t2->p2, parser.vertices(3)); ASSERT_EQ(t2->p3, parser.vertices(4)); +} + +TEST(OBJFileTest, Vertex_normal_record) +{ + const char file[] = "vn 0 0 1\n" + "vn 0.707 0 -0.707\n" + "vn 1 2 3\n"; + + OBJFile parser = OBJFile(); + parser.parseOBJFile(file); + + ASSERT_EQ(parser.verticesNormal(1), Vector(0, 0, 1)); + ASSERT_EQ(parser.verticesNormal(2), Vector(0.707, 0, -0.707)); + ASSERT_EQ(parser.verticesNormal(3), Vector(1, 2, 3)); +} + +TEST(OBJFileTest, Faces_with_normal) +{ + const char file[] = "v 0 1 0\n" + "v -1 0 0\n" + "v 1 0 0\n" + "\n" + "vn -1 0 0\n" + "vn 1 0 0\n" + "vn 0 1 0\n" + "\n" + "f 1//3 2//1 3//2\n" + "f 1/0/3 2/102/1 3/14/2\n"; + + OBJFile parser = OBJFile(); + parser.parseOBJFile(file); + + Group *g0 = parser.groups(0); + + SmoothTriangle *t1 = (SmoothTriangle *)(*g0)[0]; + SmoothTriangle *t2 = (SmoothTriangle *)(*g0)[1]; + + ASSERT_EQ(t1->p1, parser.vertices(1)); + ASSERT_EQ(t1->p2, parser.vertices(2)); + ASSERT_EQ(t1->p3, parser.vertices(3)); + + ASSERT_EQ(t1->n1, parser.verticesNormal(3)); + ASSERT_EQ(t1->n2, parser.verticesNormal(1)); + ASSERT_EQ(t1->n3, parser.verticesNormal(2)); + + ASSERT_EQ(t2->p1, parser.vertices(1)); + ASSERT_EQ(t2->p2, parser.vertices(2)); + ASSERT_EQ(t2->p3, parser.vertices(3)); + + ASSERT_EQ(t2->n1, parser.verticesNormal(3)); + ASSERT_EQ(t2->n2, parser.verticesNormal(1)); + ASSERT_EQ(t2->n3, parser.verticesNormal(2)); + + } \ No newline at end of file diff --git a/tests/smoothtriangle_test.cpp b/tests/smoothtriangle_test.cpp new file mode 100644 index 0000000..f81ab15 --- /dev/null +++ b/tests/smoothtriangle_test.cpp @@ -0,0 +1,83 @@ +/* + * DoRayMe - a quick and dirty Raytracer + * Smooth Triangle unit tests + * + * Created by Manoël Trapier + * Copyright (c) 2020 986-Studio. + * + */ +#include +#include +#include + +class SmoothTriTest : public SmoothTriangle +{ +public: + SmoothTriTest(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3) : SmoothTriangle(p1, p2, p3, n1, n2, n3) {}; + Intersect doLocalIntersect(Ray ray) + { + return this->localIntersect(ray); + }; + + Tuple doLocalNormalAt(Tuple point, Intersection *hit) + { + return this->localNormalAt(point, hit); + }; +}; + +Point p1 = Point(0, 1, 0); +Point p2 = Point(-1, 0, 0); +Point p3 = Point(1, 0, 0); +Vector n1 = Vector(0, 1, 0); +Vector n2 = Vector(-1, 0, 0); +Vector n3 = Vector(1, 0, 0); + +SmoothTriTest tri = SmoothTriTest(p1, p2, p3, n1, n2, n3); + +TEST(SmoothTriangleTest, Construcring_a_smooth_triangle) +{ + ASSERT_EQ(tri.p1, p1); + ASSERT_EQ(tri.p2, p2); + ASSERT_EQ(tri.p3, p3); + ASSERT_EQ(tri.n1, n1); + ASSERT_EQ(tri.n2, n2); + ASSERT_EQ(tri.n3, n3); +} + +TEST(SmoothTriangleTest, An_intersection_with_a_smooth_triangle_stores_u_v) +{ + Ray r = Ray(Point(-0.2, 0.3, -2), Vector(0, 0, 1)); + + Intersect xs = tri.doLocalIntersect(r); + + ASSERT_TRUE(double_equal(xs[0].u, 0.45)); + ASSERT_TRUE(double_equal(xs[0].v, 0.25)); +} + +TEST(SmoothTriangleTest, A_smooth_triangle_uses_u_v_to_interpolate_the_normal) +{ + Intersection i = Intersection(1, &tri, 0.45, 0.25); + + Tuple n = tri.doLocalNormalAt(Point(0, 0, 0), &i); + + /* Temporary lower the precision */ + set_equal_precision(0.00001); + + ASSERT_EQ(n, Vector(-0.5547, 0.83205, 0)); + + set_equal_precision(FLT_EPSILON); +} + +TEST(SmoothTriangleTest, Preparing_a_normal_on_a_smooth_triangle) +{ + Intersection i = Intersection(1, &tri, 0.45, 0.25); + + Tuple n = tri.normalAt(Point(0, 0, 0), &i); + + /* Temporary lower the precision */ + set_equal_precision(0.00001); + + ASSERT_EQ(n, Vector(-0.5547, 0.83205, 0)); + + set_equal_precision(FLT_EPSILON); +} diff --git a/tests/test_render.cpp b/tests/test_render.cpp index 8e8fbd2..827a66b 100644 --- a/tests/test_render.cpp +++ b/tests/test_render.cpp @@ -119,7 +119,7 @@ int main() /* ----------------------------- */ /* Set the camera */ - Camera camera = Camera(1280, 900, deg_to_rad(90)); + Camera camera = Camera(40, 10, deg_to_rad(90)); camera.setTransform(viewTransform(Point(-2, 2.5, -3.5), Point(2, 0, 3), Vector(0, 1, 0)));