├── .gitignore ├── CMakeLists.txt ├── README.md ├── example_output.obj └── src ├── Settings.hpp ├── catch.hpp ├── main.cpp ├── math ├── Int.hpp ├── Math.cpp ├── Math.hpp ├── Solver.cpp ├── Solver.hpp ├── Vec2.hpp └── Vec3.hpp ├── test.cpp ├── util └── Array3D.hpp └── vol ├── Contouring.cpp └── Contouring.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(DC) 3 | 4 | if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") 6 | 7 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") 9 | set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -lc++") 10 | else() 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 12 | endif() 13 | 14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Werror -Wno-nested-anon-types") 15 | 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I '/opt/local/include/eigen2'") 17 | endif() 18 | 19 | #SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin) 20 | 21 | include_directories(${DC_SOURCE_DIR}/src/math) 22 | include_directories(${DC_SOURCE_DIR}/src/vol) 23 | include_directories(${DC_SOURCE_DIR}/src/util) 24 | include_directories(${DC_SOURCE_DIR}/src) 25 | 26 | add_executable(dc 27 | src/main.cpp 28 | src/math/Math.cpp 29 | src/math/Solver.cpp 30 | src/vol/Contouring.cpp 31 | ) 32 | 33 | add_executable(test 34 | src/test.cpp 35 | src/math/Math.cpp 36 | src/math/Solver.cpp 37 | src/vol/Contouring.cpp 38 | ) 39 | 40 | add_test(core test) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | §Dual Contouring implementation in C++ 2 | ===================================== 3 | 4 | # Who? 5 | Emil Ernerfeldt, June 2013 6 | 7 | 8 | # What? 9 | This is a C++ implementation of Dual Contouring, as outlined in http://www.frankpetterson.com/publications/dualcontour/dualcontour.pdf 10 | 11 | This originated from a small weekend project, so don't expect too much! 12 | 13 | 14 | ## License 15 | This software is in the public domain. Where that dedication is not recognized, you are granted a perpetual, irrevocable license to copy and modify this file as you see fit. 16 | 17 | The license for catch.hpp is separate, as it is not written by me. 18 | 19 | # Program 20 | The program generates a distance field containing a cylinder, a box and a spherical hole in these. It then proceeds to generate a triangle mesh for this, outputting it to a file 'mesh.obj'. 21 | 22 | Settings can be found in "settings.hpp" 23 | 24 | 25 | # Building 26 | 27 | > mkdir build 28 | > cd build 29 | > cmake .. && make 30 | > ./dc 31 | > open -a "OBJ Viewer" mesh.obj 32 | 33 | or: 34 | 35 | > mkdir build 36 | > cd build 37 | > cmake -G Xcode .. 38 | > open DC.xcodeproj/ 39 | 40 | Only tested with clang. If there are any issues you may want to lower the warning-levels in CMakeLists.txt. 41 | 42 | The code relies heavily on C++11, as well as one compiler-specific extension (nameless structs). 43 | 44 | 45 | # Implementation 46 | The differences from the paper include: 47 | * No octrees, and hence no simplifications 48 | * Adapted for input on a discrete lattice (3D grid): no interpolation on edges (tried it, works badly), instead uses points/normals from all corners on a sign-changing edge. This produces good results even for sharp corners. 49 | * Uses standard "AtA" solving of the least-squares problems, as that is not only simple and fast, but sufficent when no simplifications are made (the paper recommends QR decomposition). 50 | * Double precision floats to overcome the numberical instability of the least-square solver. Test floats by typedeffing 'real' in Math.hpp. 51 | * Currently, each input voxel must have a correct sign. One could extend the method to handle 'null' voxels, so that no surface is created between a null and a non-null voxel. 52 | 53 | 54 | # Limitations of Dual Contouring 55 | ## Triangle intersection 56 | Dual Contouring produces one vertex per voxel, but that vertex doesn't necessarily lay inside of the voxel. This produces potential triangle intersections, mostly at very acute angles (sharp corners). One can clamp the coordinates to the voxel. Clamping is both necessary and sufficent to ensure no intersecting triangles, but doesn't always produce quite as nice results. 57 | 58 | A better solution is to check for triangle intersections and resolve as an extra step after the contouring algorithm is done. This is left as an exercise to the reader. 59 | 60 | ## Input jitter 61 | Dual Contouring is VERY sensitive to input jitter, both on the input distances and the normals. You can test this by setting PerturbField in Settings.hpp. This makes is un-suitable to real-world data, and more apt for computer generated data (e.g. CSG output). 62 | -------------------------------------------------------------------------------- /src/Settings.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_HPP 2 | #define SETTINGS_HPP 3 | 4 | #include 5 | 6 | /* Global settings for easy access */ 7 | 8 | 9 | //------------------------------------------------------------------------------ 10 | /* Field generation: */ 11 | 12 | 13 | // Side of the cubic input field 14 | const unsigned FieldSize = 128; // 512 -> 4 GB memory (using double) 15 | 16 | /* If true, will subtract a sphere from the scene, else add it. 17 | * Subtracting it produces very acute angles which produces intersecting triangles */ 18 | const bool SubtractSphere = true; 19 | 20 | /* Ensure a closed mesh by giving all field periphery a positive distance. */ 21 | const bool ClosedField = true; 22 | 23 | /* Emulate noisy input by perturbing the input field. 24 | * These are the standard deviation of the normal distribution of the jitter. 25 | * Dual contouring is VERY bad at handling noisy input, 26 | * even if we clamp and increate CenterPush. */ 27 | const bool PerturbField = false; // Main switch 28 | const float DistJitter = 0.05f; 29 | const float NormalJitter = 0.05f; 30 | 31 | 32 | //------------------------------------------------------------------------------ 33 | /* Contouring: */ 34 | 35 | /* 36 | If true, will restrict the vertex to the voxel. 37 | Clamping is both necessary and sufficent to ensure no intersecting triangles. 38 | Clamping will, however, not always produce as good results. 39 | */ 40 | const bool Clamp = false; 41 | 42 | /* A corner with an absolute distance greater than this will disregarded from vertex generation. 43 | * A distance larger than 1 should never occur with a perfect distance field as input. 44 | * Using this curoff we can produce much better results even for less-than-perfect inputs. 45 | * An infinite cutoff (none) can produce many "far away" vertices. 46 | */ 47 | const math::real MaxCornerDist = 1.0; 48 | 49 | /* When is a vertex "far" from the voxel center? 50 | * Far pixels are always clamped */ 51 | const math::real FarAway = 2.5; 52 | 53 | /* We add a weak push towards the center of the voxel. 54 | This controls how weak. 55 | */ 56 | const math::real CenterPush = 0.01; // The actual push is the square of this 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | using namespace math; 9 | using namespace std; 10 | using namespace util; 11 | using namespace vol; 12 | 13 | 14 | void addCylinder(Field& field) 15 | { 16 | const real rad = field.size().x / 10.0f; 17 | const auto center = Vec2(field.size().xy) * 0.5f; // in the X-Y-plane 18 | 19 | foreach3D(field.size(), [&](Vec3u upos) { 20 | auto& cell = field[upos]; 21 | auto vec = Vec2(upos.xy) - center; 22 | auto d = vec.len() - rad; 23 | 24 | if (d < cell.dist) { 25 | cell.dist = d; 26 | cell.normal = Vec3(normalized(vec), 0); 27 | } 28 | }); 29 | } 30 | 31 | void addCube(Field& field, Vec3 center, real rad) { 32 | const Vec3 size(rad); 33 | 34 | foreach3D(field.size(), [&](Vec3u p) { 35 | auto& cell = field[p]; 36 | auto r = Vec3(p) - center; 37 | 38 | auto a = r.maxAbsAxis(); 39 | auto dist = std::abs(r[a]) - size[a]; 40 | auto normal = sign(r[a]) * Vec3::Axes[a]; 41 | 42 | if (dist < cell.dist) { 43 | cell.dist = dist; 44 | cell.normal = normal; 45 | } 46 | }); 47 | } 48 | 49 | void addSphere(Field& field, Vec3 center, real rad) { 50 | foreach3D(field.size(), [&](Vec3u upos) { 51 | auto& cell = field[upos]; 52 | auto vec = Vec3(upos) - center; 53 | auto d = vec.len() - rad; 54 | 55 | if (d < cell.dist) { 56 | cell.dist = d; 57 | cell.normal = normalized(vec); 58 | } 59 | }); 60 | } 61 | 62 | void removeSphere(Field& field, Vec3 center, real rad) { 63 | foreach3D(field.size(), [&](Vec3u upos) { 64 | auto& cell = field[upos]; 65 | auto vec = Vec3(upos) - center; 66 | auto d = vec.len() - rad; 67 | 68 | if (-d > cell.dist) { 69 | cell.dist = -d; 70 | cell.normal = -normalized(vec); 71 | } 72 | }); 73 | } 74 | 75 | 76 | // Add random jitter to emulate noisy input 77 | void perturbField(Field& field) 78 | { 79 | if (DistJitter <= 0 && NormalJitter <= 0) 80 | return; 81 | 82 | std::mt19937 r; 83 | #if true 84 | std::normal_distribution<> distJitter (0, DistJitter); 85 | std::normal_distribution<> normalJitter(0, NormalJitter); 86 | #else 87 | std::uniform_real_distribution<> distJitter (-DistJitter, +DistJitter); 88 | std::uniform_real_distribution<> normalJitter(-NormalJitter, +NormalJitter); 89 | #endif 90 | 91 | foreach3D(field.size(), [&](Vec3u upos) { 92 | auto& cell = field[upos]; 93 | cell.dist += distJitter(r); 94 | cell.normal += Vec3{ normalJitter(r), normalJitter(r), normalJitter(r) }; 95 | cell.normal.normalize(); 96 | }); 97 | } 98 | 99 | 100 | // Ensure periphery has dist>0 101 | void closeField(Field& field) 102 | { 103 | const int oa[3][2] = {{1,2}, {0,2}, {0,1}}; 104 | const auto fs = field.size(); 105 | 106 | for (int a=0; a<3; ++a) 107 | { 108 | auto a0 = oa[a][0]; 109 | auto a1 = oa[a][1]; 110 | Vec2u sideSize = { fs[a0], fs[a1] }; 111 | 112 | foreach2D(sideSize, [&](Vec2u p2) { 113 | Vec3u p3 = Zero; 114 | p3[a0] = p2[0]; 115 | p3[a1] = p2[1]; 116 | p3[a] = 0; 117 | 118 | if (field[p3].dist <= 0) { 119 | field[p3].dist = 0.5f; 120 | field[p3].normal = -Vec3::Axes[a]; 121 | } 122 | 123 | p3[a] = fs[a]-1; 124 | 125 | if (field[p3].dist <= 0) { 126 | field[p3].dist = 0.5f; 127 | field[p3].normal = +Vec3::Axes[a]; 128 | } 129 | }); 130 | } 131 | } 132 | 133 | 134 | Field generateField() 135 | { 136 | const auto Size = Vec3u( FieldSize ); 137 | Field field(Size, Plane{+INF, Zero}); 138 | 139 | /* Similar test case to the original Dual Contouring paper: 140 | * A cylinder with an added box and a sphere subracted from that box. 141 | */ 142 | 143 | addCylinder(field); 144 | 145 | { 146 | const auto center = Vec3(Size) * 0.5f; 147 | const real rad = Size.x * 3 / 16.0f; 148 | addCube(field, center, rad); 149 | } 150 | 151 | if (SubtractSphere) 152 | { 153 | const auto center = Vec3(Size) * 0.5f + Vec3(0, 0, Size.z) * 2.0 / 16.0; 154 | const real rad = Size.x * 3.5 / 16.0; 155 | removeSphere(field, center, rad); 156 | } 157 | else 158 | { 159 | const auto center = Vec3(Size) * 0.5f + Vec3(0, 0, Size.z) * 2.0 / 16.0; 160 | const real rad = Size.x * 3.5 / 16.0; 161 | addSphere(field, center, rad); 162 | } 163 | 164 | if (PerturbField) { 165 | perturbField(field); 166 | } 167 | 168 | 169 | if (ClosedField) { 170 | // Ensure we get a closed mesh: 171 | closeField(field); 172 | } 173 | 174 | return field; 175 | } 176 | 177 | 178 | int main() { 179 | cout << "Generating " << FieldSize << "^3 field..." << endl; 180 | const auto field = generateField(); 181 | 182 | cout << "Contouring..." << endl; 183 | const auto mesh = dualContouring(field); 184 | 185 | cout << mesh.vecs.size() << " vertices in " << mesh.triangles.size() << " triangles" << endl; 186 | 187 | auto fileName = "mesh.obj"; 188 | cout << "Saving as " << fileName << "... " << endl; 189 | ofstream of(fileName); 190 | 191 | of << "# Vertices:" << endl; 192 | for (auto&& v_orig : mesh.vecs) { 193 | auto v = v_orig - 0.5*Vec3(field.size()); // Center 194 | of << "v " << v.x << " " << v.y << " " << v.z << endl; 195 | } 196 | 197 | of << endl << "# Triangles:" << endl; 198 | for (auto&& tri : mesh.triangles) { 199 | of << "f"; 200 | for (auto&& ix : tri) { 201 | of << " " << (ix + 1); 202 | } 203 | of << endl; 204 | } 205 | 206 | cout << "Done!" << endl; 207 | } 208 | -------------------------------------------------------------------------------- /src/math/Int.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Int.hpp 3 | // EmiLib 3.0 4 | // 5 | // Created by emilk on 2012-11-03. 6 | 7 | #ifndef Math_Int_hpp 8 | #define Math_Int_hpp 9 | 10 | namespace math 11 | { 12 | // Returns the next power-of-two HIGHER OR EQUAL to k. 13 | inline size_t nextPowerOfTwo(size_t k) 14 | { 15 | if (k==0) 16 | return 1; 17 | 18 | k--; 19 | for (size_t i=1; i> i)); 21 | return k+1; 22 | } 23 | 24 | inline constexpr bool isPowerOfTwo(size_t k) 25 | { 26 | return (k & (k-1))==0; 27 | } 28 | 29 | // Returns v if v%N==0 30 | inline constexpr int nextMultipleOfN(int v, int N) 31 | { 32 | return (v==0 ? 0 : ((v-1)/N + 1)*N); 33 | } 34 | 35 | // Returns v if v%N==0 36 | inline constexpr int prevMultipleOfN(int v, int N) 37 | { 38 | return (v/N)*N; 39 | } 40 | 41 | inline constexpr bool isMultipleOfN(int v, int N) 42 | { 43 | return (v/N)*N == v; 44 | } 45 | 46 | // Good for hashing 47 | constexpr unsigned HUGE_PRIME_0 = 0x8da6b343; 48 | constexpr unsigned HUGE_PRIME_1 = 0xd8163841; 49 | constexpr unsigned HUGE_PRIME_2 = 0xcb1ab31f; 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/math/Math.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Math.cpp 3 | // EmiLib 3.0 4 | // 5 | // Created by emilk on 2012-10-15. 6 | 7 | #include "Math.hpp" 8 | #include 9 | #include 10 | 11 | 12 | namespace math 13 | { 14 | static_assert(std::numeric_limits::has_denorm, "has_denorm"); 15 | /* See 16 | http://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/ 17 | for the potential portability problems with the union and bit-fields below. 18 | */ 19 | union Float_t 20 | { 21 | Float_t(float num = 0.0f) : f(num) {} 22 | // Portable extraction of components. 23 | bool Negative() const { return (i >> 31) != 0; } 24 | int32_t RawMantissa() const { return i & ((1 << 23) - 1); } 25 | int32_t RawExponent() const { return (i >> 23) & 0xFF; } 26 | 27 | int32_t i; 28 | float f; 29 | #ifdef _DEBUG 30 | struct 31 | { // Bitfields for exploration. Do not use in production code. 32 | uint32_t mantissa : 23; 33 | uint32_t exponent : 8; 34 | uint32_t sign : 1; 35 | } parts; 36 | #endif 37 | }; 38 | static_assert(sizeof(Float_t)==sizeof(float), "Pack"); 39 | 40 | float nextFloat(float arg) 41 | { 42 | if (std::isnan(arg)) return arg; 43 | if (arg==+INF) return +INF; // Can't go higher. 44 | if (arg==0) return std::numeric_limits::denorm_min(); 45 | 46 | Float_t f = arg; 47 | f.i += 1; 48 | assert(f.f > arg); 49 | return f.f; 50 | } 51 | 52 | 53 | //------------------------------------------------------------------------------ 54 | 55 | const zero_tag zero_tag::s_instance; 56 | const zero_tag Zero(zero_tag::s_instance); 57 | } 58 | -------------------------------------------------------------------------------- /src/math/Math.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Math.hpp 3 | // EmiLib 3.0 4 | // 5 | // Created by emilk on 2012-09-09. 6 | 7 | #ifndef EmiLib_Math_hpp 8 | #define EmiLib_Math_hpp 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace math 16 | { 17 | #define IS_FLOATING_POINT(Type) (std::is_floating_point::value) 18 | #define STATIC_ASSERT_FLOATING_POINT(Type) static_assert(IS_FLOATING_POINT(Type), "Must be a float") 19 | 20 | //------------------------------------------------------------------------------ 21 | 22 | typedef double real; 23 | 24 | constexpr float PIf = (float)3.1415926535897932384626433; 25 | constexpr real PI = PIf; 26 | constexpr real TAU = 2*PI; // Oh yes. http://tauday.com/tau-manifesto.pdf 27 | constexpr real NaN = std::numeric_limits::quiet_NaN(); 28 | constexpr real INF = std::numeric_limits::infinity(); 29 | 30 | const float SQRT_3 = std::sqrt(3.0f); // Box: outer radius over inner 31 | 32 | //------------------------------------------------------------------------------ 33 | // Numbers smaller than eps are easily rounding errors 34 | 35 | template 36 | inline constexpr F Eps(); 37 | 38 | template<> 39 | inline constexpr float Eps() { return 2e-5f; } 40 | 41 | template<> 42 | inline constexpr double Eps() { return 1e-11; } // Way more than DBL_EPSILON... but whatever. 43 | 44 | const double EPSd = Eps(); 45 | const float EPSf = Eps(); 46 | const float EPS = Eps(); 47 | 48 | //------------------------------------------------------------------------------ 49 | 50 | template 51 | constexpr int floorI(F f) { 52 | return (int)floor(f); // FIXME: doesn't work as constexpr! 53 | } 54 | 55 | template 56 | constexpr int ceilI(F f) { 57 | return (int)ceil(f); 58 | } 59 | 60 | // nearest integer, rounding away from zero in halfway cases 61 | template 62 | constexpr int roundI(F f) { 63 | //return (int)round(f); // FIXME: doesn't work as constexpr! 64 | //return floorI(f+0.5f); 65 | return int(f < 0 ? f-0.5f : f+0.5f); // int(foo) rounds towards zero 66 | } 67 | static_assert(roundI(+0.4) == 0, "roundI test"); 68 | static_assert(roundI(+0.5) == +1, "roundI test"); 69 | static_assert(roundI(+0.6) == +1, "roundI test"); 70 | static_assert(roundI(-0.4) == 0, "roundI test"); 71 | static_assert(roundI(-0.5) == -1, "roundI test"); 72 | static_assert(roundI(-0.6) == -1, "roundI test"); 73 | 74 | template 75 | constexpr F abs(F f) { 76 | return (f<0 ? -f : f); 77 | } 78 | static_assert(math::abs(-.1f) == +.1f, "math::abs"); 79 | 80 | template 81 | inline constexpr int sign(const T& val) 82 | { 83 | return (val<0 ? -1 : val>0 ? +1 : 0); 84 | } 85 | 86 | template 87 | inline constexpr T signF(const T& val) 88 | { 89 | return (val<0 ? (T)-1 : val>0 ? (T)+1 : (T)0); 90 | } 91 | 92 | template 93 | inline constexpr T clamp(T x, T mn, T mx) { 94 | return (x < mn ? mn : x > mx ? mx : x); 95 | } 96 | 97 | template 98 | inline constexpr T clamp(T x) { 99 | return clamp(x, 0, 1); 100 | } 101 | 102 | template 103 | inline constexpr T lerp(const T& a, const T& b, float t) { 104 | return a*(1-t) + b*t; 105 | } 106 | 107 | template 108 | inline constexpr T lerp(const T& a, const T& b, double t) { 109 | return a*(1-t) + b*t; 110 | } 111 | 112 | // For color-components: 113 | template<> 114 | inline constexpr uint8_t lerp(const uint8_t& a, const uint8_t& b, float t) 115 | { 116 | return (uint8_t)roundI((1-t)*a + t*b); 117 | } 118 | 119 | // IS THIS VERBOSE ENOUGH FOR YOU COMPILER? HUH? IS IT!? 120 | template 121 | constexpr auto average(const T& a, const T& b) -> decltype((a+b)/2) { 122 | return (a+b)/2; 123 | } 124 | 125 | template 126 | inline constexpr T sqr(T x) { 127 | return x*x; 128 | } 129 | 130 | template 131 | inline constexpr T cube(T x) { 132 | return x*x*x; 133 | } 134 | 135 | inline real deg2Rad(real a) { 136 | return a * PIf / 180; 137 | } 138 | 139 | // To [-PI, +PI] 140 | template 141 | inline T wrapAngle(T a) { 142 | while (a < -PIf) a += TAU; 143 | while (a > +PIf) a -= TAU; 144 | return a; 145 | } 146 | 147 | inline real lerpAngle(real a0, real a1, float t) 148 | { 149 | return a0 + t * wrapAngle(a1-a0); 150 | } 151 | 152 | template 153 | inline void sort(T& a, T& b) { 154 | if (b 167 | inline constexpr T max(T a, T b) { 168 | return a>b ? a : b; 169 | } 170 | 171 | template 172 | inline constexpr bool equals(T a, T b, T eps = Eps()) 173 | { 174 | #if 0 175 | bad 176 | return abs(a-b) <= eps * max(abs(a),abs(b)); 177 | #elif 0 178 | // FIXME: std::isnan etc aren't constexpr... :( 179 | return 180 | std::isnan(a) || std::isnan(b) ? false : 181 | std::isinf(a) && std::isinf(b) ? sign(a) == sign(b) : 182 | std::isinf(a) || std::isinf(b) ? false : 183 | a*b==0 ? abs(a+b) <= eps : // Any zero? 184 | a*b<0 ? abs(a-b) <= eps : // Different signs? 185 | abs(a-b) <= eps * max(abs(a),abs(b)); 186 | #else 187 | return abs(a-b) < eps; 188 | #endif 189 | } 190 | 191 | //static_assert(!equals(+NaN, +NaN), "math::equals broken"); 192 | //static_assert( equals(+INF, +INF), "math::equals broken"); 193 | //static_assert(!equals(+INF, -INF), "math::equals broken"); 194 | static_assert( equals(+1, +1), "math::equals broken"); 195 | static_assert( equals(-1, -1), "math::equals broken"); 196 | static_assert(!equals(-1, +1), "math::equals broken"); 197 | static_assert( equals(+1, +1.000001, 1e-5), "math::equals broken"); 198 | static_assert(!equals(+1, +1.0001, 1e-5), "math::equals broken"); 199 | //static_assert( equals(+1000000, +1000001, 1e-5), "math::equals broken"); // TODO 200 | 201 | /* 202 | Interpolate the cubic Hermite spline from: 203 | point p0 with tangent m0 at t=0. 204 | to 205 | point p1 with tangent m1 at t=1. 206 | */ 207 | template 208 | inline T hermite(T p0, T m0, T p1, T m1, float t) 209 | { 210 | float t2 = t*t; 211 | float t3 = t2*t; 212 | return (2*t3 - 3*t2 + 1)*p0 + (t3 - 2*t2 + t)*m0 + (-2*t3 + 3*t2)*p1 + (t3 - t2)*m1; 213 | } 214 | 215 | // For t=[0,1], returns [0,1] with a derivate of zero at both ends 216 | template 217 | constexpr T easeInEaseOut(T t) 218 | { 219 | return 3*t*t - 2*t*t*t; 220 | } 221 | 222 | // normalized sinc function 223 | template 224 | inline F sinc(F x) 225 | { 226 | STATIC_ASSERT_FLOATING_POINT(F); 227 | if (x==0) return 1; // Prevent singularity 228 | return sin(PI*x)/(PI*x); 229 | } 230 | 231 | // t is [0,1] between p1 and p2 232 | template 233 | inline T catmullRom(F t, T p0, T p1, T p2, T p3) { 234 | STATIC_ASSERT_FLOATING_POINT(F); 235 | return 0.5f * ( 236 | p0 * t*((2-t)*t-1) + 237 | p1 * (t*t*(3*t-5)+2) + 238 | p2 * t*((4-3*t)*t+1) + 239 | p3 * (t-1)*t*t 240 | ); 241 | } 242 | 243 | template 244 | inline T catmullRom(F t, T points[4]) { 245 | return catmullRom(t, points[0], points[1], points[2], points[3]); 246 | } 247 | 248 | 249 | //------------------------------------------------------------------------------ 250 | 251 | 252 | // Returns the next float greater than 'arg'. 253 | // if NAN, will return NAN. 254 | // if INF, will return INF. 255 | // if -INF, will return std::numeric_limit::min() 256 | // Will allow denormal numbers. 257 | inline float nextFloat(float arg); 258 | 259 | //------------------------------------------------------------------------------ 260 | 261 | // Tag class 262 | class zero_tag 263 | { 264 | public: 265 | static const zero_tag s_instance; 266 | 267 | zero_tag(const zero_tag&){} 268 | private: 269 | zero_tag(){} 270 | zero_tag& operator=(zero_tag&){return *this;} 271 | }; 272 | 273 | /* 274 | Typeless, unitless, dimensionless zero. 275 | Usage:: Vec2 v = Zero; assert(v == Zero); 276 | */ 277 | extern const zero_tag Zero; 278 | } 279 | 280 | #endif 281 | -------------------------------------------------------------------------------- /src/math/Solver.cpp: -------------------------------------------------------------------------------- 1 | #include "Solver.hpp" 2 | #include // cerr for error output (quick-n-dirty) 3 | 4 | namespace math 5 | { 6 | real determinant( 7 | real a, real b, real c, 8 | real d, real e, real f, 9 | real g, real h, real i ) 10 | { 11 | return a * e * i 12 | + b * f * g 13 | + c * d * h 14 | - a * f * h 15 | - b * d * i 16 | - c * e * g; 17 | } 18 | 19 | 20 | /* Solves for x in A*x = b. 21 | 'A' contains the matrix row-wise. 22 | 'b' and 'x' are column vectors. 23 | Uses cramers rule. 24 | */ 25 | Vec3 solve3x3(const real* A, const real b[3]) { 26 | auto det = determinant( 27 | A[0*3+0], A[0*3+1], A[0*3+2], 28 | A[1*3+0], A[1*3+1], A[1*3+2], 29 | A[2*3+0], A[2*3+1], A[2*3+2]); 30 | 31 | if (abs(det) <= 1e-12) { 32 | std::cerr << "Oh-oh - small determinant: " << det << std::endl; 33 | return Vec3(NAN); 34 | } 35 | 36 | return Vec3 { 37 | determinant( 38 | b[0], A[0*3+1], A[0*3+2], 39 | b[1], A[1*3+1], A[1*3+2], 40 | b[2], A[2*3+1], A[2*3+2] ), 41 | 42 | determinant( 43 | A[0*3+0], b[0], A[0*3+2], 44 | A[1*3+0], b[1], A[1*3+2], 45 | A[2*3+0], b[2], A[2*3+2] ), 46 | 47 | determinant( 48 | A[0*3+0], A[0*3+1], b[0] , 49 | A[1*3+0], A[1*3+1], b[1] , 50 | A[2*3+0], A[2*3+1], b[2] ) 51 | 52 | } / det; 53 | } 54 | 55 | /* 56 | Solves A*x = b for over-determined systems. 57 | 58 | Solves using At*A*x = At*b trick where At is the transponate of A 59 | */ 60 | Vec3 leastSquares(size_t N, const Vec3* A, const real* b) 61 | { 62 | if (N == 3) { 63 | const real A_mat[3*3] = { 64 | A[0].x, A[0].y, A[0].z, 65 | A[1].x, A[1].y, A[1].z, 66 | A[2].x, A[2].y, A[2].z, 67 | }; 68 | return solve3x3(A_mat, b); 69 | } 70 | 71 | real At_A[3][3]; 72 | real At_b[3]; 73 | 74 | for (int i=0; i<3; ++i) { 75 | for (int j=0; j<3; ++j) { 76 | real sum = 0; 77 | for (size_t k=0; k 12 | #include 13 | #include 14 | 15 | 16 | #if __APPLE__ 17 | # include // CGPoint & CGSize 18 | #endif 19 | 20 | 21 | #ifndef QTC_BUILD 22 | // warning: anonymous structs are a GNU extension [-pedantic,-Wgnu] 23 | //#pragma clang diagnostic ignored "-Wpedantic" 24 | #pragma clang diagnostic ignored "-Wgnu" 25 | #else 26 | #pragma GCC diagnostic ignored "-pedantic" 27 | #endif 28 | 29 | 30 | namespace math 31 | { 32 | struct scalar_tag{}; 33 | 34 | 35 | //------------------------------------------------------------------------------ 36 | 37 | 38 | template 39 | class Vec2T 40 | { 41 | public: 42 | typedef T element_type; 43 | 44 | union { 45 | T m_v[2]; 46 | struct { T x, y; }; 47 | struct { T width, height; }; 48 | //std::complex cplx; // not POD 49 | }; 50 | 51 | //------------------------------------------------------------------------------ 52 | 53 | Vec2T() = default; // Fast - no initialization! 54 | Vec2T(zero_tag) : x(0), y(0) { } 55 | Vec2T(T x_, T y_) { 56 | x = x_; 57 | y = y_; 58 | } 59 | 60 | explicit Vec2T(T a) { 61 | x = a; 62 | y = a; 63 | } 64 | 65 | template 66 | explicit Vec2T(const Vec2T& v) { 67 | x = (T)v.x; 68 | y = (T)v.y; 69 | } 70 | 71 | #if __APPLE__ 72 | explicit Vec2T(const CGPoint& p) { 73 | x = (T)p.x; 74 | y = (T)p.y; 75 | } 76 | explicit Vec2T(const CGSize& s) { 77 | x = (T)s.width; 78 | y = (T)s.height; 79 | } 80 | 81 | operator CGPoint() const 82 | { 83 | return CGPoint{x,y}; 84 | } 85 | 86 | operator CGSize() const 87 | { 88 | return CGSize{x,y}; 89 | } 90 | #endif // __APPLE__ 91 | 92 | //------------------------------------------------------------------------------ 93 | // Static constructors: 94 | 95 | // Returns the unit-vector of a certain angle (angle=0, vector=[1,0], angle=Pi/2, vector=[0,1]) 96 | // The invert of Vec2T::angle() 97 | static const Vec2T angled(real a) { 98 | Vec2T ret = Vec2T(std::cos(a), std::sin(a)); 99 | ret = idealizedNormal(ret); 100 | return ret; 101 | } 102 | 103 | /* Given a unit-vector, if the angle is VERY close to being a factor of 45∞ 104 | the the normal will be modified to be perfect. 105 | */ 106 | static const Vec2T idealizedNormal(Vec2T vec) 107 | { 108 | if (isZero(math::abs(vec.x) - math::abs(vec.y))) 109 | { 110 | // N * 45 deg 111 | vec.x = signF(vec.x); 112 | vec.y = signF(vec.y); 113 | vec *= std::sqrt((T)2) / 2; 114 | } 115 | else 116 | { 117 | for (unsigned a=0; a<2; ++a) 118 | { 119 | if (isZero(math::abs(vec[a]))) 120 | { 121 | vec[ a ] = 0; 122 | vec[1-a] = (T)sign(vec[1-a]); 123 | break; 124 | } 125 | } 126 | } 127 | return vec; 128 | } 129 | 130 | //------------------------------------------------------------------------------ 131 | 132 | T X() const { return x; } 133 | T& X() { return x; } 134 | T Y() const { return y; } 135 | T& Y() { return y; } 136 | 137 | T operator[](int i) const { assert(0<=i && i<2); return m_v[i]; } 138 | T& operator[](int i) { assert(0<=i && i<2); return m_v[i]; } 139 | T operator[](unsigned i) const { assert(0<=i && i<2); return m_v[i]; } 140 | T& operator[](unsigned i) { assert(0<=i && i<2); return m_v[i]; } 141 | 142 | //------------------------------------------------------------------------------ 143 | 144 | T lenSq() const { return x*x + y*y; } 145 | T sq() const { return x*x + y*y; } 146 | //real len() const { return std::sqrt((real)lenSq()); } 147 | real len() const { return std::hypot(x,y); } 148 | 149 | // Returns length 150 | T normalize() { 151 | T l = len(); 152 | if (l != 0) { 153 | *this *= 1.0f/l; 154 | } 155 | return l; 156 | } 157 | 158 | // The angle of the vector. Vec2T(1,0).GetAngle() == 0 and Vec2T(0,1).GetAngle() == Pi/2 159 | // Returns an angle in [-pi, +pi] 160 | // The invert of TVector2::Angled 161 | real angle() const 162 | { 163 | if (x == 0 && y == 0) 164 | return 0; 165 | return std::atan2(y, x); 166 | } 167 | 168 | T area() const { return x*y; } 169 | 170 | T min() const { return std::min(x,y); } 171 | T max() const { return std::max(x,y); } 172 | T minAbs() const { return std::min(std::abs(x), std::abs(y)); } 173 | T maxAbs() const { return std::max(std::abs(x), std::abs(y)); } 174 | 175 | //------------------------------------------------------------------------------ 176 | 177 | constexpr Vec2T operator-() const { 178 | return Vec2T(-x, -y); 179 | } 180 | 181 | constexpr const Vec2T& operator+() const { 182 | return *this; 183 | } 184 | 185 | //------------------------------------------------------------------------------ 186 | 187 | Vec2T& operator+=(const Vec2T& b) { 188 | x += b.x; 189 | y += b.y; 190 | return *this; 191 | } 192 | 193 | Vec2T& operator-=(const Vec2T& b) { 194 | x -= b.x; 195 | y -= b.y; 196 | return *this; 197 | } 198 | 199 | Vec2T& operator*=(T s) { 200 | x *= s; 201 | y *= s; 202 | return *this; 203 | } 204 | 205 | //------------------------------------------------------------------------------ 206 | 207 | friend constexpr const Vec2T operator+(const Vec2T& a, const Vec2T& b) { 208 | return Vec2T(a.x+b.x, a.y+b.y); 209 | } 210 | friend constexpr const Vec2T operator-(const Vec2T& a, const Vec2T& b) { 211 | return Vec2T(a.x-b.x, a.y-b.y); 212 | } 213 | friend constexpr const Vec2T operator*(T s, const Vec2T& a) { 214 | return Vec2T(a.x*s, a.y*s); 215 | } 216 | friend constexpr const Vec2T operator*(const Vec2T& a, T s) { 217 | return Vec2T(a.x*s, a.y*s); 218 | } 219 | friend constexpr const Vec2T operator/(const Vec2T& a, T s) { 220 | return Vec2T(a.x/s, a.y/s); 221 | } 222 | 223 | //------------------------------------------------------------------------------ 224 | 225 | friend constexpr bool operator==(const Vec2T& a, const Vec2T& b) { 226 | return a.x==b.x && a.y==b.y; 227 | } 228 | friend constexpr bool operator!=(const Vec2T& a, const Vec2T& b) { 229 | return a.x!=b.x || a.y!=b.y; 230 | } 231 | friend constexpr bool operator<(const Vec2T& a, const Vec2T& b) { 232 | return (a.x!=b.x ? a.x Vec2; 275 | typedef Vec2T Vec2f; 276 | typedef Vec2T Vec2d; 277 | typedef Vec2T Vec2i; 278 | typedef Vec2T Vec2u; 279 | typedef Vec2T Vec2u16; 280 | typedef Vec2T Vec2u8; 281 | 282 | typedef std::vector Vec2List; 283 | 284 | static_assert(sizeof(Vec2) == 2*sizeof(real), "Pack"); 285 | 286 | static_assert(std::is_pod::value, "is_pod"); 287 | static_assert(std::is_standard_layout::value, "is_standard_layout"); 288 | static_assert(std::is_trivial::value, "is_trivial"); 289 | static_assert(std::is_trivially_copyable::value, "is_trivially_copyable"); 290 | 291 | //------------------------------------------------------------------------------ 292 | // utils 293 | 294 | template 295 | inline const Vec2T rot90CCW(const Vec2T& v) 296 | { 297 | return Vec2T(-v.y, v.x); 298 | } 299 | 300 | template 301 | inline const Vec2T rot90CW(const Vec2T& v) 302 | { 303 | return Vec2T(v.y, -v.x); 304 | } 305 | 306 | //------------------------------------------------------------------------------ 307 | 308 | inline Vec2 normalized(const Vec2& v) 309 | { 310 | auto len = v.len(); 311 | if (len == 0) 312 | return Vec2(0,0); 313 | else 314 | return v / len; 315 | } 316 | 317 | // Safe: normalize 0, return zero. 318 | inline Vec2 normalizedOrZero(const Vec2& v) 319 | { 320 | auto len = v.len(); 321 | if (isZero(len)) 322 | return Vec2(0,0); 323 | else 324 | return v / len; 325 | } 326 | 327 | /* Quickly calculated the difference in angle between two vectors b and a. */ 328 | inline real vec2AngleDiff(const Vec2& b, const Vec2& a) 329 | { 330 | return b.angle() - a.angle(); // TODO: optimize 331 | } 332 | 333 | inline bool isFinite(Vec2 v) 334 | { 335 | return isFinite(v.x) && isFinite(v.y); 336 | } 337 | 338 | template 339 | inline Vec2T div(T t, const Vec2T& vec) 340 | { 341 | return Vec2T(t/vec.x, t/vec.y); 342 | } 343 | 344 | template 345 | inline Vec2T div(Vec2T a, const Vec2T& b) 346 | { 347 | return Vec2T(a.x/b.x, a.y/b.y); 348 | } 349 | 350 | template 351 | inline Vec2T mul(Vec2T a, const Vec2T& b) 352 | { 353 | return Vec2T(a.x*b.x, a.y*b.y); 354 | } 355 | 356 | // Works like in glsl 357 | inline Vec2 reflect(const Vec2& d, const Vec2& n) { 358 | return d - 2*dot(d, n)*n; 359 | } 360 | 361 | 362 | //------------------------------------------------------------------------------ 363 | 364 | 365 | inline bool isPowerOfTwo(Vec2u vec) { 366 | return math::isPowerOfTwo(vec.x) 367 | && 368 | math::isPowerOfTwo(vec.y); 369 | } 370 | 371 | inline Vec2 round(Vec2 v) { 372 | return Vec2(std::round(v.x), std::round(v.y)); 373 | } 374 | 375 | inline Vec2i roundI(Vec2 v) { 376 | return Vec2i(roundI(v.x), roundI(v.y)); 377 | } 378 | 379 | inline Vec2i floorI(Vec2 v) { 380 | return Vec2i(floorI(v.x), floorI(v.y)); 381 | } 382 | 383 | inline Vec2i ceilI(Vec2 v) { 384 | return Vec2i(ceilI(v.x), ceilI(v.y)); 385 | } 386 | 387 | inline Vec2i sign(Vec2 v) { 388 | return Vec2i(sign(v.x), sign(v.y)); 389 | } 390 | 391 | template 392 | inline Vec2T abs(Vec2T v) { 393 | return {abs(v.x), abs(v.y)}; 394 | } 395 | 396 | // ensure [0,size) 397 | inline Vec2i clampToSize(Vec2i p, Vec2i size) { 398 | return Vec2i::min(size-Vec2i(1), Vec2i::max(Vec2i(0), p)); 399 | } 400 | 401 | template 402 | inline Vec2T clamp(Vec2T v, Vec2T mn, Vec2T mx) { 403 | Vec2T ret; 404 | for (int d=0; d<2; ++d) 405 | ret[d] = math::clamp(v[d], mn[d], mx[d]); 406 | return ret; 407 | } 408 | 409 | template 410 | inline constexpr T sqr(Vec2T v) { 411 | return v.sq(); 412 | } 413 | 414 | //------------------------------------------------------------------------------ 415 | 416 | #if 1 417 | template 418 | inline std::ostream& operator<<(std::ostream& os, const Vec2T& v) 419 | { 420 | return os << "<" << v.x << ", " << v.y << ">"; 421 | } 422 | #endif 423 | } 424 | 425 | namespace std 426 | { 427 | template struct hash> { 428 | size_t operator()(const math::Vec2T& val) const { 429 | return std::hash()(val.x) + std::hash()(val.y) * math::HUGE_PRIME_0; 430 | } 431 | }; 432 | } 433 | 434 | #endif 435 | -------------------------------------------------------------------------------- /src/math/Vec3.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MATH_VEC3_HPP 2 | #define MATH_VEC3_HPP 3 | 4 | #include 5 | #include "Vec2.hpp" 6 | 7 | namespace math 8 | { 9 | /* Tagging is a way to get typesafe (strong) typedefs: 10 | 11 | struct RGBAf_tag{}; 12 | typedef Vec4T RGBAf; 13 | */ 14 | template 15 | class Vec3T 16 | { 17 | public: 18 | typedef T element_type; 19 | typedef T* iterator; 20 | typedef const T* const_iterator; 21 | 22 | union { 23 | T m_v[3]; 24 | struct { T x, y, z; }; 25 | struct { T r, g, b; }; 26 | struct { Vec2T xy; T z_; }; 27 | }; 28 | 29 | 30 | static const Vec3T Axes[3];/* = { 31 | {1,0,0}, {0,1,0}, {0,0,1} 32 | };*/ 33 | 34 | //------------------------------------------------------------------------------ 35 | 36 | Vec3T() = default; // Fast - no initialization! 37 | Vec3T(zero_tag) : x(0), y(0), z(0) { } 38 | explicit Vec3T(T v); 39 | Vec3T(const Vec2& v, T z); 40 | 41 | #if 1 42 | Vec3T(T x, T y, T z); 43 | #else 44 | template 45 | explicit Vec3T(T2 x_, T2 y_, T2 z_); 46 | #endif 47 | 48 | //Vec3T(const Vec3T& v) = default; 49 | 50 | template 51 | explicit Vec3T(Vec3T v) : x(v.x), y(v.y), z(v.z) { } 52 | 53 | // Explicit cast: 54 | template 55 | explicit Vec3T(const Vec3T& v) : x((T)v.x), y((T)v.y), z((T)v.z) { } 56 | 57 | //------------------------------------------------------------------------------ 58 | 59 | T* data() { return m_v; } 60 | const T* data() const { return m_v; } 61 | 62 | static constexpr unsigned size() { return 3; } 63 | iterator begin() { return data(); } 64 | const_iterator begin() const { return data(); } 65 | iterator end() { return data() + size(); } 66 | const_iterator end() const { return data() + size(); } 67 | 68 | 69 | //------------------------------------------------------------------------------ 70 | 71 | //const Vec2 xy const { return Vec2(x,y); } 72 | 73 | T operator[](int i) const { return m_v[i]; } 74 | T& operator[](int i) { return m_v[i]; } 75 | 76 | T operator[](unsigned i) const { return m_v[i]; } 77 | T& operator[](unsigned i) { return m_v[i]; } 78 | 79 | //------------------------------------------------------------------------------ 80 | 81 | T lenSq() const { return x*x + y*y + z*z; } 82 | T len() const { return std::sqrt((T)lenSq()); } 83 | bool isNormalized() const { return equals(lenSq(), 1); } 84 | 85 | // Returns length 86 | T normalize() { 87 | T l = len(); 88 | if (l != 0) { 89 | *this *= 1.0f/l; 90 | } 91 | return l; 92 | } 93 | 94 | T volume() const { return x*y*z; } 95 | 96 | T min() const { return std::min({x,y,z}); } 97 | T max() const { return std::max({x,y,z}); } 98 | T minAbs() const { return std::min({std::abs(x), std::abs(y), std::abs(z)}); } 99 | T maxAbs() const { return std::max({std::abs(x), std::abs(y), std::abs(z)}); } 100 | 101 | unsigned minAxis() const { return (x<=y && x<=z ? 0 : 102 | y<=x && y<=z ? 1 : 2); } 103 | unsigned maxAxis() const { return (x>=y && x>=z ? 0 : 104 | y>=x && y>=z ? 1 : 2); } 105 | unsigned maxAbsAxis() const { return abs(*this).maxAxis(); } 106 | 107 | //------------------------------------------------------------------------------ 108 | 109 | //Vec3T& operator = (const Vec3T& v) = default; 110 | 111 | Vec3T operator + (const Vec3T& v) const; 112 | Vec3T& operator += (const Vec3T& v); 113 | 114 | Vec3T operator - (const Vec3T& v) const; 115 | Vec3T& operator -= (const Vec3T& v); 116 | 117 | #if 1 118 | inline friend Vec3T operator * (const Vec3T& v, T s) { return {v.x*s, v.y*s, v.z*s}; } 119 | #else 120 | template 121 | inline friend auto operator * (const Vec3T& v, F s) -> Vec3T { 122 | return Vec3T{v.x*s, v.y*s, v.z*s}; 123 | } 124 | #endif 125 | 126 | inline friend Vec3T operator * (T s, const Vec3T& v) { return {v.x*s, v.y*s, v.z*s}; } 127 | Vec3T& operator *= (T v); 128 | 129 | Vec3T operator * (const Vec3T& v) const; 130 | Vec3T& operator *= (const Vec3T& v); 131 | 132 | Vec3T operator / (T v) const; 133 | Vec3T& operator /= (T v); 134 | 135 | bool operator == (const Vec3T& v) const; 136 | bool operator != (const Vec3T& v) const; 137 | 138 | constexpr Vec3T operator -() const { return Vec3T(-x, -y, -z); } 139 | constexpr const Vec3T& operator +() const { return *this; } 140 | 141 | //------------------------------------------------------------------------------ 142 | 143 | friend T dot(const Vec3T& a, const Vec3T& b) { 144 | return a.x*b.x + a.y*b.y + a.z*b.z; 145 | } 146 | 147 | friend Vec3T cross(const Vec3T& lhs, const Vec3T& rhs) { 148 | return { 149 | lhs[1]*rhs[2] - lhs[2]*rhs[1], 150 | lhs[2]*rhs[0] - lhs[0]*rhs[2], 151 | lhs[0]*rhs[1] - lhs[1]*rhs[0] 152 | }; 153 | } 154 | 155 | // Scalar multiplication 156 | friend Vec3T mul(const Vec3T& lhs, const Vec3T& rhs) 157 | { 158 | return Vec3T(lhs[0]*rhs[0], lhs[1]*rhs[1], lhs[2]*rhs[2]); 159 | } 160 | 161 | friend Vec3T div(const Vec3T& lhs, const Vec3T& rhs) { 162 | return { 163 | lhs[0] / rhs[0], 164 | lhs[1] / rhs[1], 165 | lhs[2] / rhs[2] 166 | }; 167 | } 168 | 169 | friend T dist(const Vec3T& a, const Vec3T& b) { 170 | return (a-b).len(); 171 | } 172 | 173 | friend T distSq(const Vec3T& a, const Vec3T& b) { 174 | return (a-b).lenSq(); 175 | } 176 | 177 | //------------------------------------------------------------------------------ 178 | 179 | Vec3T untag() const { return Vec3T{x,y,z}; } 180 | }; 181 | 182 | 183 | //------------------------------------------------------------------------------ 184 | 185 | 186 | template 187 | inline std::ostream& operator<<(std::ostream& os, const Vec3T& v) 188 | { 189 | return os << "<" << v.x << ", " << v.y << ", " << v.z << ">"; 190 | } 191 | 192 | 193 | 194 | template 195 | const Vec3T Vec3T::Axes[3] = { 196 | {1,0,0}, {0,1,0}, {0,0,1} 197 | }; 198 | 199 | template 200 | inline Vec3T::Vec3T(T v) 201 | :x(v), y(v), z(v) 202 | { 203 | } 204 | 205 | template 206 | inline Vec3T::Vec3T(T X, T Y, T Z) 207 | :x(X), y(Y), z(Z) 208 | { 209 | } 210 | 211 | template 212 | inline Vec3T::Vec3T(const Vec2& v, T z) 213 | :x(v.x), y(v.y), z(z) 214 | { 215 | } 216 | 217 | template 218 | inline Vec3T Vec3T::operator + (const Vec3T& v) const 219 | { 220 | return Vec3T(x+v.x, y+v.y, z+v.z); 221 | } 222 | 223 | template 224 | inline Vec3T& Vec3T::operator += (const Vec3T& v) 225 | { 226 | x += v.x; 227 | y += v.y; 228 | z += v.z; 229 | 230 | return *this; 231 | } 232 | 233 | template 234 | inline Vec3T Vec3T::operator - (const Vec3T& v) const 235 | { 236 | return Vec3T(x-v.x, y-v.y, z-v.z); 237 | } 238 | 239 | template 240 | inline Vec3T& Vec3T::operator -= (const Vec3T& v) 241 | { 242 | x -= v.x; 243 | y -= v.y; 244 | z -= v.z; 245 | 246 | return *this; 247 | } 248 | 249 | /* 250 | template 251 | inline Vec3T operator * (const Vec3T& v, T s) { 252 | return {v.x*s, v.y*s, v.z*s}; 253 | } 254 | 255 | template 256 | inline Vec3T operator * (T s, const Vec3T& v) { 257 | return {v.x*s, v.y*s, v.z*s}; 258 | } 259 | */ 260 | 261 | template 262 | inline Vec3T& Vec3T::operator *= (T v) 263 | { 264 | x *= v; 265 | y *= v; 266 | z *= v; 267 | 268 | return *this; 269 | } 270 | 271 | template 272 | inline Vec3T Vec3T::operator * (const Vec3T& v) const 273 | { 274 | return Vec3T(x*v.x, y*v.y, z*v.z); 275 | } 276 | 277 | template 278 | inline Vec3T& Vec3T::operator *= (const Vec3T& v) 279 | { 280 | x *= v.x; 281 | y *= v.y; 282 | z *= v.z; 283 | 284 | return *this; 285 | } 286 | 287 | template 288 | inline Vec3T Vec3T::operator / (T v) const 289 | { 290 | return Vec3T(x/v, y/v, z/v); 291 | } 292 | 293 | template 294 | inline Vec3T& Vec3T::operator /= (T v) 295 | { 296 | x /= v; 297 | y /= v; 298 | z /= v; 299 | 300 | return *this; 301 | } 302 | 303 | template 304 | inline bool Vec3T::operator == (const Vec3T& v) const 305 | { 306 | return x == v.x && y == v.y && z == v.z; 307 | } 308 | 309 | template 310 | inline bool Vec3T::operator != (const Vec3T& v) const 311 | { 312 | return !((*this) == v); 313 | } 314 | 315 | //------------------------------------------------------------------------------ 316 | 317 | typedef unsigned char byte; 318 | 319 | typedef Vec3T Vec3; 320 | typedef Vec3T Vec3f; 321 | typedef Vec3T Vec3d; 322 | typedef Vec3T Vec3i; 323 | typedef Vec3T Vec3u; 324 | typedef Vec3T Vec3u16; 325 | typedef Vec3T Vec3u8; // e.g. RGB 326 | typedef Vec3T Vec3s8; // e.g. normal 327 | 328 | typedef std::vector Vec3List; 329 | 330 | static_assert(sizeof(Vec3u8) == 3*sizeof(byte), "Pack"); 331 | static_assert(sizeof(Vec3u16) == 3*sizeof(uint16_t), "Pack"); 332 | static_assert(sizeof(Vec3) == 3*sizeof(real), "Pack"); 333 | 334 | 335 | static_assert(std::is_pod::value, "is_pod"); 336 | static_assert(std::is_standard_layout::value, "is_standard_layout"); 337 | static_assert(std::is_trivial::value, "is_trivial"); 338 | static_assert(std::is_trivially_copyable::value, "is_trivially_copyable"); 339 | 340 | static_assert(std::is_pod::value, "is_pod"); 341 | static_assert(std::is_standard_layout::value, "is_standard_layout"); 342 | static_assert(std::is_trivial::value, "is_trivial"); 343 | static_assert(std::is_trivially_copyable::value, "is_trivially_copyable"); 344 | 345 | 346 | 347 | //------------------------------------------------------------------------------ 348 | // Utilities 349 | 350 | inline Vec3 normalized(const Vec3& v) 351 | { 352 | auto len = v.len(); 353 | if (len == 0) { 354 | return Zero; 355 | } else { 356 | return v / len; 357 | } 358 | } 359 | 360 | // Safe: normalize 0, return zero. 361 | inline Vec3 normalizedOrZero(const Vec3& v) 362 | { 363 | auto len = v.len(); 364 | if (isZero(len)) 365 | return Vec3(0); 366 | else 367 | return v / len; 368 | } 369 | 370 | inline Vec3 round(Vec3 v) { 371 | return Vec3(std::round(v.x), std::round(v.y), std::round(v.z)); 372 | } 373 | 374 | inline Vec3i roundI(Vec3 v) { 375 | return Vec3i(roundI(v.x), roundI(v.y), roundI(v.z)); 376 | } 377 | 378 | inline Vec3i floorI(Vec3 v) { 379 | return Vec3i(floorI(v.x), floorI(v.y), floorI(v.z)); 380 | } 381 | 382 | inline Vec3i ceilI(Vec3 v) { 383 | return Vec3i(ceilI(v.x), ceilI(v.y), ceilI(v.z)); 384 | } 385 | 386 | inline Vec3i sign(Vec3 v) { 387 | return Vec3i(sign(v.x), sign(v.y), sign(v.z)); 388 | } 389 | 390 | // Works like in glsl 391 | inline Vec3 reflect(const Vec3& d, const Vec3& n) { 392 | return d - 2*dot(d, n)*n; 393 | } 394 | 395 | inline bool isFinite(Vec3 v) 396 | { 397 | return isFinite(v.x) && isFinite(v.y) && isFinite(v.z); 398 | } 399 | 400 | template 401 | inline Vec3T abs(const Vec3T& v) { 402 | return {std::abs(v.x), std::abs(v.y), std::abs(v.z)}; 403 | } 404 | 405 | template 406 | inline Vec3T clamp(Vec3T v, Vec3T mn, Vec3T mx) { 407 | Vec3T ret; 408 | for (int d=0; d<3; ++d) 409 | ret[d] = math::clamp(v[d], mn[d], mx[d]); 410 | return ret; 411 | } 412 | 413 | // e.g. up is [0,0,1], we return things like [x,y,0] 414 | inline Vec3 projectOnto(Vec3 v, Vec3 up) { 415 | return v - up * dot(v, up); 416 | } 417 | } 418 | #endif 419 | -------------------------------------------------------------------------------- /src/test.cpp: -------------------------------------------------------------------------------- 1 | 2 | // To get CATCH to complie with -pedantic: 3 | 4 | // catch.hpp:4865:28: Private field 'm_impl' is not used 5 | #pragma clang diagnostic ignored "-Wunused-private-field" 6 | 7 | #define USE_EIGEN 0 8 | 9 | 10 | #if USE_EIGEN 11 | #pragma clang diagnostic ignored "-Wunused-parameter" 12 | #pragma clang diagnostic ignored "-Wunused-value" 13 | #pragma clang diagnostic ignored "-Wunused-variable" 14 | #include 15 | #endif 16 | 17 | 18 | #define CATCH_CONFIG_MAIN 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | using namespace math; 25 | //using namespace std; 26 | 27 | 28 | 29 | struct Plane { 30 | math::real dist; // Signed distance to closest feature 31 | math::Vec3 normal; // Unit-length normal 32 | }; 33 | 34 | 35 | Vec3 intersectPlanes(std::vector planes) 36 | { 37 | std::vector A; 38 | std::vector b; 39 | 40 | for (auto&& p : planes) { 41 | A.push_back(p.normal); 42 | b.push_back(p.dist); 43 | } 44 | 45 | return leastSquares(A.size(), A.data(), b.data()); 46 | } 47 | 48 | 49 | TEST_CASE( "solve3x3/overdetermined", "Testing 3x3 linear algebra solver" ) 50 | { 51 | auto v = intersectPlanes( 52 | { 53 | {1, {1,0,0}}, 54 | {2, {1,0,0}}, 55 | 56 | {2, {0,1,0}}, 57 | {3, {0,1,0}}, 58 | 59 | {3, {0,0,1}}, 60 | {4, {0,0,1}} 61 | } ); 62 | 63 | 64 | REQUIRE(dist(v, Vec3{1.5, 2.5, 3.5}) < 1e-5); 65 | } 66 | 67 | 68 | TEST_CASE( "solve3x3/underdetermined", "Testing 3x3 linear algebra solver" ) 69 | { 70 | /* 71 | When conturing we always have a weak push towards voxel center, 72 | in case of an underdetermines system. 73 | 74 | This way we can constrain less than 3 dimension without 75 | the solver blowing up. 76 | */ 77 | const real W = 0.01; // Weight of weak push towards [2,2,2] 78 | 79 | auto v = intersectPlanes( 80 | { 81 | {2*W, {W,0,0}}, 82 | {2*W, {0,W,0}}, 83 | {2*W, {0,0,W}}, 84 | 85 | {0.3*std::sqrt(2.0), normalized({1,1,0})}, 86 | } ); 87 | 88 | 89 | REQUIRE(dist(v, Vec3{0.3, 0.3, 2}) < 1e-3); 90 | } 91 | 92 | 93 | #if USE_EIGEN 94 | 95 | using namespace Eigen; 96 | 97 | typedef Matrix Mat_A; 98 | 99 | template 100 | void nukeZeros(Mat& m, real eps) 101 | { 102 | auto rows = m.rows(); 103 | auto cols = m.cols(); 104 | 105 | for (int c=0; c svd( A ); 117 | auto U = svd.matrixU(); 118 | auto S = svd.singularValues(); 119 | auto V = svd.matrixV(); 120 | 121 | std::cout << "SVD: " << std::endl; 122 | std::cout << "U: " << std::endl << U << std::endl << std::endl; 123 | std::cout << "S: " << std::endl << S << std::endl << std::endl; 124 | std::cout << "V: " << std::endl << V << std::endl << std::endl << std::endl; 125 | 126 | MatrixXd S_d = DiagonalMatrix( S ); 127 | 128 | //nukeZeros(S_d, 1e-2); 129 | 130 | MatrixXd A_R = U * S_d * V.transpose(); 131 | nukeZeros(A_R, 1e-6); 132 | 133 | std::cout << "Reconstructed: " << std::endl << A_R << std::endl << std::endl << std::endl; 134 | } 135 | 136 | 137 | void doQR(const Mat_A& A, const VectorXd& b) 138 | { 139 | 140 | auto qr = A.qr(); 141 | 142 | std::cout << "QR: " << std::endl; 143 | std::cout << "rank: " << qr.rank() << std::endl << std::endl; 144 | std::cout << "Q: " << std::endl << qr.matrixQ() << std::endl << std::endl << std::endl; 145 | std::cout << "R: " << std::endl << qr.matrixR() << std::endl << std::endl; 146 | } 147 | 148 | 149 | void inspectPlanes(std::vector planes) 150 | { 151 | std::cout << "--------------------------" << std::endl; 152 | 153 | const auto N = planes.size(); 154 | 155 | Mat_A A( N, 3 ); 156 | VectorXd b( N ); 157 | 158 | for (size_t r=0; r 5 | #include 6 | #include // fill_n 7 | 8 | 9 | namespace util 10 | { 11 | template 12 | class Array3D 13 | { 14 | public: 15 | typedef math::Vec3u pos_t; 16 | typedef math::Vec3u size_t; 17 | 18 | 19 | Array3D() {} 20 | 21 | Array3D(size_t size) : 22 | m_size(size), 23 | m_array(new T[m_size.volume()]) 24 | { 25 | 26 | } 27 | 28 | Array3D(size_t size, T fill) : 29 | m_size(size), 30 | m_array(new T[m_size.volume()]) 31 | { 32 | std::fill_n(m_array.get(), m_size.volume(), fill); 33 | } 34 | 35 | Array3D(Array3D&& other) : 36 | m_size(other.m_size), 37 | m_array(std::move(other.m_array)) 38 | { 39 | other.m_size = math::Zero; 40 | } 41 | 42 | 43 | size_t size() const { return m_size; } 44 | 45 | 46 | T& operator[](const pos_t& pos) { 47 | assert(pos.x < m_size.x && pos.y < m_size.y && pos.z < m_size.z); 48 | /* 49 | return m_array[ pos.z * m_size.x * m_size.y 50 | + pos.y * m_size.x 51 | + poz.x]; 52 | */ 53 | return m_array[((pos.z * m_size.y) + pos.y) * m_size.x + pos.x]; 54 | } 55 | 56 | const T& operator[](const pos_t& pos) const { 57 | assert(pos.x < m_size.x && pos.y < m_size.y && pos.z < m_size.z); 58 | return m_array[((pos.z * m_size.y) + pos.y) * m_size.x + pos.x]; 59 | } 60 | 61 | 62 | private: 63 | size_t m_size = math::Zero; 64 | std::unique_ptr m_array; 65 | }; 66 | 67 | 68 | /* 69 | Vec2u size = {2,3}; 70 | foreach2D(size, [](Vec2u pos) { 71 | ... 72 | }); 73 | 74 | */ 75 | template 76 | void foreach2D(math::Vec2u min, 77 | math::Vec2u max, 78 | const Fun& fun) 79 | { 80 | for (unsigned y=min.y; y 86 | void foreach2D(math::Vec2u max, const Fun& fun) { 87 | foreach2D({0,0}, max, fun); 88 | } 89 | 90 | 91 | /* 92 | Vec3u size = {2,3,4}; 93 | foreach3D(size, [](Vec3u pos) { 94 | ... 95 | }); 96 | 97 | */ 98 | template 99 | void foreach3D(math::Vec3u min, 100 | math::Vec3u max, 101 | const Fun& fun) 102 | { 103 | for (unsigned z=min.z; z 110 | void foreach3D(math::Vec3u max, const Fun& fun) { 111 | foreach3D({0,0,0}, max, fun); 112 | } 113 | } 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /src/vol/Contouring.cpp: -------------------------------------------------------------------------------- 1 | #include "Contouring.hpp" 2 | #include 3 | #include 4 | #include // cerr 5 | 6 | 7 | namespace vol 8 | { 9 | using namespace math; 10 | using namespace util; 11 | 12 | 13 | #pragma clang diagnostic ignored "-Wmissing-braces" 14 | typedef std::array Edge; 15 | 16 | 17 | const int NumCorners = 8; 18 | const int NumEdges = 3*4; 19 | 20 | const Vec3u Corners[NumCorners] = { 21 | {0,0,0}, {0,0,1}, {0,1,0}, {0,1,1}, 22 | {1,0,0}, {1,0,1}, {1,1,0}, {1,1,1}, 23 | }; 24 | 25 | // Indices into Corners: 26 | const Edge Edges[NumEdges] = { 27 | {0,1}, {0,2}, {0,4}, 28 | {1,3}, {1,5}, 29 | {2,3}, {2,6}, 30 | {3,7}, 31 | {4,5}, {4,6}, 32 | {5,7}, 33 | {6,7} 34 | }; 35 | 36 | 37 | struct VoxelInfo { 38 | int vertIx = -1; // -1 == no vertex 39 | }; 40 | 41 | 42 | void constructVertices(const Field& field, 43 | TriMesh& mesh, 44 | Array3D& voxels) 45 | { 46 | // Counters: 47 | int numClamped = 0; 48 | int numDistant = 0; 49 | 50 | // Reused for speed (no dynamic allocations in inner loop): 51 | std::vector planes; 52 | std::vector A; 53 | std::vector b; 54 | 55 | foreach3D(voxels.size(), [&](const Vec3u& p) 56 | { 57 | bool inside[NumCorners]; 58 | int numInside = 0; 59 | 60 | for (int ci=0; ci MaxCornerDist) { 102 | // Large distance change - produced by bad input 103 | // This will most likely cause a bad vertex, so skip this corner 104 | continue; 105 | } 106 | 107 | plane.dist = dot(plane.normal, Vec3(p_n)) - plane.dist; 108 | planes.push_back( plane ); 109 | } 110 | 111 | /* 112 | Add a weak 'push' towards the voxel center to improve conditioning. 113 | This is needed for any surface which is flat in at least one dimension, including a cylinder. 114 | 115 | We could do only as needed (when lastSquared have failed once), 116 | but the push is so weak that it makes little difference to the precision of the model. 117 | */ 118 | for (int ai=0; ai<3; ++ai) { 119 | Vec3 normal = CenterPush * Vec3::Axes[ai]; 120 | Vec3 pos = Vec3(p) + Vec3(0.5); 121 | planes.push_back( Plane{dot(normal, pos), normal} ); 122 | } 123 | 124 | for (auto&& p : planes) { 125 | A.push_back(p.normal); 126 | b.push_back(p.dist); 127 | } 128 | 129 | Vec3 vertex = leastSquares(A.size(), A.data(), b.data()); 130 | 131 | auto voxelCenter = Vec3(p) + Vec3(0.5); 132 | 133 | if (!isFinite(vertex)) { 134 | std::cerr << "leastSquares failed " << std::endl; 135 | vertex = voxelCenter; 136 | } 137 | 138 | auto clamped = clamp(vertex, Vec3(p), Vec3(p) + Vec3(1)); 139 | 140 | if (Clamp) 141 | { 142 | if (vertex != clamped) 143 | { 144 | vertex = clamped; 145 | ++numClamped; 146 | } 147 | } 148 | else if (dist(voxelCenter, vertex) > FarAway) { 149 | vertex = clamped; 150 | ++numDistant; 151 | } 152 | 153 | vox.vertIx = mesh.vecs.size(); 154 | mesh.vecs.push_back( vertex ); 155 | }); 156 | 157 | 158 | if (numClamped > 0) { 159 | std::cerr << "Clamped " << numClamped << " vertices to voxels" << std::endl; 160 | } 161 | 162 | if (numDistant > 0) { 163 | std::cerr << "Warning: " << numDistant << " vertices far outside voxel. They where clamped." << std::endl; 164 | } 165 | } 166 | 167 | 168 | void constructFaces(const Field& field, 169 | TriMesh& mesh, 170 | const Array3D& voxels) 171 | { 172 | // The edges leading to our far corner: 173 | const Edge FarEdges[3] = {{3,7}, {5,7}, {6,7}}; 174 | 175 | 176 | foreach3D(voxels.size(), [&](const Vec3u& p) 177 | { 178 | auto&& vox = voxels[p]; 179 | auto v0 = vox.vertIx; 180 | 181 | if (v0 == -1) { 182 | // Voxel has no vertex (fully contained or excluded) 183 | return; 184 | } 185 | 186 | bool inside[NumCorners]; 187 | 188 | for (int ci=0; ci voxels(field.size() - Vec3u(1), VoxelInfo{}); 247 | TriMesh mesh; 248 | 249 | constructVertices(field, mesh, voxels); 250 | constructFaces(field, mesh, voxels); 251 | 252 | 253 | return mesh; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/vol/Contouring.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CONTOURING_HPP 2 | #define CONTOURING_HPP 3 | 4 | #include 5 | #include 6 | 7 | 8 | namespace vol 9 | { 10 | /* For all p so that 11 | dot(p, normal) == dist 12 | */ 13 | struct Plane { 14 | math::real dist; // Signed distance to closest feature 15 | math::Vec3 normal; // Unit-length normal of the feature 16 | 17 | bool valid() const { return normal != math::Zero; } 18 | }; 19 | 20 | typedef util::Array3D Field; 21 | 22 | typedef std::array Triangle; 23 | 24 | // CW <-> CCW 25 | inline Triangle flip(Triangle t) { 26 | return {{ t[0], t[2], t[1] }}; 27 | } 28 | 29 | 30 | struct TriMesh { 31 | std::vector vecs; 32 | std::vector triangles; // Indices into vecs 33 | }; 34 | 35 | 36 | /* 37 | Implementation of: 38 | 39 | Dual Contouring on Hermite Data 40 | Proceedings of ACM SIGGRAPH, 2002 41 | Tao Ju, Frank Losasso, Scott Schaefer and Joe Warren 42 | http://www.cs.wustl.edu/~taoju/research/dualContour.pdf 43 | 44 | Will use the same resolution as the field for the dual contouring. 45 | 46 | No simplification. 47 | */ 48 | TriMesh dualContouring(const Field& field); 49 | } 50 | 51 | #endif 52 | --------------------------------------------------------------------------------