├── 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 | ![](https://pbs.twimg.com/media/Cd4GQUDUsAA20tv.jpg:large) 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 | ![](http://i.imgur.com/4saK9O4.jpg) 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 --------------------------------------------------------------------------------