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