Skip to content

Commit 423213b

Browse files
committed
Created a library that can reconstruct a mesh from an sdf using the gradient of the sdf as vertex normals. The mesh can then be saved to an obj file.
0 parents  commit 423213b

17 files changed

+774
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bin
2+
html/
3+
latex/

CMakeLists.txt

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
cmake_minimum_required (VERSION 3.0)
2+
project (MeshReconstruction)
3+
4+
set(CMAKE_CXX_STANDARD 11)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
# Parallel compilation.
8+
if(WIN32)
9+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
10+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250")
11+
endif()
12+
13+
set(CMAKE_DEBUG_POSTFIX "d")
14+
15+
add_subdirectory(lib)
16+
add_subdirectory(demo)

Readme.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# MeshReconstruction
2+
3+
This is a small library that can reconstruct a triangle mesh from a <a href="http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm">signed distance function</a> using the <a href="https://en.wikipedia.org/wiki/Marching_cubes">Marching Cubes algorithm</a> and export it to a file in the <a href="https://de.wikipedia.org/wiki/Wavefront_OBJ">obj format</a>.
4+
5+
The library is self-contained and has no dependencies. The library is fast due to precomputed lookup tables and a narrow-band approach which excludes a lot of marching cubes that are far away from the surface.
6+
7+
The library requires C++14 and has been tested under Visual Studio 2017 and Windows 10 but should port to other systems without major problems.
8+
9+
The library can be used under the terms of the MIT License.
10+
11+
<p align="center">
12+
<img src="overview.png" width="400" alt="Overview">
13+
</p>

demo/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include_directories("." ${PROJECT_SOURCE_DIR}/lib)
2+
add_executable(Demo main.cpp)
3+
target_link_libraries (Demo MeshReconstruction)

demo/main.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <MeshReconstruction.h>
2+
#include <IO.h>
3+
4+
using namespace MeshReconstruction;
5+
6+
int main()
7+
{
8+
auto sphereSdf = [](Vec3 const& pos)
9+
{
10+
auto const Radius = 1.0;
11+
return pos.Norm() - Radius;
12+
};
13+
14+
Rect3 domain;
15+
domain.min = { -1.1, -1.1, -1.1 };
16+
domain.size = {2.2, 2.2, 2.2};
17+
18+
Vec3 cubeSize{ 0.05, 0.05, 0.05 };
19+
20+
auto mesh = MarchCube(sphereSdf, domain, cubeSize);
21+
WriteObjFile(mesh, "sphere.obj");
22+
}

lib/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
add_library(MeshReconstruction MeshReconstruction.h MeshReconstruction.cpp Cube.h Cube.cpp DataStructs.h IO.h IO.cpp Triangulation.h Triangulation.cpp)
2+
3+
install(TARGETS MeshReconstruction
4+
RUNTIME DESTINATION bin
5+
LIBRARY DESTINATION lib
6+
ARCHIVE DESTINATION lib)
7+
8+
install(DIRECTORY .
9+
DESTINATION .
10+
FILES_MATCHING PATTERN "*.h")

