Browse Source

Smooth triangles! And support for them in the OBJ File parser.
Also add an interesting tea party scene!

Godzil 1 year ago
parent
commit
e57b5715e8

+ 7 - 2
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):**
 

+ 274 - 0
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
+

BIN
output/ch15_teapot_objfile.png


+ 1 - 1
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);

+ 2 - 2
source/include/cube.h

@@ -16,12 +16,12 @@
 #include <stdio.h>
 
 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(); };

+ 3 - 2
source/include/cylinder.h

@@ -16,10 +16,11 @@
 #include <stdio.h>
 
 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);

+ 1 - 1
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;
 

+ 3 - 1
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);

+ 7 - 1
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();

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

+ 13 - 10
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() {};

+ 6 - 3
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();

+ 28 - 0
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 <triangle.h>
+
+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 */

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

+ 1 - 1
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;

+ 2 - 1
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;

+ 1 - 1
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);
 

+ 14 - 2
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,

+ 8 - 0
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);

+ 1 - 1
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;

+ 1 - 1
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));
 

+ 1 - 1
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;

+ 1 - 1
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);
 }

+ 116 - 13
source/shapes/objfile.cpp

@@ -17,6 +17,7 @@
 #include <math_helper.h>
 #include <group.h>
 #include <triangle.h>
+#include <smoothtriangle.h>
 
 #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)
         {

+ 1 - 1
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);
 }

+ 3 - 2
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);
 

+ 28 - 0
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 <ray.h>
+#include <shape.h>
+#include <triangle.h>
+#include <smoothtriangle.h>
+#include <math_helper.h>
+#include <renderstat.h>
+
+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();
+}

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

+ 1 - 1
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);
 }

+ 3 - 3
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;
 }

+ 2 - 1
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 $<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 Chapter14_Test COMMAND $<TARGET_FILE:ch14_test>)
+add_test(NAME Chapter15_Teapots COMMAND $<TARGET_FILE:ch15_teapot_objfile>)
 add_test(NAME AreaLight_Test COMMAND $<TARGET_FILE:arealight_test>)
 add_test(NAME UVMap_CheckeredSphere COMMAND $<TARGET_FILE:uvmap_checkeredsphere>)
 add_test(NAME UVMap_CheckeredPlane COMMAND $<TARGET_FILE:uvmap_checkeredplane>)

+ 1 - 1
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)));

+ 1 - 1
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();

+ 21 - 15
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)));

+ 10 - 0
tests/intersect_test.cpp

@@ -10,6 +10,7 @@
 #include <intersection.h>
 #include <sphere.h>
 #include <plane.h>
+#include <triangle.h>
 #include <transformation.h>
 #include <gtest/gtest.h>
 
@@ -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);
 }

+ 55 - 0
tests/objfile_test.cpp

@@ -10,6 +10,7 @@
 #include <math.h>
 #include <gtest/gtest.h>
 #include <triangle.h>
+#include <smoothtriangle.h>
 
 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));
+
+
 }

+ 83 - 0
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 <smoothtriangle.h>
+#include <math.h>
+#include <gtest/gtest.h>
+
+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);
+}

+ 1 - 1
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)));