Browse Source

And CSG! \o/

Still working on a nice scene for it.
Godzil 4 years ago
parent
commit
b5ee92c544

+ 2 - 2
source/camera.cpp

@@ -62,9 +62,9 @@ Canvas Camera::render(World world, uint32_t depth)
     uint32_t x, y;
     Canvas image = Canvas(this->horizontalSize, this->verticalSize);
 
-#pragma omp parallel private(x, y) shared(image, stats)
+    #pragma omp parallel default(shared) private(x, y) shared(image, stats)
     {
-#pragma omp for
+        #pragma omp for schedule(dynamic, 5)
         for (y = 0 ; y < this->verticalSize ; y++)
         {
             for (x = 0 ; x < this->horizontalSize ; x++)

+ 52 - 0
source/include/csg.h

@@ -0,0 +1,52 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  CSG header
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#ifndef DORAYME_CSG_H
+#define DORAYME_CSG_H
+
+#include <shape.h>
+
+class CSG : public Shape
+{
+public:
+    enum OperationType
+    {
+        UNION,
+        DIFFERENCE,
+        INTERSECTION
+    };
+
+protected:
+    Shape *left;
+    Shape *right;
+    OperationType operation;
+    BoundingBox bounds;
+
+protected:
+    Intersect localIntersect(Ray r);
+    Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
+    BoundingBox getLocalBounds();
+
+    bool intersectionAllowed(bool leftHit, bool inLeft, bool inRight);
+
+    Intersect filterIntersections(Intersect &xs);
+
+    void updateBoundingBox();
+    BoundingBox getBounds();
+
+public:
+    CSG(OperationType operation, Shape *left, Shape *right);
+
+    bool includes(Shape *b);
+
+    void updateTransform();
+
+    void dumpMe(FILE *fp);
+};
+
+#endif /* DORAYME_CSG_H */

+ 2 - 0
source/include/group.h

@@ -44,6 +44,8 @@ public:
     void updateBoundingBox();
     void updateTransform();
 
+    bool includes(Shape *b);
+
     Group();
 
     void dumpMe(FILE * fp);

+ 2 - 0
source/include/objfile.h

@@ -61,6 +61,8 @@ public:
     BoundingBox getLocalBounds();
     BoundingBox getBounds();
 
+    bool includes(Shape *b);
+
     void updateBoundingBox();
     void updateTransform();
 

+ 5 - 2
source/include/renderstat.h

@@ -24,6 +24,7 @@ private:
     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 csgCount;                   /* Total number of CSG             */
 
     uint64_t pixelCount;                 /* Total number of rendered pixels */
     uint64_t rayCount;                   /* Total number of rays */
@@ -42,7 +43,7 @@ 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), smoothTriangleCount(0),
-                    discardedIntersectCount(0), maxDepthAttained(UINT64_MAX), maxIntersectOnARay(0), objfileCount(0) {};
+                    discardedIntersectCount(0), maxDepthAttained(UINT64_MAX), maxIntersectOnARay(0), objfileCount(0), csgCount(0) {};
 #ifdef RENDER_STATS
     void addCone();
     void addCylinder();
@@ -51,6 +52,7 @@ public:
     void addLight();
     void addPlane();
     void addSphere();
+    void addCsg();
     void addOBJFile();
     void addTriangle();
     void addSmoothTriangle();
@@ -90,7 +92,8 @@ public:
     static void addMalloc() {};
     static void addRealloc() {};
     static void setMaxIntersect(uint32_t count) {};
-    static void void RenderStats::addOBJFile() {};
+    static void addOBJFile() {};
+    static void addCsg() {};
 #endif
 };
 

+ 6 - 4
source/include/shape.h

@@ -31,6 +31,7 @@ enum ShapeType
     SHAPE_TRIANGLE,
     SHAPE_OBJFILE,
     SHAPE_SMOOTHTRIANGLE,
+    SHAPE_CSG,
 };
 
 /* Base class for all object that can be presented in the world */
@@ -58,7 +59,6 @@ public:
     Shape(ShapeType = SHAPE_NONE);
 
     virtual Intersect intersect(Ray r);
-    virtual Intersect intersectOOB(Ray r) { return this->intersect(r); };
     Tuple normalAt(Tuple point, Intersection *hit = nullptr);
 
     /* Bounding box points are always world value */