lib/Cube.cpp

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include "Cube.h"
2+
3+
using namespace MeshReconstruction;
4+
5+
namespace
6+
{
7+
// Cube has 8 vertices. Each vertex can have positive or negative sign.
8+
// 2^8 = 256 possible configurations mapped to intersected edges in each case.
9+
// The 12 edges are numbered as 1, 2, 4, ..., 2048 and are stored as a 12-bit bitstring for each configuration.
10+
const int signConfigToIntersectedEdges[256] = {
11+
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
12+
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
13+
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
14+
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
15+
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
16+
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
17+
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
18+
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
19+
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
20+
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
21+
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
22+
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
23+
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
24+
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
25+
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
26+
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
27+
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
28+
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
29+
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
30+
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
31+
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
32+
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
33+
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
34+
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
35+
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
36+
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
37+
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
38+
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
39+
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
40+
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
41+
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
42+
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 };
43+
44+
struct Edge
45+
{
46+
int edgeFlag : 12; // flag: 1, 2, 4, ... 2048
47+
int vert0; // 0-7
48+
int vert1; // 0-7
49+
};
50+
51+
const Edge edges[12] =
52+
{
53+
{ 1, 0, 1 }, // edge 0
54+
{ 2, 1, 2 }, // edge 1
55+
{ 4, 2, 3 }, // ...
56+
{ 8, 3, 0 },
57+
{ 16, 4, 5 },
58+
{ 32, 5, 6 },
59+
{ 64, 6, 7 },
60+
{ 128, 7, 4 },
61+
{ 256, 0, 4 },
62+
{ 512, 1, 5 },
63+
{ 1024, 2, 6 },
64+
{ 2048, 3, 7 } // edge 11
65+
};
66+
}
67+
68+
Vec3 Cube::LerpVertex(double isoLevel, int i1, int i2) const
69+
{
70+
auto const Eps = 1e-5;
71+
auto const v1 = sdf[i1];
72+
auto const v2 = sdf[i2];
73+
auto const& p1 = pos[i1];
74+
auto const& p2 = pos[i2];
75+
76+
if (abs(isoLevel - v1) < Eps) return p1;
77+
if (abs(isoLevel - v2) < Eps) return p2;
78+
if (abs(v1 - v2) < Eps) return p1;
79+
80+
auto mu = (isoLevel - v1) / (v2 - v1);
81+
return p1 + (p2 - p1)*mu;
82+
}
83+
84+
Cube::Cube(Rect3 const& space, Fun3s const& sdf)
85+
{
86+
auto mx = space.min.x;
87+
auto my = space.min.y;
88+
auto mz = space.min.z;
89+
90+
auto sx = space.size.x;
91+
auto sy = space.size.y;
92+
auto sz = space.size.z;
93+
94+
pos[0] = space.min;
95+
pos[1] = { mx + sx, my, mz };
96+
pos[2] = { mx + sx, my, mz + sz };
97+
pos[3] = { mx, my, mz + sz };
98+
pos[4] = { mx, my + sy, mz };
99+
pos[5] = { mx + sx, my + sy, mz };
100+
pos[6] = { mx + sx, my + sy, mz + sz };
101+
pos[7] = { mx, my + sy, mz + sz };
102+
103+
for (auto i = 0; i < 8; ++i)
104+
{
105+
auto sd = sdf(pos[i]);
106+
if (sd == 0) sd += 1e-6;
107+
this->sdf[i] = sd;
108+
}
109+
}
110+
111+
int Cube::SignConfig(double isoLevel) const
112+
{
113+
auto edgeIndex = 0;
114+
115+
for (auto i = 0; i < 8; ++i)
116+
{
117+
if (sdf[i] < isoLevel)
118+
{
119+
edgeIndex |= 1 << i;
120+
}
121+
}
122+
123+
return edgeIndex;
124+
}
125+
126+
IntersectInfo Cube::Intersect(double iso) const
127+
{
128+
// idea:
129+
// from signs at 8 corners of cube a sign configuration (256 possible ones) is computed
130+
// this configuration can be used to index into a table that tells which of the 12 edges are intersected
131+
// find vertices adjacent to edges and interpolate cut vertex and store it in IntersectionInfo object
132+
133+
IntersectInfo intersect;
134+
intersect.signConfig = SignConfig(iso);
135+
136+
for (auto e = 0; e<12; ++e)
137+
{
138+
if (signConfigToIntersectedEdges[intersect.signConfig] & edges[e].edgeFlag)
139+
{
140+
auto v0 = edges[e].vert0;
141+
auto v1 = edges[e].vert1;
142+
auto vert = LerpVertex(iso, v0, v1);
143+
intersect.edgeVertIndices[e] = vert;
144+
}
145+
}
146+
147+
return intersect;
148+
}

