├── README.md
├── mesh.cpp
├── mesh.hpp
├── mesh_generators.cpp
├── mesh_generators.hpp
└── sample_meshes
├── bagelklein.mesh
├── kleinbottle.mesh
├── sphere.mesh
└── torus.mesh
/README.md:
--------------------------------------------------------------------------------
1 | # paramesh
2 |
3 | 
4 | _The bagel immersion of a Klein bottle_
5 |
6 | Generate a triangle mesh from a parametric equation. This is done by taking the surface and "cutting it up" into "rings" and "slices". Two angles, theta and phi step along the rings and the slices to form quads on the mesh, which are then broken up into two triangles.
7 |
8 | For example, for a sphere we'd break it up like this:
9 |
10 | 
11 |
12 | More rings and slices will mean more triangles in the mesh. 32 rings and 32 slices seems to work pretty well for building a generally smooth mesh.
13 |
14 | Thanks to Nicolas Guillemot (@nlguillemot) for pointing me towards this method for triangulating a sphere. I'm glad it generalizes so well to other surfaces!
15 |
16 |
17 | #### Requirements
18 | * Some sort of vector math library. I used glm. You can easily sub your own in by editing mesh.hpp. (I'll make this more standalone when I have time; it's extracted from a much larger assignment, and it made more sense to use a separate library there!)
19 | * A C++ compiler that supports at least C++11.
20 |
21 |
22 | #### Usage
23 | mesh_generators.cpp contains some sample functions for generating meshes. You can easily create your own mesh-generating function (or a general function) by using those functions as a model.
24 |
25 | ###### Making a Mesh-Generating Function
26 | 1. Create a list of vertices and triangles for the mesh
27 | 2. Create a lambda vec3 f(float u, v)
where u
, v
define angles in radians. u
=theta is the angle between horizontal cuts in the surface, and v
=phi is the angle between vertical cuts in the surface
28 | 3. Call GeneratePoints
with your lambda
29 | 4. Call GenerateFaces
to generate the triangles for the mesh
30 | 5. Call GenerateVertexNormals
31 | 6. Copy over the number of vertices, number of triangles, and the lists to the mesh
32 |
33 | After, you can use the resulting TriangleMesh
type directly, or you can output to a file by calling WriteMesh
.
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/mesh.cpp:
--------------------------------------------------------------------------------
1 | #include "mesh.hpp"
2 |
3 | /*
4 | * Read in a mesh file and initialize a triangleMesh using it.
5 | * 1st 4B of file: # vertices
6 | * 2nd 4B of file: # triangles
7 | * Next # vertices * sizeof(meshVertex) B: vertex data
8 | * Next # triangles * sizeof(meshTriangle) B: triangle data
9 | */
10 | void ReadMesh(TriangleMesh &tm, const string &fname)
11 | {
12 | cout << "Loading mesh file '" << fname << "'...\n";
13 | FILE* meshFile = fopen(fname.c_str(), "rb");
14 | if (!meshFile) {
15 | cerr << "Could not open '" << fname << "'.\n";
16 | exit(-1);
17 | }
18 |
19 | // Read in the number of vertices and then the number of triangles
20 | if (fread(&tm.nv, 4, 1, meshFile) < 1) {
21 | cerr << "ERROR: Could not load mesh file. \n";
22 | exit(-1);
23 | }
24 |
25 | if (fread(&tm.nt, 4, 1, meshFile) < 1) {
26 | cerr << "ERROR: Could not load mesh file. \n";
27 | exit(-1);
28 | }
29 |
30 | // Allocate memory for the vertex and triangle arrays
31 | tm.vertexArray = (MeshVertex*) malloc(tm.nv * sizeof(MeshVertex));
32 | if (!tm.vertexArray) {
33 | cerr << "ERROR: Could not allocate memory for vertexArray. (number of vertices = " << tm.nv << ")\n";
34 | exit(-1);
35 | }
36 |
37 | tm.triangleArray = (MeshTriangle*) malloc(tm.nt * sizeof(MeshTriangle));
38 | if (!tm.triangleArray) {
39 | cerr << "ERROR: Could not allocate memory for triangleArray. (number of triangles = " << tm.nt << ")\n";
40 | exit(-1);
41 | }
42 |
43 | // Read in the vertexArray and the triangleArrays
44 | if (fread(tm.vertexArray, sizeof(uint32_t), tm.nv * 8, meshFile) < (tm.nv * 8)) {
45 | cerr << "ERROR: Could not load mesh file. \n";
46 | exit(-1);
47 | }
48 |
49 | if (fread(tm.triangleArray, sizeof(uint32_t), tm.nt * 3, meshFile) < (tm.nt * 3)) {
50 | cerr << "ERROR: Could not load mesh file. \n";
51 | exit(-1);
52 | }
53 |
54 | fclose(meshFile);
55 | }
56 |
57 | /* Write a triangle mesh to a file */
58 | void WriteMesh(const TriangleMesh &tm, const string &fname)
59 | {
60 | FILE* meshFile = fopen(fname.c_str(), "wb");
61 | if (!meshFile) {
62 | cerr << "Could not open '" << fname << "' for writing.\n";
63 | exit(-1);
64 | }
65 |
66 | // Read in the number of vertices and then the number of triangles
67 | if (fwrite(&tm.nv, 4, 1, meshFile) < 1) {
68 | cerr << "ERROR: Could not write to mesh file. \n";
69 | exit(-1);
70 | }
71 |
72 | if (fwrite(&tm.nt, 4, 1, meshFile) < 1) {
73 | cerr << "ERROR: Could not write to mesh file. \n";
74 | exit(-1);
75 | }
76 |
77 | // Read in the vertexArray and the triangleArrays
78 | if (fwrite(tm.vertexArray, sizeof(uint32_t), tm.nv * 8, meshFile) < (tm.nv * 8)) {
79 | cerr << "ERROR: Could not write to mesh file. \n";
80 | exit(-1);
81 | }
82 |
83 | if (fwrite(tm.triangleArray, sizeof(uint32_t), tm.nt * 3, meshFile) < (tm.nt * 3)) {
84 | cerr << "ERROR: Could not write to mesh file. \n";
85 | exit(-1);
86 | }
87 |
88 | fclose(meshFile);
89 | }
90 |
91 | /*
92 | * Generate points on a surface by stepping along it in discrete horizontal
93 | * and vertical steps using two angles, theta and phi. This results in points
94 | * on the surface coming out as squares, which can then be used to triangulate
95 | * the surface. The angle theta steps along the slices (vertical cuts), and the
96 | * angle phi steps along the rings (horizontal cuts).
97 | * ---------------------------- Parameters ----------------------------
98 | * rings: The number of horizontal cuts to make in the surface
99 | * slices: The number of vertical cuts to make in the surface
100 | * pt_fn: The parametric function for the surface
101 | * pstep: The step size for the angle phi
102 | * tstep: The step size for the angle theta
103 | */
104 | void GeneratePoints(vector &vlist,
105 | const int &rings, const int &slices,
106 | function pt_fn,
107 | const float pstep, const float tstep)
108 | {
109 | float theta = 0.0f;
110 | float umap = 1.0f/((float)rings * pstep);
111 | float vmap = 1.0f/((float)slices * tstep);
112 |
113 | for (int i = 0; i <= slices; i++) {
114 | float phi = 0.0f;
115 | for (int j = 0; j <= rings; j++) {
116 | MeshVertex v;
117 | vec3 pt = pt_fn(theta, phi); // get pt on surface
118 | v.position[0] = pt.x;
119 | v.position[1] = pt.y;
120 | v.position[2] = pt.z;
121 |
122 | // map texture coords to surface
123 | v.tex_coord[0] = phi * umap;
124 | v.tex_coord[1] = theta * vmap;
125 | vlist.push_back(v);
126 | phi += pstep;
127 | }
128 | theta += tstep;
129 | }
130 | }
131 |
132 | /*
133 | * Compute the triangles for the surface
134 | *
135 | * Memory for tlist is laid out like
136 | * <0...nrings><0...nrings><0...nrings>...<0...nrings>, where each <...>
137 | * corresponds to a single slice.
138 | * From this, we can figure out what the quad is that we formed for the surface
139 | * and split it into two triangles.
140 | */
141 | void GenerateFaces(vector &tlist, const int &nrings, const int &nslices)
142 | {
143 | for (int pt = 0; pt < nslices * (nrings+1); pt += (nrings+1)) {
144 | for (int curr_ring = 0; curr_ring <= nrings; curr_ring++) {
145 |
146 | /* form a quad:
147 | * pt+next_ring--------------pt+next_ring+nrings
148 | * | |
149 | * | |
150 | * | |
151 | * pt+curr_ring--------------pt+curr_ring+nrings
152 | */
153 |
154 | MeshTriangle t1, t2;
155 | int next_ring = (curr_ring+1) % (nrings+1);
156 |
157 | /*
158 | * triangle formed by
159 | * i0 = pt+curr_ring, i1 = pt+next_ring, i2 = pt+curr_ring+nrings
160 | * i1
161 | * | \
162 | * | \
163 | * i0---i2
164 | */
165 | t1.i0 = pt + curr_ring;
166 | t1.i1 = pt + next_ring;
167 | t1.i2 = pt + curr_ring + (nrings+1);
168 | tlist.push_back(t1);
169 |
170 | /*
171 | * in this triangle, i0 = prev triangle's i1
172 | * triangle formed by
173 | * i0 = pt+next_ring, i1 = pt+next_ring+nrings, i2 = pt+curr_ring+nrings
174 | * i0 ---i1
175 | * \ |
176 | * \ |
177 | * i2
178 | */
179 | t2.i0 = t1.i1;
180 | t2.i1 = pt + next_ring + (nrings+1);
181 | t2.i2 = t1.i2;
182 | tlist.push_back(t2);
183 | }
184 | }
185 | }
186 |
187 | /*
188 | * Compute the vertex normals for a surface as the weighted average of the
189 | * vertex's incident face normals. Incident normals contribute to the average
190 | * if the cosine of the angle between them is > 0 (they aren't perpendicular).
191 | * The contribution of each triangle other than the first one the vertex appears
192 | * in is weighted by the area of that triangle.
193 | */
194 | void GenerateVertexNormals(vector &vlist, const vector &tlist)
195 | {
196 | // helper lambda to find the area of a triangle
197 | auto area = [&vlist](MeshTriangle t)
198 | {
199 | vec3 p0 = vec3(vlist[t.i0].vx, vlist[t.i0].vy, vlist[t.i0].vz);
200 | vec3 p1 = vec3(vlist[t.i1].vx, vlist[t.i1].vy, vlist[t.i1].vz);
201 | vec3 p2 = vec3(vlist[t.i2].vx, vlist[t.i2].vy, vlist[t.i2].vz);
202 |
203 | // every triangle is ~roughly~ a right triangle
204 | return 0.5f * length(p2-p0) * length(p1-p0);
205 | };
206 |
207 | // helper lambda to get a normal for a triangle
208 | auto get_normal = [&vlist](int i0, int i1, int i2)
209 | {
210 | vec3 p0 = vec3(vlist[i0].vx, vlist[i0].vy, vlist[i0].vz);
211 | vec3 p1 = vec3(vlist[i1].vx, vlist[i1].vy, vlist[i1].vz);
212 | vec3 p2 = vec3(vlist[i2].vx, vlist[i2].vy, vlist[i2].vz);
213 |
214 | return normalize(cross(p1-p0, p2-p0));
215 | };
216 |
217 | // loop over vertices and find the vertex normal for the vertex
218 | #pragma omp parallel for
219 | for (int v = 0; v < vlist.size(); v++) {
220 |
221 | // find the first triangle the vertex appears in
222 | for (int i = 0; i < tlist.size(); i++) {
223 | if ((tlist[i].i0 != v) &&
224 | (tlist[i].i1 != v) &&
225 | (tlist[i].i2 != v)) continue;
226 |
227 | vec3 n = get_normal(tlist[i].i0, tlist[i].i1, tlist[i].i2);
228 |
229 | // average n with every other triangle that the vertex appears in
230 | for (int j = 0; j < tlist.size(); j++) {
231 | if (i == j) continue;
232 | if ((tlist[j].i0 != v) && (tlist[j].i1 != v) && (tlist[j].i2 != v)) continue;
233 |
234 | vec3 q = get_normal(tlist[j].i0, tlist[j].i1, tlist[j].i2);
235 | if (dot(n, q) > 0.1f) {
236 | n += area(tlist[j]) * q;
237 | }
238 | }
239 |
240 | n = normalize(n);
241 | vlist[v].nx = n.x;
242 | vlist[v].ny = n.y;
243 | vlist[v].nz = n.z;
244 |
245 | break; // move to the next vertex
246 | }
247 | }
248 | }
249 |
250 | /*
251 | * Computing the verted normals of a sphere can be done easily by just
252 | * taking point-origin for each point on the sphere.
253 | */
254 | void GenerateSphereVertexNormals(vector &vlist)
255 | {
256 | for (int i = 0; i < vlist.size(); i++)
257 | {
258 | // get the normal for the pt
259 | vec3 n = normalize(vec3(
260 | vlist[i].vx,
261 | vlist[i].vy,
262 | vlist[i].vz
263 | ));
264 |
265 | // set the normal
266 | vlist[i].nx = n.x;
267 | vlist[i].ny = n.y;
268 | vlist[i].nz = n.z;
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/mesh.hpp:
--------------------------------------------------------------------------------
1 | #ifndef MESH_HPP
2 | #define MESH_HPP
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include "glm/glm.hpp"
11 |
12 | using namespace std;
13 | using namespace glm;
14 | const float pi = 3.1415926f;
15 |
16 | /*
17 | * A vertex read from a .mesh file.
18 | */
19 | struct MeshVertex {
20 |
21 | // Position coordinates
22 | union {
23 | float position[3];
24 | struct {
25 | float vx, vy, vz;
26 | };
27 | };
28 |
29 | // Texture coordinates
30 | union {
31 | float tex_coord[2];
32 | struct {
33 | float tx, ty;
34 | };
35 | };
36 |
37 | // Normal coordinates
38 | union {
39 | float normal[3]; // normal
40 | struct {
41 | float nx, ny, nz;
42 | };
43 | };
44 | };
45 |
46 | /*
47 | * Triangle in a mesh from a .mesh file. Note that triangles are
48 | * stored in CW order. The meshTriangle struct stores the index of
49 | * a vertex in the vertexArray of its triangleMesh.
50 | */
51 | struct MeshTriangle {
52 | uint32_t i0; // ID of first vertex in vertexArray
53 | uint32_t i1; // ID of second vertex in vertexArray
54 | uint32_t i2; // ID of third vertex in vertexArray
55 | };
56 |
57 | struct TriangleMesh
58 | {
59 | uint32_t nv; // number of vertices
60 | uint32_t nt; // number of triangles
61 | MeshVertex* vertexArray; // contains every vertex in the mesh
62 | MeshTriangle* triangleArray; // contains every triangle in the mesh
63 |
64 | TriangleMesh()
65 | : nv(0), nt(0), vertexArray(nullptr), triangleArray(nullptr)
66 | {}
67 |
68 | ~TriangleMesh()
69 | {
70 | if (vertexArray) { free(vertexArray); }
71 | if (triangleArray) { free(triangleArray); }
72 | }
73 | };
74 |
75 | void ReadMesh(TriangleMesh &tm, const string &fname);
76 | void WriteMesh(const TriangleMesh &tm, const string &fname);
77 | void GeneratePoints(vector &vlist,
78 | const int &rings, const int &slices,
79 | function pt_fn,
80 | const float pstep, const float tstep);
81 | void GenerateFaces(vector &tlist, const int &nrings, const int &nslices);
82 | void GenerateVertexNormals(vector &vlist, const vector &tlist);
83 | void GenerateSphereVertexNormals(vector &vlist);
84 |
85 | #endif // MESH_HPP
86 |
--------------------------------------------------------------------------------
/mesh_generators.cpp:
--------------------------------------------------------------------------------
1 | /* Some example mesh functions */
2 | #include "mesh_generators.hpp"
3 |
4 | TriangleMesh GenerateParametricSphereMesh(const int &rings, const int &slices)
5 | {
6 | vector tlist; // contains triangles for mesh
7 | vector vlist; // contains vertices for mesh
8 |
9 | // parametric eq for a point on a sphere given u = theta, v = phi
10 | auto sphere_pt = [](float u, float v) {
11 | return vec3(
12 | sin(u)*cos(v),
13 | cos(u),
14 | -sin(u)*sin(v));
15 | };
16 |
17 | GeneratePoints(vlist, rings, slices, sphere_pt, 2.0f*pi/(float)rings, pi/(float)slices);
18 | GenerateFaces(tlist, rings, slices);
19 | GenerateSphereVertexNormals(vlist);
20 |
21 | TriangleMesh sphere;
22 | sphere.nv = vlist.size();
23 | sphere.nt = tlist.size();
24 | sphere.vertexArray = (MeshVertex*) malloc(vlist.size() * sizeof(MeshVertex));
25 | sphere.triangleArray = (MeshTriangle*) malloc(tlist.size() * sizeof(MeshTriangle));
26 |
27 | copy(vlist.begin(), vlist.begin() + vlist.size(), sphere.vertexArray);
28 | copy(tlist.begin(), tlist.begin() + tlist.size(), sphere.triangleArray);
29 |
30 | return sphere;
31 | }
32 |
33 |
34 | TriangleMesh GenerateParametricKleinMesh(const int &rings, const int &slices)
35 | {
36 | vector tlist; // contains triangles for mesh
37 | vector vlist; // contains vertices for mesh
38 |
39 | // Equation for a point on a Klein bottle given u = theta, v = phi
40 | auto klein_pt = [](float u, float v) {
41 | float x, y, z;
42 |
43 | if (u < pi) {
44 | x = 3.0f*cos(u)*(1.0f+sin(u))+(2.0f*(1.0f-cos(u)/2.0f))*cos(u)*cos(v);
45 | z = -8.0f*sin(u)-2.0f*(1.0f-cos(u)/2.0f)*sin(u)*cos(v);
46 | }
47 |
48 | else {
49 | x = 3.0f*cos(u)*(1.0f+sin(u))+(2.0f*(1.0f-cos(u)/2.0f))*cos(v+pi);
50 | z = -8.0f*sin(u);
51 | }
52 |
53 | y = -2.0f*(1.0f-cos(u)/2.0f)*sin(v);
54 | return vec3(x,y,z);
55 | };
56 |
57 | GeneratePoints(vlist, rings, slices, klein_pt, 2.0f*pi/(float)rings, 2.0f*pi/(float)slices);
58 | GenerateFaces(tlist, rings, slices);
59 | GenerateVertexNormals(vlist, tlist);
60 |
61 | TriangleMesh klein;
62 | klein.nv = vlist.size();
63 | klein.nt = tlist.size();
64 | klein.vertexArray = (MeshVertex*) malloc(vlist.size() * sizeof(MeshVertex));
65 | klein.triangleArray = (MeshTriangle*) malloc(tlist.size() * sizeof(MeshTriangle));
66 |
67 | copy(vlist.begin(), vlist.begin() + vlist.size(), klein.vertexArray);
68 | copy(tlist.begin(), tlist.begin() + tlist.size(), klein.triangleArray);
69 |
70 | return klein;
71 | }
72 |
73 |
74 | TriangleMesh GenerateParametricTorusMesh(const int &rings, const int &slices)
75 | {
76 | vector tlist; // contains triangles for mesh
77 | vector vlist; // contains vertices for mesh
78 |
79 | auto torus_pt = [](float u, float v) {
80 | float x, y, z;
81 | x = (1 + 0.5*cos(u)) * cos(v);
82 | y = (1 + 0.5*cos(u)) * sin(v);
83 | z = 0.5 * sin(u);
84 | return vec3(x,y,z);
85 | };
86 |
87 | GeneratePoints(vlist, rings, slices, torus_pt, 2.0f*pi/(float)rings, 2.0f*pi/(float)rings);
88 | GenerateFaces(tlist, rings, slices);
89 | GenerateVertexNormals(vlist, tlist);
90 |
91 | TriangleMesh torus;
92 | torus.nv = vlist.size();
93 | torus.nt = tlist.size();
94 | torus.vertexArray = (MeshVertex*) malloc(vlist.size() * sizeof(MeshVertex));
95 | torus.triangleArray = (MeshTriangle*) malloc(tlist.size() * sizeof(MeshTriangle));
96 |
97 | copy(vlist.begin(), vlist.begin() + vlist.size(), torus.vertexArray);
98 | copy(tlist.begin(), tlist.begin() + tlist.size(), torus.triangleArray);
99 |
100 | return torus;
101 | }
102 |
103 |
104 | TriangleMesh GenerateBagelKleinMesh(const int &rings, const int &slices)
105 | {
106 | vector tlist; // contains triangles for mesh
107 | vector vlist; // contains vertices for mesh
108 |
109 | auto klein_pt = [](float u, float v) {
110 | float x, y, z;
111 | x = (1.0f + cos(v/2.0f)*sin(u) - sin(v/2.0f)*sin(2.0f*u)) * cos(v);
112 | y = (1 + cos(v/2.0f)*sin(u) - sin(v/2.0f)*sin(2.0f*u)) * sin(v);
113 | z = sin(v/2.0f)*sin(u) + cos(v/2.0f)*sin(2.0f*u);
114 | return vec3(x,y,z);
115 | };
116 |
117 | GeneratePoints(vlist, rings, slices, klein_pt, 2.0f*pi/(float)rings, 2.0f*pi/(float)rings);
118 | GenerateFaces(tlist, rings, slices);
119 | GenerateVertexNormals(vlist, tlist);
120 |
121 | TriangleMesh klein;
122 | klein.nv = vlist.size();
123 | klein.nt = tlist.size();
124 | klein.vertexArray = (MeshVertex*) malloc(vlist.size() * sizeof(MeshVertex));
125 | klein.triangleArray = (MeshTriangle*) malloc(tlist.size() * sizeof(MeshTriangle));
126 |
127 | copy(vlist.begin(), vlist.begin() + vlist.size(), klein.vertexArray);
128 | copy(tlist.begin(), tlist.begin() + tlist.size(), klein.triangleArray);
129 |
130 | return klein;
131 | }
132 |
--------------------------------------------------------------------------------
/mesh_generators.hpp:
--------------------------------------------------------------------------------
1 | #ifndef MESH_GENERATORS_HPP
2 | #define MESH_GENERATORS_HPP
3 |
4 | #include "mesh.hpp"
5 |
6 | TriangleMesh GenerateParametricSphereMesh(const int &rings, const int &slices);
7 | TriangleMesh GenerateParametricKleinMesh(const int &rings, const int &slices);
8 | TriangleMesh GenerateParametricTorusMesh(const int &rings, const int &slices);
9 | TriangleMesh GenerateBagelKleinMesh(const int &rings, const int &slices);
10 |
11 | #endif // MESH_GENERATORS_HPP
12 |
--------------------------------------------------------------------------------
/sample_meshes/bagelklein.mesh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ornata/paramesh/1f0a00bc7c796cb15c4c7375540383ee053f7e84/sample_meshes/bagelklein.mesh
--------------------------------------------------------------------------------
/sample_meshes/kleinbottle.mesh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ornata/paramesh/1f0a00bc7c796cb15c4c7375540383ee053f7e84/sample_meshes/kleinbottle.mesh
--------------------------------------------------------------------------------
/sample_meshes/sphere.mesh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ornata/paramesh/1f0a00bc7c796cb15c4c7375540383ee053f7e84/sample_meshes/sphere.mesh
--------------------------------------------------------------------------------
/sample_meshes/torus.mesh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ornata/paramesh/1f0a00bc7c796cb15c4c7375540383ee053f7e84/sample_meshes/torus.mesh
--------------------------------------------------------------------------------