@@ -68,6 +68,8 @@ public:
 
     virtual void updateTransform();
 
+    virtual bool includes(Shape *b) { return this == b; };
+
     virtual void dumpMe(FILE *fp);
 
     Tuple worldToObject(Tuple point) { return this->inverseTransform * point; };
@@ -75,9 +77,9 @@ public:
     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); };
-    Ray invTransform(Ray r) {  return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); };
+    void setMaterial(Material material) { this->material = material; this->materialSet = true; };
+    Ray transform(Ray r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); };
+    Ray invTransform(Ray r) { return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); };
 
     bool operator==(const Shape &b) const { return this->material == b.material &&
                                                    this->type == b.type &&

+ 8 - 0
source/renderstat.cpp

@@ -133,6 +133,13 @@ void RenderStats::addDiscardedIntersect()
     this->discardedIntersectCount++;
 };
 
+void RenderStats::addCsg()
+{
+#pragma omp atomic
+    this->csgCount++;
+};
+
+
 void RenderStats::setMaxDepth(uint32_t depth)
 {
     if (this->maxDepthAttained > depth)
@@ -162,6 +169,7 @@ void RenderStats::printStats()
     printf("Triangles               : %lld\n", this->triangleCount);
     printf("Smooth Triangles        : %lld\n", this->smoothTriangleCount);
     printf("OBJ File                : %lld\n", this->objfileCount);
+    printf("CSG                     : %lld\n", this->csgCount);
     printf("==================================================\n");
     printf("Pixel rendered          : %lld\n", this->pixelCount);
     printf("Ray casted              : %lld\n", this->rayCount);

+ 133 - 0
source/shapes/csg.cpp

@@ -0,0 +1,133 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Constructive Solid Geometry (CSG) implementation
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#include <tuple.h>
+#include <ray.h>
+#include <shape.h>
+#include <csg.h>
+#include <math_helper.h>
+
+
+CSG::CSG(OperationType operation, Shape *left, Shape *right) : Shape(SHAPE_CSG), operation(operation), left(left), right(right)
+{
+    stats.addCsg();
+
+    this->left->parent = this;
+    this->right->parent = this;
+
+    this->bounds | this->left->getBounds();
+    this->bounds | this->right->getBounds();
+}
+
+Intersect CSG::localIntersect(Ray r)
+{
+    int i;
+    Intersect leftxs = this->left->intersect(r);
+    Intersect rightxs = this->right->intersect(r);
+
+    for(i = 0; i < rightxs.count(); i++)
+    {
+        leftxs.add(rightxs[i]);
+    }
+
+    Intersect ret = this->filterIntersections(leftxs);
+
+    return ret;
+}
+
+Tuple CSG::localNormalAt(Tuple point, Intersection *hit)
+{
+    return Vector(1, 0, 0);
+}
+
+BoundingBox CSG::getLocalBounds()
+{
+    return this->bounds;
+}
+
+BoundingBox CSG::getBounds()
+{
+    if (this->bounds.isEmpty()) { this->updateBoundingBox(); }
+    return this->bounds;
+}
+
+void CSG::updateBoundingBox()
+{
+    this->bounds.reset();
+
+    this->bounds | this->left->getBounds();
+    this->bounds | this->right->getBounds();
+}
+
+void CSG::updateTransform()
+{
+    Shape::updateTransform();
+
+    this->left->updateTransform();
+    this->right->updateTransform();
+
+    /* Once the full stack being notified of the changes, let's update the
+     * bounding box
+     */
+    this->updateBoundingBox();
+}
+
+bool CSG::includes(Shape *b)
+{
+    if (this->left->includes(b)) { return true; }
+    if (this->right->includes(b)) { return true; }
+    if (this == b) { return true; }
+    return false;
+}
+
+bool CSG::intersectionAllowed(bool leftHit, bool inLeft, bool inRight)
+{
+    switch(this->operation)
+    {
+    case CSG::UNION:        return (leftHit && !inRight) || (!leftHit && !inLeft);
+    case CSG::INTERSECTION: return (!leftHit && inLeft) || (leftHit && inRight);
+    case CSG::DIFFERENCE:   return (leftHit && !inRight) || (!leftHit && inLeft);
+    }
+    return false;
+}
+
+Intersect CSG::filterIntersections(Intersect &xs)
+{
+    bool inl = false;
+    bool inr = false;
+
+    Intersect ret = Intersect();
+
+    int i;
+
+    for(i = 0; i < xs.count(); i++)
+    {
+        bool lhit = this->left->includes(xs[i].object);
+
+        if (this->intersectionAllowed(lhit, inl, inr))
+        {
+            ret.add(xs[i]);
+        }
+
+        if (lhit)
+        {
+            inl = !inl;
+        }
+        else
+        {
+            inr = !inr;
+        }
+    }
+
+    return ret;
+}
+
+void CSG::dumpMe(FILE *fp)
+{
+
+}

+ 29 - 0
source/shapes/group.cpp

@@ -67,6 +67,35 @@ Intersect Group::intersect(Ray r)
     return ret;
 }
 
