mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
631 lines
16 KiB
C++
631 lines
16 KiB
C++
#define _CRT_SECURE_NO_WARNINGS
|
|
|
|
#include "../src/meshoptimizer.h"
|
|
#include "objparser.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include <vector>
|
|
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#ifdef GLTF
|
|
#define CGLTF_IMPLEMENTATION
|
|
#include "cgltf.h"
|
|
#endif
|
|
|
|
#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;
|
|
|
|
// 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)
|
|
{
|
|
ObjFile file;
|
|
|
|
if (!objParseFile(file, path) || !objValidate(file))
|
|
{
|
|
printf("Error loading %s\n", path);
|
|
return Mesh();
|
|
}
|
|
|
|
size_t total_indices = file.f_size / 3;
|
|
|
|
std::vector<Vertex> vertices(total_indices);
|
|
|
|
for (size_t i = 0; i < total_indices; ++i)
|
|
{
|
|
int vi = file.f[i * 3 + 0];
|
|
int vti = file.f[i * 3 + 1];
|
|
int vni = file.f[i * 3 + 2];
|
|
|
|
Vertex v =
|
|
{
|
|
file.v[vi * 3 + 0],
|
|
file.v[vi * 3 + 1],
|
|
file.v[vi * 3 + 2],
|
|
|
|
vni >= 0 ? file.vn[vni * 3 + 0] : 0,
|
|
vni >= 0 ? file.vn[vni * 3 + 1] : 0,
|
|
vni >= 0 ? file.vn[vni * 3 + 2] : 0,
|
|
|
|
vti >= 0 ? file.vt[vti * 3 + 0] : 0,
|
|
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
|
|
};
|
|
|
|
vertices[i] = v;
|
|
}
|
|
|
|
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]);
|
|
|
|
return result;
|
|
}
|
|
|
|
#ifdef GLTF
|
|
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;
|
|
}
|
|
|
|
template <typename T>
|
|
const T* getComponentPtr(const cgltf_accessor* a)
|
|
{
|
|
const char* buffer = (char*)a->buffer_view->buffer->data;
|
|
size_t offset = a->offset + a->buffer_view->offset;
|
|
|
|
return reinterpret_cast<const T*>(&buffer[offset]);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
if (ai->component_type == cgltf_component_type_r_32u)
|
|
{
|
|
const unsigned int* ptr = getComponentPtr<unsigned int>(ai);
|
|
|
|
for (size_t i = 0; i < ai->count; ++i)
|
|
result.indices[index_offset + i] = unsigned(vertex_offset + ptr[i]);
|
|
}
|
|
else
|
|
{
|
|
const unsigned short* ptr = getComponentPtr<unsigned short>(ai);
|
|
|
|
for (size_t i = 0; i < ai->count; ++i)
|
|
result.indices[index_offset + i] = unsigned(vertex_offset + ptr[i]);
|
|
}
|
|
|
|
{
|
|
const float* ptr = getComponentPtr<float>(ap);
|
|
|
|
for (size_t i = 0; i < ap->count; ++i)
|
|
{
|
|
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];
|
|
ptr += ap->stride / 4;
|
|
}
|
|
}
|
|
|
|
if (cgltf_accessor* an = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_normal))
|
|
{
|
|
const float* ptr = getComponentPtr<float>(an);
|
|
|
|
for (size_t i = 0; i < ap->count; ++i)
|
|
{
|
|
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];
|
|
ptr += an->stride / 4;
|
|
}
|
|
}
|
|
|
|
if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord))
|
|
{
|
|
const float* ptr = getComponentPtr<float>(at);
|
|
|
|
for (size_t i = 0; i < ap->count; ++i)
|
|
{
|
|
result.vertices[vertex_offset + i].tx = ptr[0];
|
|
result.vertices[vertex_offset + i].ty = ptr[1];
|
|
ptr += at->stride / 4;
|
|
}
|
|
}
|
|
|
|
vertex_offset += ap->count;
|
|
index_offset += ai->count;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
Mesh loadMesh(const char* path)
|
|
{
|
|
if (strstr(path, ".obj"))
|
|
return parseObj(path);
|
|
|
|
#ifdef GLTF
|
|
if (strstr(path, ".gltf") || strstr(path, ".glb"))
|
|
return parseGltf(path);
|
|
#endif
|
|
|
|
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);
|
|
fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz);
|
|
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;
|
|
|
|
fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, 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 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;
|
|
|
|
display(x * tilew, y * tileh, tilew, tileh, f.lodmesh, options);
|
|
}
|
|
|
|
glfwSwapBuffers(window);
|
|
}
|
|
|
|
glfwWaitEvents();
|
|
}
|
|
}
|