| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- #define _CRT_SECURE_NO_WARNINGS
- #include "../src/meshoptimizer.h"
- #include "fast_obj.h"
- #include "cgltf.h"
- #include <algorithm>
- #include <cmath>
- #include <cstdio>
- #include <ctime>
- #include <vector>
- #include <GLFW/glfw3.h>
- #ifdef _WIN32
- #pragma comment(lib, "opengl32.lib")
- #endif
- extern unsigned char* meshopt_simplifyDebugKind;
- extern unsigned int* meshopt_simplifyDebugLoop;
- #ifndef TRACE
- unsigned char* meshopt_simplifyDebugKind;
- unsigned int* meshopt_simplifyDebugLoop;
- #endif
- struct Options
- {
- bool wireframe;
- enum
- {
- Mode_Default,
- Mode_Texture,
- Mode_Normals,
- Mode_UV,
- Mode_Kind,
- } mode;
- };
- struct Vertex
- {
- float px, py, pz;
- float nx, ny, nz;
- float tx, ty;
- };
- struct Mesh
- {
- std::vector<Vertex> vertices;
- std::vector<unsigned int> indices;
- bool hasnormals;
- bool hastexture;
- // TODO: this is debug only visualization and will go away at some point
- std::vector<unsigned char> kinds;
- std::vector<unsigned int> loop;
- };
- Mesh parseObj(const char* path)
- {
- fastObjMesh* obj = fast_obj_read(path);
- if (!obj)
- {
- printf("Error loading %s: file not found\n", path);
- return Mesh();
- }
- size_t total_indices = 0;
- for (unsigned int i = 0; i < obj->face_count; ++i)
- total_indices += 3 * (obj->face_vertices[i] - 2);
- std::vector<Vertex> vertices(total_indices);
- size_t vertex_offset = 0;
- size_t index_offset = 0;
- bool hasnormals = false;
- bool hastexture = false;
- for (unsigned int i = 0; i < obj->face_count; ++i)
- {
- for (unsigned int j = 0; j < obj->face_vertices[i]; ++j)
- {
- fastObjIndex gi = obj->indices[index_offset + j];
- Vertex v =
- {
- obj->positions[gi.p * 3 + 0],
- obj->positions[gi.p * 3 + 1],
- obj->positions[gi.p * 3 + 2],
- obj->normals[gi.n * 3 + 0],
- obj->normals[gi.n * 3 + 1],
- obj->normals[gi.n * 3 + 2],
- obj->texcoords[gi.t * 2 + 0],
- obj->texcoords[gi.t * 2 + 1],
- };
- hasnormals |= (gi.n > 0);
- hastexture |= (gi.t > 0);
- // triangulate polygon on the fly; offset-3 is always the first polygon vertex
- if (j >= 3)
- {
- vertices[vertex_offset + 0] = vertices[vertex_offset - 3];
- vertices[vertex_offset + 1] = vertices[vertex_offset - 1];
- vertex_offset += 2;
- }
- vertices[vertex_offset] = v;
- vertex_offset++;
- }
- index_offset += obj->face_vertices[i];
- }
- fast_obj_destroy(obj);
- Mesh result;
- std::vector<unsigned int> remap(total_indices);
- size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex));
- result.indices.resize(total_indices);
- meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]);
- result.vertices.resize(total_vertices);
- meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]);
- result.hasnormals = hasnormals;
- result.hastexture = hastexture;
- return result;
- }
- cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0)
- {
- for (size_t i = 0; i < attribute_count; ++i)
- if (attributes[i].type == type && attributes[i].index == index)
- return attributes[i].data;
- return 0;
- }
- Mesh parseGltf(const char* path)
- {
- cgltf_options options = {};
- cgltf_data* data = 0;
- cgltf_result res = cgltf_parse_file(&options, path, &data);
- if (res != cgltf_result_success)
- {
- return Mesh();
- }
- res = cgltf_load_buffers(&options, data, path);
- if (res != cgltf_result_success)
- {
- cgltf_free(data);
- return Mesh();
- }
- res = cgltf_validate(data);
- if (res != cgltf_result_success)
- {
- cgltf_free(data);
- return Mesh();
- }
- size_t total_vertices = 0;
- size_t total_indices = 0;
- for (size_t ni = 0; ni < data->nodes_count; ++ni)
- {
- if (!data->nodes[ni].mesh)
- continue;
- const cgltf_mesh& mesh = *data->nodes[ni].mesh;
- for (size_t pi = 0; pi < mesh.primitives_count; ++pi)
- {
- const cgltf_primitive& primitive = mesh.primitives[pi];
- cgltf_accessor* ai = primitive.indices;
- cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position);
- if (!ai || !ap)
- continue;
- total_vertices += ap->count;
- total_indices += ai->count;
- }
- }
- Mesh result;
- result.vertices.resize(total_vertices);
- result.indices.resize(total_indices);
- size_t vertex_offset = 0;
- size_t index_offset = 0;
- bool hasnormals = false;
- bool hastexture = false;
- for (size_t ni = 0; ni < data->nodes_count; ++ni)
- {
- if (!data->nodes[ni].mesh)
- continue;
- const cgltf_mesh& mesh = *data->nodes[ni].mesh;
- float transform[16];
- cgltf_node_transform_world(&data->nodes[ni], transform);
- for (size_t pi = 0; pi < mesh.primitives_count; ++pi)
- {
- const cgltf_primitive& primitive = mesh.primitives[pi];
- cgltf_accessor* ai = primitive.indices;
- cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position);
- if (!ai || !ap)
- continue;
- for (size_t i = 0; i < ai->count; ++i)
- result.indices[index_offset + i] = unsigned(vertex_offset + cgltf_accessor_read_index(ai, i));
- {
- for (size_t i = 0; i < ap->count; ++i)
- {
- float ptr[3];
- cgltf_accessor_read_float(ap, i, ptr, 3);
- result.vertices[vertex_offset + i].px = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12];
- result.vertices[vertex_offset + i].py = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13];
- result.vertices[vertex_offset + i].pz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14];
- }
- }
- if (cgltf_accessor* an = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_normal))
- {
- for (size_t i = 0; i < ap->count; ++i)
- {
- float ptr[3];
- cgltf_accessor_read_float(an, i, ptr, 3);
- result.vertices[vertex_offset + i].nx = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8];
- result.vertices[vertex_offset + i].ny = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9];
- result.vertices[vertex_offset + i].nz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10];
- }
- hasnormals = true;
- }
- if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord))
- {
- for (size_t i = 0; i < ap->count; ++i)
- {
- float ptr[2];
- cgltf_accessor_read_float(at, i, ptr, 2);
- result.vertices[vertex_offset + i].tx = ptr[0];
- result.vertices[vertex_offset + i].ty = ptr[1];
- }
- hastexture = true;
- }
- vertex_offset += ap->count;
- index_offset += ai->count;
- }
- }
- result.hasnormals = hasnormals;
- result.hastexture = hastexture;
- std::vector<unsigned int> remap(total_indices);
- size_t unique_vertices = meshopt_generateVertexRemap(&remap[0], &result.indices[0], total_indices, &result.vertices[0], total_vertices, sizeof(Vertex));
- meshopt_remapIndexBuffer(&result.indices[0], &result.indices[0], total_indices, &remap[0]);
- meshopt_remapVertexBuffer(&result.vertices[0], &result.vertices[0], total_vertices, sizeof(Vertex), &remap[0]);
- result.vertices.resize(unique_vertices);
- cgltf_free(data);
- return result;
- }
- Mesh loadMesh(const char* path)
- {
- if (strstr(path, ".obj"))
- return parseObj(path);
- if (strstr(path, ".gltf") || strstr(path, ".glb"))
- return parseGltf(path);
- return Mesh();
- }
- bool saveObj(const Mesh& mesh, const char* path)
- {
- std::vector<Vertex> verts = mesh.vertices;
- std::vector<unsigned int> tris = mesh.indices;
- size_t vertcount = meshopt_optimizeVertexFetch(verts.data(), tris.data(), tris.size(), verts.data(), verts.size(), sizeof(Vertex));
- FILE* obj = fopen(path, "w");
- if (!obj)
- return false;
- for (size_t i = 0; i < vertcount; ++i)
- {
- fprintf(obj, "v %f %f %f\n", verts[i].px, verts[i].py, verts[i].pz);
- if (mesh.hasnormals)
- fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz);
- if (mesh.hastexture)
- fprintf(obj, "vt %f %f %f\n", verts[i].tx, verts[i].ty, 0.f);
- }
- for (size_t i = 0; i < tris.size(); i += 3)
- {
- unsigned int i0 = tris[i + 0] + 1;
- unsigned int i1 = tris[i + 1] + 1;
- unsigned int i2 = tris[i + 2] + 1;
- if (mesh.hasnormals && mesh.hastexture)
- fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, i2);
- else if (mesh.hasnormals && !mesh.hastexture)
- fprintf(obj, "f %d//%d %d//%d %d//%d\n", i0, i0, i1, i1, i2, i2);
- else if (!mesh.hasnormals && mesh.hastexture)
- fprintf(obj, "f %d/%d %d/%d %d/%d\n", i0, i0, i1, i1, i2, i2);
- else
- fprintf(obj, "f %d %d %dd\n", i0, i1, i2);
- }
- fclose(obj);
- return true;
- }
- Mesh optimize(const Mesh& mesh, int lod)
- {
- float threshold = powf(0.5f, float(lod));
- size_t target_index_count = size_t(mesh.indices.size() * threshold);
- float target_error = 1e-2f;
- Mesh result = mesh;
- result.kinds.resize(result.vertices.size());
- result.loop.resize(result.vertices.size());
- meshopt_simplifyDebugKind = &result.kinds[0];
- meshopt_simplifyDebugLoop = &result.loop[0];
- 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));
- return result;
- }
- void computeNormals(Mesh& mesh)
- {
- if (mesh.hasnormals)
- return;
- for (size_t i = 0; i < mesh.vertices.size(); ++i)
- {
- Vertex& v = mesh.vertices[i];
- v.nx = v.ny = v.nz = 0.f;
- }
- for (size_t i = 0; i < mesh.indices.size(); i += 3)
- {
- Vertex& v0 = mesh.vertices[mesh.indices[i + 0]];
- Vertex& v1 = mesh.vertices[mesh.indices[i + 1]];
- Vertex& v2 = mesh.vertices[mesh.indices[i + 2]];
- float v10[3] = {v1.px - v0.px, v1.py - v0.py, v1.pz - v0.pz};
- float v20[3] = {v2.px - v0.px, v2.py - v0.py, v2.pz - v0.pz};
- float normalx = v10[1] * v20[2] - v10[2] * v20[1];
- float normaly = v10[2] * v20[0] - v10[0] * v20[2];
- float normalz = v10[0] * v20[1] - v10[1] * v20[0];
- v0.nx += normalx;
- v0.ny += normaly;
- v0.nz += normalz;
- v1.nx += normalx;
- v1.ny += normaly;
- v1.nz += normalz;
- v2.nx += normalx;
- v2.ny += normaly;
- v2.nz += normalz;
- }
- for (size_t i = 0; i < mesh.vertices.size(); ++i)
- {
- Vertex& v = mesh.vertices[i];
- float nl = sqrtf(v.nx * v.nx + v.ny * v.ny + v.nz * v.nz);
- float ns = (nl == 0.f) ? 0.f : 1.f / nl;
- v.nx *= ns;
- v.ny *= ns;
- v.nz *= ns;
- }
- }
- void display(int x, int y, int width, int height, const Mesh& mesh, const Options& options)
- {
- glViewport(x, y, width, height);
- glEnable(GL_DEPTH_TEST);
- glDepthFunc(GL_LESS);
- glDepthMask(GL_TRUE);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glRotatef(0.f, 0.f, 1.f, 0.f);
- glPolygonMode(GL_FRONT_AND_BACK, options.wireframe ? GL_LINE : GL_FILL);
- float centerx = 0;
- float centery = 0;
- float centerz = 0;
- float centeru = 0;
- float centerv = 0;
- for (size_t i = 0; i < mesh.vertices.size(); ++i)
- {
- const Vertex& v = mesh.vertices[i];
- centerx += v.px;
- centery += v.py;
- centerz += v.pz;
- centeru += v.tx;
- centerv += v.ty;
- }
- centerx /= float(mesh.vertices.size());
- centery /= float(mesh.vertices.size());
- centerz /= float(mesh.vertices.size());
- centeru /= float(mesh.vertices.size());
- centerv /= float(mesh.vertices.size());
- float extent = 0;
- float extentuv = 0;
- for (size_t i = 0; i < mesh.vertices.size(); ++i)
- {
- const Vertex& v = mesh.vertices[i];
- extent = std::max(extent, fabsf(v.px - centerx));
- extent = std::max(extent, fabsf(v.py - centery));
- extent = std::max(extent, fabsf(v.pz - centerz));
- extentuv = std::max(extentuv, fabsf(v.tx - centeru));
- extentuv = std::max(extentuv, fabsf(v.ty - centerv));
- }
- extent *= 1.1f;
- extentuv *= 1.1f;
- float scalex = width > height ? float(height) / float(width) : 1;
- float scaley = height > width ? float(width) / float(height) : 1;
- glBegin(GL_TRIANGLES);
- for (size_t i = 0; i < mesh.indices.size(); ++i)
- {
- const Vertex& v = mesh.vertices[mesh.indices[i]];
- float intensity = -(v.pz - centerz) / extent * 0.5f + 0.5f;
- switch (options.mode)
- {
- case Options::Mode_UV:
- glColor3f(intensity, intensity, intensity);
- glVertex3f((v.tx - centeru) / extentuv * scalex, (v.ty - centerv) / extentuv * scaley, 0);
- break;
- case Options::Mode_Texture:
- glColor3f(v.tx - floorf(v.tx), v.ty - floorf(v.ty), 0.5f);
- glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
- break;
- case Options::Mode_Normals:
- glColor3f(v.nx * 0.5f + 0.5f, v.ny * 0.5f + 0.5f, v.nz * 0.5f + 0.5f);
- glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
- break;
- default:
- glColor3f(intensity, intensity, intensity);
- glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent);
- }
- }
- glEnd();
- float zbias = 1e-3f;
- if (options.mode == Options::Mode_Kind && !mesh.kinds.empty() && !mesh.loop.empty())
- {
- glLineWidth(1);
- glBegin(GL_LINES);
- for (size_t i = 0; i < mesh.indices.size(); ++i)
- {
- unsigned int a = mesh.indices[i];
- unsigned int b = mesh.loop[a];
- if (b != ~0u)
- {
- const Vertex& v0 = mesh.vertices[a];
- const Vertex& v1 = mesh.vertices[b];
- unsigned char kind = mesh.kinds[a];
- glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3);
- glVertex3f((v0.px - centerx) / extent * scalex, (v0.py - centery) / extent * scaley, (v0.pz - centerz) / extent - zbias);
- glVertex3f((v1.px - centerx) / extent * scalex, (v1.py - centery) / extent * scaley, (v1.pz - centerz) / extent - zbias);
- }
- }
- glEnd();
- glPointSize(3);
- glBegin(GL_POINTS);
- for (size_t i = 0; i < mesh.indices.size(); ++i)
- {
- const Vertex& v = mesh.vertices[mesh.indices[i]];
- unsigned char kind = mesh.kinds[mesh.indices[i]];
- if (kind != 0)
- {
- glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3);
- glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent - zbias * 2);
- }
- }
- glEnd();
- }
- }
- void stats(GLFWwindow* window, const char* path, unsigned int triangles, int lod, double time)
- {
- char title[256];
- snprintf(title, sizeof(title), "%s: LOD %d - %d triangles (%.1f msec)", path, lod, triangles, time * 1000);
- glfwSetWindowTitle(window, title);
- }
- struct File
- {
- Mesh basemesh;
- Mesh lodmesh;
- const char* path;
- };
- std::vector<File> files;
- Options options;
- bool redraw;
- void keyhandler(GLFWwindow* window, int key, int scancode, int action, int mods)
- {
- if (action == GLFW_PRESS)
- {
- if (key == GLFW_KEY_W)
- {
- options.wireframe = !options.wireframe;
- redraw = true;
- }
- else if (key == GLFW_KEY_T)
- {
- options.mode = options.mode == Options::Mode_Texture ? Options::Mode_Default : Options::Mode_Texture;
- redraw = true;
- }
- else if (key == GLFW_KEY_N)
- {
- options.mode = options.mode == Options::Mode_Normals ? Options::Mode_Default : Options::Mode_Normals;
- redraw = true;
- }
- else if (key == GLFW_KEY_U)
- {
- options.mode = options.mode == Options::Mode_UV ? Options::Mode_Default : Options::Mode_UV;
- redraw = true;
- }
- else if (key == GLFW_KEY_K)
- {
- options.mode = options.mode == Options::Mode_Kind ? Options::Mode_Default : Options::Mode_Kind;
- redraw = true;
- }
- else if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9)
- {
- int lod = int(key - GLFW_KEY_0);
- unsigned int triangles = 0;
- clock_t start = clock();
- for (auto& f : files)
- {
- f.lodmesh = optimize(f.basemesh, lod);
- triangles += unsigned(f.lodmesh.indices.size() / 3);
- }
- clock_t end = clock();
- stats(window, files[0].path, triangles, lod, double(end - start) / CLOCKS_PER_SEC);
- redraw = true;
- }
- else if (key == GLFW_KEY_S)
- {
- int i = 0;
- for (auto& f : files)
- {
- char path[32];
- sprintf(path, "result%d.obj", i);
- saveObj(f.lodmesh, path);
- printf("Saved LOD of %s to %s\n", f.path, path);
- }
- }
- }
- }
- void sizehandler(GLFWwindow* window, int width, int height)
- {
- redraw = true;
- }
- int main(int argc, char** argv)
- {
- if (argc <= 1)
- {
- printf("Usage: %s [.obj files]\n", argv[0]);
- return 0;
- }
- unsigned int basetriangles = 0;
- for (int i = 1; i < argc; ++i)
- {
- files.emplace_back();
- File& f = files.back();
- f.path = argv[i];
- f.basemesh = loadMesh(f.path);
- f.lodmesh = optimize(f.basemesh, 0);
- basetriangles += unsigned(f.basemesh.indices.size() / 3);
- }
- glfwInit();
- GLFWwindow* window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);
- glfwMakeContextCurrent(window);
- stats(window, files[0].path, basetriangles, 0, 0);
- glfwSetKeyCallback(window, keyhandler);
- glfwSetWindowSizeCallback(window, sizehandler);
- redraw = true;
- while (!glfwWindowShouldClose(window))
- {
- if (redraw)
- {
- redraw = false;
- int width, height;
- glfwGetFramebufferSize(window, &width, &height);
- glViewport(0, 0, width, height);
- glClearDepth(1.f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- int cols = int(ceil(sqrt(double(files.size()))));
- int rows = int(ceil(double(files.size()) / cols));
- int tilew = width / cols;
- int tileh = height / rows;
- for (size_t i = 0; i < files.size(); ++i)
- {
- File& f = files[i];
- int x = int(i) % cols;
- int y = int(i) / cols;
- if (options.mode == Options::Mode_Normals)
- computeNormals(f.lodmesh);
- display(x * tilew, y * tileh, tilew, tileh, f.lodmesh, options);
- }
- glfwSwapBuffers(window);
- }
- glfwWaitEvents();
- }
- }
|