256 lines
7.2 KiB
C++
256 lines
7.2 KiB
C++
// Converts .obj files to .optmesh files
|
|
// Usage: meshencoder [.obj] [.optmesh]
|
|
|
|
// Data layout:
|
|
// Header: 64b
|
|
// Object table: 16b * object_count
|
|
// Object data
|
|
// Vertex data
|
|
// Index data
|
|
|
|
#include "../src/meshoptimizer.h"
|
|
#include "objparser.h"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
struct Header
|
|
{
|
|
char magic[4]; // OPTM
|
|
|
|
unsigned int group_count;
|
|
unsigned int vertex_count;
|
|
unsigned int index_count;
|
|
unsigned int vertex_data_size;
|
|
unsigned int index_data_size;
|
|
|
|
float pos_offset[3];
|
|
float pos_scale;
|
|
float uv_offset[2];
|
|
float uv_scale[2];
|
|
|
|
unsigned int reserved[2];
|
|
};
|
|
|
|
struct Object
|
|
{
|
|
unsigned int index_offset;
|
|
unsigned int index_count;
|
|
unsigned int material_length;
|
|
unsigned int reserved;
|
|
};
|
|
|
|
struct Vertex
|
|
{
|
|
unsigned short px, py, pz, pw; // unsigned 16-bit value, use pos_offset/pos_scale to unpack
|
|
char nx, ny, nz, nw; // normalized signed 8-bit value
|
|
unsigned short tx, ty; // unsigned 16-bit value, use uv_offset/uv_scale to unpack
|
|
};
|
|
|
|
float rcpSafe(float v)
|
|
{
|
|
return v == 0.f ? 0.f : 1.f / v;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc <= 2)
|
|
{
|
|
printf("Usage: %s [.obj] [.optmesh]\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const char* input = argv[1];
|
|
const char* output = argv[2];
|
|
|
|
ObjFile file;
|
|
|
|
if (!objParseFile(file, input))
|
|
{
|
|
printf("Error loading %s: file not found\n", input);
|
|
return 2;
|
|
}
|
|
|
|
if (!objValidate(file))
|
|
{
|
|
printf("Error loading %s: invalid file data\n", input);
|
|
return 3;
|
|
}
|
|
|
|
float pos_offset[3] = { FLT_MAX, FLT_MAX, FLT_MAX };
|
|
float pos_scale = 0.f;
|
|
|
|
for (size_t i = 0; i < file.v_size; i += 3)
|
|
{
|
|
pos_offset[0] = std::min(pos_offset[0], file.v[i + 0]);
|
|
pos_offset[1] = std::min(pos_offset[1], file.v[i + 1]);
|
|
pos_offset[2] = std::min(pos_offset[2], file.v[i + 2]);
|
|
}
|
|
|
|
for (size_t i = 0; i < file.v_size; i += 3)
|
|
{
|
|
pos_scale = std::max(pos_scale, file.v[i + 0] - pos_offset[0]);
|
|
pos_scale = std::max(pos_scale, file.v[i + 1] - pos_offset[1]);
|
|
pos_scale = std::max(pos_scale, file.v[i + 2] - pos_offset[2]);
|
|
}
|
|
|
|
float uv_offset[2] = { FLT_MAX, FLT_MAX };
|
|
float uv_scale[2] = { 0, 0 };
|
|
|
|
for (size_t i = 0; i < file.vt_size; i += 3)
|
|
{
|
|
uv_offset[0] = std::min(uv_offset[0], file.vt[i + 0]);
|
|
uv_offset[1] = std::min(uv_offset[1], file.vt[i + 1]);
|
|
}
|
|
|
|
for (size_t i = 0; i < file.vt_size; i += 3)
|
|
{
|
|
uv_scale[0] = std::max(uv_scale[0], file.vt[i + 0] - uv_offset[0]);
|
|
uv_scale[1] = std::max(uv_scale[1], file.vt[i + 1] - uv_offset[1]);
|
|
}
|
|
|
|
float pos_scale_inverse = rcpSafe(pos_scale);
|
|
float uv_scale_inverse[2] = { rcpSafe(uv_scale[0]), rcpSafe(uv_scale[1]) };
|
|
|
|
size_t total_indices = file.f_size / 3;
|
|
|
|
std::vector<Vertex> triangles(total_indices);
|
|
|
|
int pos_bits = 14;
|
|
int uv_bits = 12;
|
|
|
|
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];
|
|
|
|
// note: we scale the vertices uniformly; this is not the best option wrt compression quality
|
|
// however, it means we can scale the mesh uniformly without distorting the normals
|
|
// this is helpful for backends like ThreeJS that apply mesh scaling to normals
|
|
float px = (file.v[vi * 3 + 0] - pos_offset[0]) * pos_scale_inverse;
|
|
float py = (file.v[vi * 3 + 1] - pos_offset[1]) * pos_scale_inverse;
|
|
float pz = (file.v[vi * 3 + 2] - pos_offset[2]) * pos_scale_inverse;
|
|
|
|
// normal is 0 if absent from the mesh
|
|
float nx = vni >= 0 ? file.vn[vni * 3 + 0] : 0;
|
|
float ny = vni >= 0 ? file.vn[vni * 3 + 1] : 0;
|
|
float nz = vni >= 0 ? file.vn[vni * 3 + 2] : 0;
|
|
|
|
// scale the normal to make sure the largest component is +-1.0
|
|
// this reduces the entropy of the normal by ~1.5 bits without losing precision
|
|
// it's better to use octahedral encoding but that requires special shader support
|
|
float nm = std::max(fabsf(nx), std::max(fabsf(ny), fabsf(nz)));
|
|
float ns = nm == 0.f ? 0.f : 1 / nm;
|
|
|
|
nx *= ns;
|
|
ny *= ns;
|
|
nz *= ns;
|
|
|
|
// texture coordinates are 0 if absent, and require a texture matrix to decode
|
|
float tx = vti >= 0 ? (file.vt[vti * 3 + 0] - uv_offset[0]) * uv_scale_inverse[0] : 0;
|
|
float ty = vti >= 0 ? (file.vt[vti * 3 + 1] - uv_offset[1]) * uv_scale_inverse[1] : 0;
|
|
|
|
Vertex v =
|
|
{
|
|
(unsigned short)(meshopt_quantizeUnorm(px, pos_bits)),
|
|
(unsigned short)(meshopt_quantizeUnorm(py, pos_bits)),
|
|
(unsigned short)(meshopt_quantizeUnorm(pz, pos_bits)),
|
|
0,
|
|
|
|
char(meshopt_quantizeSnorm(nx, 8)),
|
|
char(meshopt_quantizeSnorm(ny, 8)),
|
|
char(meshopt_quantizeSnorm(nz, 8)),
|
|
0,
|
|
|
|
(unsigned short)(meshopt_quantizeUnorm(tx, uv_bits)),
|
|
(unsigned short)(meshopt_quantizeUnorm(ty, uv_bits)),
|
|
};
|
|
|
|
triangles[i] = v;
|
|
}
|
|
|
|
std::vector<unsigned int> remap(total_indices);
|
|
|
|
size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &triangles[0], total_indices, sizeof(Vertex));
|
|
|
|
std::vector<unsigned int> indices(total_indices);
|
|
meshopt_remapIndexBuffer(&indices[0], NULL, total_indices, &remap[0]);
|
|
|
|
std::vector<Vertex> vertices(total_vertices);
|
|
meshopt_remapVertexBuffer(&vertices[0], &triangles[0], total_indices, sizeof(Vertex), &remap[0]);
|
|
|
|
for (size_t i = 0; i < file.g_size; ++i)
|
|
{
|
|
ObjGroup& g = file.g[i];
|
|
|
|
meshopt_optimizeVertexCache(&indices[g.index_offset], &indices[g.index_offset], g.index_count, vertices.size());
|
|
}
|
|
|
|
meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indices.size(), &vertices[0], vertices.size(), sizeof(Vertex));
|
|
|
|
std::vector<unsigned char> vbuf(meshopt_encodeVertexBufferBound(vertices.size(), sizeof(Vertex)));
|
|
vbuf.resize(meshopt_encodeVertexBuffer(&vbuf[0], vbuf.size(), &vertices[0], vertices.size(), sizeof(Vertex)));
|
|
|
|
std::vector<unsigned char> ibuf(meshopt_encodeIndexBufferBound(indices.size(), vertices.size()));
|
|
ibuf.resize(meshopt_encodeIndexBuffer(&ibuf[0], ibuf.size(), &indices[0], indices.size()));
|
|
|
|
FILE* result = fopen(output, "wb");
|
|
if (!result)
|
|
{
|
|
printf("Error saving %s: can't open file for writing\n", output);
|
|
return 4;
|
|
}
|
|
|
|
Header header = {};
|
|
memcpy(header.magic, "OPTM", 4);
|
|
|
|
header.group_count = unsigned(file.g_size);
|
|
header.vertex_count = unsigned(vertices.size());
|
|
header.index_count = unsigned(indices.size());
|
|
header.vertex_data_size = unsigned(vbuf.size());
|
|
header.index_data_size = unsigned(ibuf.size());
|
|
|
|
header.pos_offset[0] = pos_offset[0];
|
|
header.pos_offset[1] = pos_offset[1];
|
|
header.pos_offset[2] = pos_offset[2];
|
|
header.pos_scale = pos_scale / float((1 << pos_bits) - 1);
|
|
|
|
header.uv_offset[0] = uv_offset[0];
|
|
header.uv_offset[1] = uv_offset[1];
|
|
header.uv_scale[0] = uv_scale[0] / float((1 << uv_bits) - 1);
|
|
header.uv_scale[1] = uv_scale[1] / float((1 << uv_bits) - 1);
|
|
|
|
fwrite(&header, 1, sizeof(header), result);
|
|
|
|
for (size_t i = 0; i < file.g_size; ++i)
|
|
{
|
|
ObjGroup& g = file.g[i];
|
|
|
|
Object object = {};
|
|
object.index_offset = unsigned(g.index_offset);
|
|
object.index_count = unsigned(g.index_count);
|
|
object.material_length = unsigned(strlen(g.material));
|
|
|
|
fwrite(&object, 1, sizeof(object), result);
|
|
}
|
|
|
|
for (size_t i = 0; i < file.g_size; ++i)
|
|
{
|
|
ObjGroup& g = file.g[i];
|
|
|
|
fwrite(g.material, 1, strlen(g.material), result);
|
|
}
|
|
|
|
fwrite(&vbuf[0], 1, vbuf.size(), result);
|
|
fwrite(&ibuf[0], 1, ibuf.size(), result);
|
|
fclose(result);
|
|
|
|
return 0;
|
|
}
|