1 #define _CRT_SECURE_NO_WARNINGS
2
3 #include "../src/meshoptimizer.h"
4 #include "fast_obj.h"
5 #include "cgltf.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <cstdio>
10 #include <ctime>
11 #include <vector>
12
13 #include <GLFW/glfw3.h>
14
15 #ifdef _WIN32
16 #pragma comment(lib, "opengl32.lib")
17 #endif
18
19 extern unsigned char* meshopt_simplifyDebugKind;
20 extern unsigned int* meshopt_simplifyDebugLoop;
21
22 #ifndef TRACE
23 unsigned char* meshopt_simplifyDebugKind;
24 unsigned int* meshopt_simplifyDebugLoop;
25 #endif
26
27 struct Options
28 {
29 bool wireframe;
30 enum
31 {
32 Mode_Default,
33 Mode_Texture,
34 Mode_Normals,
35 Mode_UV,
36 Mode_Kind,
37 } mode;
38 };
39
40 struct Vertex
41 {
42 float px, py, pz;
43 float nx, ny, nz;
44 float tx, ty;
45 };
46
47 struct Mesh
48 {
49 std::vector<Vertex> vertices;
50 std::vector<unsigned int> indices;
51
52 // TODO: this is debug only visualization and will go away at some point
53 std::vector<unsigned char> kinds;
54 std::vector<unsigned int> loop;
55 };
56
parseObj(const char * path)57 Mesh parseObj(const char* path)
58 {
59 fastObjMesh* obj = fast_obj_read(path);
60 if (!obj)
61 {
62 printf("Error loading %s: file not found\n", path);
63 return Mesh();
64 }
65
66 size_t total_indices = 0;
67
68 for (unsigned int i = 0; i < obj->face_count; ++i)
69 total_indices += 3 * (obj->face_vertices[i] - 2);
70
71 std::vector<Vertex> vertices(total_indices);
72
73 size_t vertex_offset = 0;
74 size_t index_offset = 0;
75
76 for (unsigned int i = 0; i < obj->face_count; ++i)
77 {
78 for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
79 {
80 fastObjIndex gi = obj->indices[index_offset + j];
81
82 Vertex v =
83 {
84 obj->positions[gi.p * 3 + 0],
85 obj->positions[gi.p * 3 + 1],
86 obj->positions[gi.p * 3 + 2],
87 obj->normals[gi.n * 3 + 0],
88 obj->normals[gi.n * 3 + 1],
89 obj->normals[gi.n * 3 + 2],
90 obj->texcoords[gi.t * 2 + 0],
91 obj->texcoords[gi.t * 2 + 1],
92 };
93
94 // triangulate polygon on the fly; offset-3 is always the first polygon vertex
95 if (j >= 3)
96 {
97 vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
98 vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
99 vertex_offset += 2;
100 }
101
102 vertices[vertex_offset] = v;
103 vertex_offset++;
104 }
105
106 index_offset += obj->face_vertices[i];
107 }
108
109 fast_obj_destroy(obj);
110
111 Mesh result;
112
113 std::vector<unsigned int> remap(total_indices);
114 size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));
115
116 result.indices.resize(total_indices);
117 meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);
118
119 result.vertices.resize(total_vertices);
120 meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]);
121
122 return result;
123 }
124
getAccessor(const cgltf_attribute * attributes,size_t attribute_count,cgltf_attribute_type type,int index=0)125 cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0)
126 {
127 for (size_t i = 0; i < attribute_count; ++i)
128 if (attributes[i].type == type && attributes[i].index == index)
129 return attributes[i].data;
130
131 return 0;
132 }
133
parseGltf(const char * path)134 Mesh parseGltf(const char* path)
135 {
136 cgltf_options options = {};
137 cgltf_data* data = 0;
138 cgltf_result res = cgltf_parse_file(&options, path, &data);
139
140 if (res != cgltf_result_success)
141 {
142 return Mesh();
143 }
144
145 res = cgltf_load_buffers(&options, data, path);
146 if (res != cgltf_result_success)
147 {
148 cgltf_free(data);
149 return Mesh();
150 }
151
152 res = cgltf_validate(data);
153 if (res != cgltf_result_success)
154 {
155 cgltf_free(data);
156 return Mesh();
157 }
158
159 size_t total_vertices = 0;
160 size_t total_indices = 0;
161
162 for (size_t ni = 0; ni < data->nodes_count; ++ni)
163 {
164 if (!data->nodes[ni].mesh)
165 continue;
166
167 const cgltf_mesh& mesh = *data->nodes[ni].mesh;
168
169 for (size_t pi = 0; pi < mesh.primitives_count; ++pi)
170 {
171 const cgltf_primitive& primitive = mesh.primitives[pi];
172
173 cgltf_accessor* ai = primitive.indices;
174 cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position);
175
176 if (!ai || !ap)
177 continue;
178
179 total_vertices += ap->count;
180 total_indices += ai->count;
181 }
182 }
183
184 Mesh result;
185 result.vertices.resize(total_vertices);
186 result.indices.resize(total_indices);
187
188 size_t vertex_offset = 0;
189 size_t index_offset = 0;
190
191 for (size_t ni = 0; ni < data->nodes_count; ++ni)
192 {
193 if (!data->nodes[ni].mesh)
194 continue;
195
196 const cgltf_mesh& mesh = *data->nodes[ni].mesh;
197
198 float transform[16];
199 cgltf_node_transform_world(&data->nodes[ni], transform);
200
201 for (size_t pi = 0; pi < mesh.primitives_count; ++pi)
202 {
203 const cgltf_primitive& primitive = mesh.primitives[pi];
204
205 cgltf_accessor* ai = primitive.indices;
206 cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position);
207
208 if (!ai || !ap)
209 continue;
210
211 for (size_t i = 0; i < ai->count; ++i)
212 result.indices[index_offset + i] = unsigned(vertex_offset + cgltf_accessor_read_index(ai, i));
213
214 {
215 for (size_t i = 0; i < ap->count; ++i)
216 {
217 float ptr[3];
218 cgltf_accessor_read_float(ap, i, ptr, 3);
219
220 result.vertices[vertex_offset + i].px = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12];
221 result.vertices[vertex_offset + i].py = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13];
222 result.vertices[vertex_offset + i].pz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14];
223 }
224 }
225
226 if (cgltf_accessor* an = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_normal))
227 {
228 for (size_t i = 0; i < ap->count; ++i)
229 {
230 float ptr[3];
231 cgltf_accessor_read_float(an, i, ptr, 3);
232
233 result.vertices[vertex_offset + i].nx = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8];
234 result.vertices[vertex_offset + i].ny = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9];
235 result.vertices[vertex_offset + i].nz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10];
236 }
237 }
238
239 if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord))
240 {
241 for (size_t i = 0; i < ap->count; ++i)
242 {
243 float ptr[2];
244 cgltf_accessor_read_float(at, i, ptr, 2);
245
246 result.vertices[vertex_offset + i].tx = ptr[0];
247 result.vertices[vertex_offset + i].ty = ptr[1];
248 }
249 }
250
251 vertex_offset += ap->count;
252 index_offset += ai->count;
253 }
254 }
255
256 std::vector<unsigned int> remap(total_indices);
257 size_t unique_vertices = meshopt_generateVertexRemap(&remap[0], &result.indices[0], total_indices, &result.vertices[0], total_vertices, sizeof(Vertex));
258
259 meshopt_remapIndexBuffer(&result.indices[0], &result.indices[0], total_indices, &remap[0]);
260 meshopt_remapVertexBuffer(&result.vertices[0], &result.vertices[0], total_vertices, sizeof(Vertex), &remap[0]);
261
262 result.vertices.resize(unique_vertices);
263
264 cgltf_free(data);
265
266 return result;
267 }
268
loadMesh(const char * path)269 Mesh loadMesh(const char* path)
270 {
271 if (strstr(path, ".obj"))
272 return parseObj(path);
273
274 if (strstr(path, ".gltf") || strstr(path, ".glb"))
275 return parseGltf(path);
276
277 return Mesh();
278 }
279
saveObj(const Mesh & mesh,const char * path)280 bool saveObj(const Mesh& mesh, const char* path)
281 {
282 std::vector<Vertex> verts = mesh.vertices;
283 std::vector<unsigned int> tris = mesh.indices;
284 size_t vertcount = meshopt_optimizeVertexFetch(verts.data(), tris.data(), tris.size(), verts.data(), verts.size(), sizeof(Vertex));
285
286 FILE* obj = fopen(path, "w");
287 if (!obj)
288 return false;
289
290 for (size_t i = 0; i < vertcount; ++i)
291 {
292 fprintf(obj, "v %f %f %f\n", verts[i].px, verts[i].py, verts[i].pz);
293 fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz);
294 fprintf(obj, "vt %f %f %f\n", verts[i].tx, verts[i].ty, 0.f);
295 }
296
297 for (size_t i = 0; i < tris.size(); i += 3)
298 {
299 unsigned int i0 = tris[i + 0] + 1;
300 unsigned int i1 = tris[i + 1] + 1;
301 unsigned int i2 = tris[i + 2] + 1;
302
303 fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, i2);
304 }
305
306 fclose(obj);
307
308 return true;
309 }
310
optimize(const Mesh & mesh,int lod)311 Mesh optimize(const Mesh& mesh, int lod)
312 {
313 float threshold = powf(0.5f, float(lod));
314 size_t target_index_count = size_t(mesh.indices.size() * threshold);
315 float target_error = 1e-2f;
316
317 Mesh result = mesh;
318 result.kinds.resize(result.vertices.size());
319 result.loop.resize(result.vertices.size());
320 meshopt_simplifyDebugKind = &result.kinds[0];
321 meshopt_simplifyDebugLoop = &result.loop[0];
322 result.indices.resize(meshopt_simplify(&result.indices[0], &result.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error));
323
324 return result;
325 }
326
display(int x,int y,int width,int height,const Mesh & mesh,const Options & options)327 void display(int x, int y, int width, int height, const Mesh& mesh, const Options& options)
328 {
329 glViewport(x, y, width, height);
330 glEnable(GL_DEPTH_TEST);
331 glDepthFunc(GL_LESS);
332 glDepthMask(GL_TRUE);
333
334 glMatrixMode(GL_MODELVIEW);
335 glLoadIdentity();
336 glRotatef(0.f, 0.f, 1.f, 0.f);
337
338 glPolygonMode(GL_FRONT_AND_BACK, options.wireframe ? GL_LINE : GL_FILL);
339
340 float centerx = 0;
341 float centery = 0;
342 float centerz = 0;
343 float centeru = 0;
344 float centerv = 0;
345
346 for (size_t i = 0; i < mesh.vertices.size(); ++i)
347 {
348 const Vertex& v = mesh.vertices[i];
349
350 centerx += v.px;
351 centery += v.py;
352 centerz += v.pz;
353 centeru += v.tx;
354 centerv += v.ty;
355 }
356
357 centerx /= float(mesh.vertices.size());
358 centery /= float(mesh.vertices.size());
359 centerz /= float(mesh.vertices.size());
360 centeru /= float(mesh.vertices.size());
361 centerv /= float(mesh.vertices.size());
362
363 float extent = 0;
364 float extentuv = 0;
365
366 for (size_t i = 0; i < mesh.vertices.size(); ++i)
367 {
368 const Vertex& v = mesh.vertices[i];
369
370 extent = std::max(extent, fabsf(v.px - centerx));
371 extent = std::max(extent, fabsf(v.py - centery));
372 extent = std::max(extent, fabsf(v.pz - centerz));
373 extentuv = std::max(extentuv, fabsf(v.tx - centeru));
374 extentuv = std::max(extentuv, fabsf(v.ty - centerv));
375 }
376
377 extent *= 1.1f;
378 extentuv *= 1.1f;
379
380 float scalex = width > height ? float(height) / float(width) : 1;
381 float scaley = height > width ? float(width) / float(height) : 1;
382
383 glBegin(GL_TRIANGLES);
384
385 for (size_t i = 0; i < mesh.indices.size(); ++i)
386 {
387 const Vertex& v = mesh.vertices[mesh.indices[i]];
388
389 float intensity = -(v.pz - centerz) / extent * 0.5f + 0.5f;
390
391 switch (options.mode)
392 {
393 case Options::Mode_UV:
394 glColor3f(intensity, intensity, intensity);
395 glVertex3f((v.tx - centeru) / extentuv * scalex, (v.ty - centerv) / extentuv * scaley, 0);
396 break;
397
398 case Options::Mode_Texture:
399 glColor3f(v.tx - floorf(v.tx), v.ty - floorf(v.ty), 0.5f);
400 glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
401 break;
402
403 case Options::Mode_Normals:
404 glColor3f(v.nx * 0.5f + 0.5f, v.ny * 0.5f + 0.5f, v.nz * 0.5f + 0.5f);
405 glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
406 break;
407
408 default:
409 glColor3f(intensity, intensity, intensity);
410 glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
411 }
412 }
413
414 glEnd();
415
416 float zbias = 1e-3f;
417
418 if (options.mode == Options::Mode_Kind && !mesh.kinds.empty() && !mesh.loop.empty())
419 {
420 glLineWidth(1);
421
422 glBegin(GL_LINES);
423
424 for (size_t i = 0; i < mesh.indices.size(); ++i)
425 {
426 unsigned int a = mesh.indices[i];
427 unsigned int b = mesh.loop[a];
428
429 if (b != ~0u)
430 {
431 const Vertex& v0 = mesh.vertices[a];
432 const Vertex& v1 = mesh.vertices[b];
433
434 unsigned char kind = mesh.kinds[a];
435
436 glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3);
437 glVertex3f((v0.px - centerx) / extent * scalex, (v0.py - centery) / extent * scaley, (v0.pz - centerz) / extent - zbias);
438 glVertex3f((v1.px - centerx) / extent * scalex, (v1.py - centery) / extent * scaley, (v1.pz - centerz) / extent - zbias);
439 }
440 }
441
442 glEnd();
443
444 glPointSize(3);
445
446 glBegin(GL_POINTS);
447
448 for (size_t i = 0; i < mesh.indices.size(); ++i)
449 {
450 const Vertex& v = mesh.vertices[mesh.indices[i]];
451 unsigned char kind = mesh.kinds[mesh.indices[i]];
452
453 if (kind != 0)
454 {
455 glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3);
456 glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent - zbias * 2);
457 }
458 }
459
460 glEnd();
461 }
462 }
463
stats(GLFWwindow * window,const char * path,unsigned int triangles,int lod,double time)464 void stats(GLFWwindow* window, const char* path, unsigned int triangles, int lod, double time)
465 {
466 char title[256];
467 snprintf(title, sizeof(title), "%s: LOD %d - %d triangles (%.1f msec)", path, lod, triangles, time * 1000);
468
469 glfwSetWindowTitle(window, title);
470 }
471
472 struct File
473 {
474 Mesh basemesh;
475 Mesh lodmesh;
476 const char* path;
477 };
478
479 std::vector<File> files;
480 Options options;
481 bool redraw;
482
keyhandler(GLFWwindow * window,int key,int scancode,int action,int mods)483 void keyhandler(GLFWwindow* window, int key, int scancode, int action, int mods)
484 {
485 if (action == GLFW_PRESS)
486 {
487 if (key == GLFW_KEY_W)
488 {
489 options.wireframe = !options.wireframe;
490 redraw = true;
491 }
492 else if (key == GLFW_KEY_T)
493 {
494 options.mode = options.mode == Options::Mode_Texture ? Options::Mode_Default : Options::Mode_Texture;
495 redraw = true;
496 }
497 else if (key == GLFW_KEY_N)
498 {
499 options.mode = options.mode == Options::Mode_Normals ? Options::Mode_Default : Options::Mode_Normals;
500 redraw = true;
501 }
502 else if (key == GLFW_KEY_U)
503 {
504 options.mode = options.mode == Options::Mode_UV ? Options::Mode_Default : Options::Mode_UV;
505 redraw = true;
506 }
507 else if (key == GLFW_KEY_K)
508 {
509 options.mode = options.mode == Options::Mode_Kind ? Options::Mode_Default : Options::Mode_Kind;
510 redraw = true;
511 }
512 else if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9)
513 {
514 int lod = int(key - GLFW_KEY_0);
515
516 unsigned int triangles = 0;
517
518 clock_t start = clock();
519 for (auto& f : files)
520 {
521 f.lodmesh = optimize(f.basemesh, lod);
522 triangles += unsigned(f.lodmesh.indices.size() / 3);
523 }
524 clock_t end = clock();
525
526 stats(window, files[0].path, triangles, lod, double(end - start) / CLOCKS_PER_SEC);
527 redraw = true;
528 }
529 else if (key == GLFW_KEY_S)
530 {
531 int i = 0;
532
533 for (auto& f : files)
534 {
535 char path[32];
536 sprintf(path, "result%d.obj", i);
537
538 saveObj(f.lodmesh, path);
539
540 printf("Saved LOD of %s to %s\n", f.path, path);
541 }
542 }
543 }
544 }
545
sizehandler(GLFWwindow * window,int width,int height)546 void sizehandler(GLFWwindow* window, int width, int height)
547 {
548 redraw = true;
549 }
550
main(int argc,char ** argv)551 int main(int argc, char** argv)
552 {
553 if (argc <= 1)
554 {
555 printf("Usage: %s [.obj files]\n", argv[0]);
556 return 0;
557 }
558
559 unsigned int basetriangles = 0;
560
561 for (int i = 1; i < argc; ++i)
562 {
563 files.emplace_back();
564 File& f = files.back();
565
566 f.path = argv[i];
567 f.basemesh = loadMesh(f.path);
568 f.lodmesh = optimize(f.basemesh, 0);
569
570 basetriangles += unsigned(f.basemesh.indices.size() / 3);
571 }
572
573 glfwInit();
574
575 GLFWwindow* window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);
576 glfwMakeContextCurrent(window);
577
578 stats(window, files[0].path, basetriangles, 0, 0);
579
580 glfwSetKeyCallback(window, keyhandler);
581 glfwSetWindowSizeCallback(window, sizehandler);
582
583 redraw = true;
584
585 while (!glfwWindowShouldClose(window))
586 {
587 if (redraw)
588 {
589 redraw = false;
590
591 int width, height;
592 glfwGetFramebufferSize(window, &width, &height);
593
594 glViewport(0, 0, width, height);
595 glClearDepth(1.f);
596 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
597
598 int cols = int(ceil(sqrt(double(files.size()))));
599 int rows = int(ceil(double(files.size()) / cols));
600
601 int tilew = width / cols;
602 int tileh = height / rows;
603
604 for (size_t i = 0; i < files.size(); ++i)
605 {
606 File& f = files[i];
607 int x = int(i) % cols;
608 int y = int(i) / cols;
609
610 display(x * tilew, y * tileh, tilew, tileh, f.lodmesh, options);
611 }
612
613 glfwSwapBuffers(window);
614 }
615
616 glfwWaitEvents();
617 }
618 }
619