lib/Cube.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
#include "DataStructs.h"
3+
4+
namespace MeshReconstruction
5+
{
6+
struct IntersectInfo
7+
{
8+
// 0 - 255
9+
int signConfig;
10+
11+
// If it exists, vertex on edge i is stored at position i.
12+
// For edge numbering and location see numberings.png.
13+
std::array<Vec3, 12> edgeVertIndices;
14+
};
15+
16+
class Cube
17+
{
18+
Vec3 pos[8];
19+
double sdf[8];
20+
21+
Vec3 LerpVertex(double isoLevel, int i1, int i2) const;
22+
int SignConfig(double isoLevel) const;
23+
24+
public:
25+
Cube(Rect3 const& space, Fun3s const& sdf);
26+
27+
// Find the vertices where the surface intersects the cube.
28+
IntersectInfo Intersect(double isoLevel = 0) const;
29+
};
30+
}

lib/DataStructs.h

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
#include <vector>
3+
#include <array>
4+
#include <functional>
5+
6+
namespace MeshReconstruction
7+
{
8+
struct Vec3
9+
{
10+
double x, y, z;
11+
12+
Vec3 operator+(Vec3 const& o) const
13+
{
14+
return { x + o.x, y + o.y, z + o.z };
15+
}
16+
17+
Vec3 operator-(Vec3 const& o) const
18+
{
19+
return { x - o.x, y - o.y, z - o.z };
20+
}
21+
22+
Vec3 operator*(double c) const
23+
{
24+
return { c*x, c*y, c*z };
25+
}
26+
27+
double Norm() const
28+
{
29+
return sqrt(x*x + y*y + z*z);
30+
}
31+
32+
Vec3 Normalized() const
33+
{
34+
auto n = Norm();
35+
return { x / n, y / n, z / n };
36+
}
37+
};
38+
39+
struct Rect3
40+
{
41+
Vec3 min;
42+
Vec3 size;
43+
};
44+
45+
using Triangle = std::array<int, 3>;
46+
47+
struct Mesh
48+
{
49+
std::vector<Vec3> vertices;
50+
std::vector<Triangle> triangles;
51+
std::vector<Vec3> vertexNormals;
52+
};
53+
54+
using Fun3s = std::function<double(Vec3 const&)>;
55+
using Fun3v = std::function<Vec3(Vec3 const&)>;
56+
}

lib/IO.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include "IO.h"
2+
using namespace std;
3+
4+
void MeshReconstruction::WriteObjFile(Mesh const& mesh, string const& fileName)
5+
{
6+
// FILE faster than streams.
7+
8+
FILE* file;
9+
auto status = fopen_s(&file, fileName.c_str(), "w");
10+
if (status != 0)
11+
{
12+
throw runtime_error("Could not write obj file.");
13+
}
14+
15+
// write stats
16+
fprintf(file, "# %d vertices, %d triangles\n\n",
17+
static_cast<int>(mesh.vertices.size()),
18+
static_cast<int>(mesh.triangles.size()));
19+
20+
// vertices
21+
for (auto vi = 0; vi < mesh.vertices.size(); ++vi)
22+
{
23+
auto const& v = mesh.vertices.at(vi);
24+
fprintf(file, "v %f %f %f\n", v.x, v.y, v.z);
25+
}
26+
27+
// vertex normals
28+
fprintf(file, "\n");
29+
for (auto ni = 0; ni < mesh.vertices.size(); ++ni)
30+
{
31+
auto const& vn = mesh.vertexNormals.at(ni);
32+
fprintf(file, "vn %f %f %f\n", vn.x, vn.y, vn.z);
33+
}
34+
35+
// triangles (1-based)
36+
fprintf(file, "\n");
37+
for (auto ti = 0; ti < mesh.triangles.size(); ++ti)
38+
{
39+
auto const& t = mesh.triangles.at(ti);
40+
fprintf(file, "f %d//%d %d//%d %d//%d\n",
41+
t[0] + 1, t[0] + 1,
42+
t[1] + 1, t[1] + 1,
43+
t[2] + 1, t[2] + 1);
44+
}
45+
46+
fclose(file);
47+
}

lib/IO.h

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
#include <string>
3+
#include "DataStructs.h"
4+
5+
namespace MeshReconstruction
6+
{
7+
/// Writes a mesh to a file in <a href="https://de.wikipedia.org/wiki/Wavefront_OBJ">obj format</a>.
8+
void WriteObjFile(Mesh const& mesh, std::string const& file);
9+
}

0 commit comments

Comments
 (0)