+bool Group::includes(Shape *b)
+{
+    if (this->objectCount > 0)
+    {
+        int i;
+        for (i = 0 ; i < this->objectCount ; i++)
+        {
+            if (this->objectList[i] == b)
+            {
+                return true;
+            }
+        }
+    }
+
+    /* We are force to do them all the time */
+    if (this->unboxableObjectCount > 0)
+    {
+        int i;
+        for(i = 0; i < this->unboxableObjectCount; i++)
+        {
+            if (this->unboxableObjectList[i] == b)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 Intersect Group::localIntersect(Ray r)
 {
     return Intersect();

+ 16 - 0
source/shapes/objfile.cpp

@@ -130,6 +130,22 @@ Intersect OBJFile::intersect(Ray r)
     return ret;
 }
 
+bool OBJFile::includes(Shape *b)
+{
+    int i;
+    if (this->faceGroupCount > 0)
+    {
+        for (i = 0 ; i < this->faceGroupCount ; i++)
+        {
+            if (this->faceGroupList[i] == b)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 Intersect OBJFile::localIntersect(Ray r)
 {
     return Intersect();

+ 5 - 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 smoothtriangle_test.cpp)
+        boundingbox_test.cpp triangle_test.cpp sequence_test.cpp objfile_test.cpp smoothtriangle_test.cpp csg_test.cpp)
 
 add_executable(testMyRays)
 target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
@@ -81,6 +81,9 @@ add_custom_command(
         ${CMAKE_CURRENT_BINARY_DIR}/
 )
 
+add_executable(ch16_test)
+target_sources(ch16_test PRIVATE ch16_test.cpp)
+
 add_executable(arealight_test)
 target_sources(arealight_test PRIVATE arealight_test.cpp)
 
@@ -148,6 +151,7 @@ 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 Chapter16_Test COMMAND $<TARGET_FILE:ch16_test>)
 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>)

+ 105 - 0
tests/ch16_test.cpp

@@ -0,0 +1,105 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  Render test for CSG in chapter 16.
+ *
+ *  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 <group.h>
+#include <cone.h>
+#include <csg.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 lights */
+    Light light1 = Light(POINT_LIGHT, Point(100, 100, -100), Colour(1, 1, 1));
+    w.addLight(&light1);
+
+    /* ----------------------------- */
+
+    /* White background */
+    Plane p = Plane();
+    p.setTransform(translation(0, 0, 100) *  rotationX(1.5708));
+    p.material.colour = Colour(1, 1, 1);
+    p.material.ambient = 1;
+    p.material.diffuse = 0;
+    p.material.specular = 0;
+    w.addObject(&p);
+
+
+    Cylinder c1 = Cylinder();
+    c1.minCap = -2;
+    c1.maxCap = 2;
+    c1.isClosed = true;
+    c1.material.colour = Colour(1, 0, 0);
+    c1.setTransform(scaling(0.4, 1, 0.4));
+    c1.materialSet = true;
+
+    Cylinder c2 = Cylinder();
+    c2.minCap = -2;
+    c2.maxCap = 2;
+    c2.isClosed = true;
+    c2.material.colour = Colour(0, 1, 0);
+    c2.setTransform(rotationX(M_PI/2) * scaling(0.4, 1, 0.4));
+    c2.materialSet = true;
+
+    CSG leaf1 = CSG(CSG::UNION, &c1, &c2);
+
+    Cylinder c3 = Cylinder();
+    c3.minCap = -2;
+    c3.maxCap = 2;
+    c3.isClosed = true;
+    c3.material.colour = Colour(0, 0, 1);
+    c3.setTransform(rotationZ(M_PI/2) * scaling(0.4, 1, 0.4));
+    c3.materialSet = true;
+
+    CSG leaf2 = CSG(CSG::UNION, &leaf1, &c3);
+
+    Cube cb = Cube();
+    //cb.materialSet = true;
+    //cb.material.reflective = 1;
+
+    Sphere sp = Sphere();
+    sp.setTransform(scaling(1.35, 1.35, 1.35));
+    CSG leaf3 = CSG(CSG::INTERSECTION, &sp, &cb);
+
+    CSG leaf4 = CSG(CSG::DIFFERENCE, &leaf3, &leaf2);
+
+    w.addObject(&leaf4);
+
+    /* ----------------------------- */
+
+    /* Set the camera */
+    Camera camera = Camera(800, 400, M_PI / 2);
+    camera.setTransform(viewTransform(Point(-4, 4, -9),
+                                      Point(0, 1, 0),
+                                      Vector(0, 1, 0)));
+
+    /* Now render it */
+    Canvas image = camera.render(w, 5);
+
+    image.SaveAsPNG("ch16_test.png");
+
+    return 0;
+}

+ 226 - 0
tests/csg_test.cpp

@@ -0,0 +1,226 @@
+/*
+ *  DoRayMe - a quick and dirty Raytracer
+ *  CSG unit tests
+ *
+ *  Created by Manoël Trapier
+ *  Copyright (c) 2020 986-Studio.
+ *
+ */
+#include <intersect.h>
+#include <intersection.h>
+#include <sphere.h>
+#include <cube.h>
+#include <csg.h>
+#include <transformation.h>
+#include <gtest/gtest.h>
+
+/* Proxy class to get access to protected functions / members */
+class CSGTest : public CSG
+{
+public:
+    Intersect doLocalIntersect(Ray r) {
+        return this->localIntersect(r);
+    };
+    Tuple doLocalNormalAt(Tuple point, Intersection *hit = nullptr) {
+        return this->localNormalAt(point, hit);
+    };
+    BoundingBox doGetLocalBounds() {
+        return this->getLocalBounds();
+    };
+    bool doIntersectionAllowed(bool leftHit, bool inLeft, bool inRight) {
+        return this->intersectionAllowed(leftHit, inLeft, inRight);
+    };
+
+    Intersect doFilterIntersections(Intersect &xs) {
+        return this->filterIntersections(xs);
+    }
+
+    CSGTest(OperationType operation, Shape *left, Shape *right) : CSG(operation, left, right) {};
+
+
+    Shape *getLeft() { return this->left; };
+    Shape *getRight() { return this->right; };
+    OperationType getOperation() { return this->operation; };
+    void setOperation(OperationType operation) { this->operation = operation; };
+};
+
+TEST(CSGTest, Csg_is_created_with_an_operation_and_two_shape)
+{
+    Sphere s1 = Sphere();
+    Cube s2 = Cube();
+
+    CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
+
+    ASSERT_EQ(c.getOperation(), CSG::UNION);
+    ASSERT_EQ(*c.getLeft(), s1);
+    ASSERT_EQ(*c.getRight(), s2);
+
+    ASSERT_EQ(*s1.parent, c);
+    ASSERT_EQ(*s2.parent, c);
+}
+
+TEST(CSGTest, Evaluating_the_rules_for_a_csg_operation)
+{
+    Sphere s1 = Sphere();
+    Cube s2 = Cube();
+    CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
+
+    CSG::OperationType testList2[] = {
+            CSG::UNION, CSG::UNION,
+            CSG::UNION, CSG::UNION,
+            CSG::UNION, CSG::UNION,
+            CSG::UNION, CSG::UNION,
+
+            CSG::INTERSECTION, CSG::INTERSECTION,
+            CSG::INTERSECTION, CSG::INTERSECTION,
+            CSG::INTERSECTION, CSG::INTERSECTION,
+            CSG::INTERSECTION, CSG::INTERSECTION,
+
+            CSG::DIFFERENCE, CSG::DIFFERENCE,
+            CSG::DIFFERENCE, CSG::DIFFERENCE,
+            CSG::DIFFERENCE, CSG::DIFFERENCE,
+            CSG::DIFFERENCE, CSG::DIFFERENCE,
+    };
+
+    bool testList[][3] = {
+            /* lhit, inl,   inr */
+/* UNION */ { true,  true,  true  },
+            { true,  true,  false },
+            { true,  false, true  },
+            { true,  false, false },
+            { false, true,  true  },
+            { false, true,  false },
+            { false, false, true  },
+            { false, false, false },
+
+/* INTER */ { true,  true,  true  },
+            { true,  true,  false },
+            { true,  false, true  },
+            { true,  false, false },
+            { false, true,  true  },
+            { false, true,  false },
+            { false, false, true  },
+            { false, false, false },
+
+/* DIFFE */ { true,  true,  true  },
+            { true,  true,  false },
+            { true,  false, true  },
+            { true,  false, false },
+            { false, true,  true  },
+            { false, true,  false },
+            { false, false, true  },
+            { false, false, false },
+    };
+
+    bool testResults[] {
+        /* Unions */
+            false,
+            true,
+            false,
+            true,
+            false,
+            false,
+            true,
+            true,
+        /* Intersection */
+            true,
+            false,
+            true,
+            false,
+            true,
+            true,
+            false,
+            false,
+        /* difference */
+            false,
+            true,
+            false,
+            true,
+            true,
+            true,
+            false,
+            false
+    };
+
+    int testCount = sizeof(testList)/sizeof((testList)[0]);
+    int i;
+
+    for(i = 0; i < testCount; i++)
+    {
+        c.setOperation(testList2[i]);
+        ASSERT_EQ(c.doIntersectionAllowed(testList[i][0], testList[i][1], testList[i][2]), testResults[i]);
+    }
+}
+
+TEST(CSGTest, Filtering_a_list_of_intersections)
+{
+    Sphere s1 = Sphere();
+    Cube s2 = Cube();
+    CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
+
+    CSG::OperationType testList[] = {
+            CSG::UNION,
+            CSG::INTERSECTION,
+            CSG::DIFFERENCE,
+    };
+
+    uint32_t testResults[][2] = {
+            {0, 3},
+            {1, 2},
+            {0, 1},
+    };
+
+    int testCount = sizeof(testList)/sizeof((testList)[0]);
+    int i;
+
+    Intersect xs = Intersect();
+    Intersection i1 = Intersection(1, &s1);
+    Intersection i2 = Intersection(2, &s2);
+    Intersection i3 = Intersection(3, &s1);
+    Intersection i4 = Intersection(4, &s2);
+    xs.add(i1);
+    xs.add(i2);
+    xs.add(i3);
+    xs.add(i4);
+
+    for(i = 0; i < testCount; i++)
+    {
+        c.setOperation(testList[i]);
+        Intersect result = c.doFilterIntersections(xs);
+
+        ASSERT_EQ(result.count(), 2);
+        ASSERT_EQ(result[0], xs[testResults[i][0]]);
+        ASSERT_EQ(result[1], xs[testResults[i][1]]);
+    }
+}
+
+TEST(CSGTest, A_ray_misses_a_csg_object)
+{
+    Sphere s1 = Sphere();
+    Cube s2 = Cube();
+    CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
+
+    Ray r = Ray(Point(0, 2, -5), Vector(0, 0, 1));
+    Intersect xs = c.doLocalIntersect(r);
+
+    ASSERT_EQ(xs.count(), 0);
+}
+
+TEST(CSGTest, A_ray_hits_a_csg_object)
+{
+    Sphere s1 = Sphere();
+    Sphere s2 = Sphere();
+
+    s2.setTransform(translation(0, 0, 0.5));
+
+    CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
+
+    Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
+    Intersect xs = c.doLocalIntersect(r);
+
+    ASSERT_EQ(xs.count(), 2);
+    ASSERT_TRUE(double_equal(xs[0].t, 4));
+    ASSERT_EQ(xs[0].object, &s1);
+    ASSERT_TRUE(double_equal(xs[1].t, 6.5));
+    ASSERT_EQ(xs[1].object, &s2);
+}