Files
2025-09-18 17:55:52 -04:00

334 lines
10 KiB
C++

#include "GfxBase/FileMeshData.h"
#include "rbx/Debug.h"
#include "rbx/DenseHash.h"
namespace RBX
{
struct MeshVertexHasher
{
bool operator()(const FileMeshVertexNormalTexture3d& l, const FileMeshVertexNormalTexture3d& r) const
{
return memcmp(&l, &r, sizeof(l)) == 0;
}
size_t operator()(const FileMeshVertexNormalTexture3d& v) const
{
size_t result = 0;
boost::hash_combine(result, v.vx);
boost::hash_combine(result, v.vy);
boost::hash_combine(result, v.vz);
return result;
}
};
void optimizeMesh(FileMeshData& mesh)
{
std::vector<unsigned int> remap(mesh.vnts.size());
FileMeshVertexNormalTexture3d dummy = {};
dummy.vx = FLT_MAX;
typedef DenseHashMap<FileMeshVertexNormalTexture3d, unsigned int, MeshVertexHasher, MeshVertexHasher> VertexMap;
VertexMap vertexMap(dummy);
for (size_t i = 0; i < mesh.vnts.size(); ++i)
{
unsigned int& vi = vertexMap[mesh.vnts[i]];
if (vi == 0)
vi = vertexMap.size();
remap[i] = vi - 1;
}
std::vector<FileMeshVertexNormalTexture3d> newvnts(vertexMap.size());
for (size_t i = 0; i < mesh.vnts.size(); ++i)
newvnts[remap[i]] = mesh.vnts[i];
mesh.vnts.swap(newvnts);
for (size_t i = 0; i < mesh.faces.size(); ++i)
{
FileMeshFace& face = mesh.faces[i];
face.a = remap[face.a];
face.b = remap[face.b];
face.c = remap[face.c];
}
}
inline unsigned int atouFast(const char* value, const char** end)
{
const char* s = value;
// skip whitespace
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
s++;
// read integer part
unsigned int result = 0;
while (static_cast<unsigned int>(*s - '0') < 10)
{
result = result * 10 + (*s - '0');
s++;
}
// done!
*end = s;
return result;
}
inline double atofFast(const char* value, const char** end)
{
static const double digits[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
static const double powers[] = { 1e0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22 };
const char* s = value;
// skip whitespace
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
s++;
// read sign
double sign = (*s == '-') ? -1 : 1;
s += (*s == '-' || *s == '+');
// read integer part
double result = 0;
int power = 0;
while (static_cast<unsigned int>(*s - '0') < 10)
{
result = result * 10 + digits[*s - '0'];
s++;
}
// read fractional part
if (*s == '.')
{
s++;
while (static_cast<unsigned int>(*s - '0') < 10)
{
result = result * 10 + digits[*s - '0'];
s++;
power--;
}
}
// read exponent part
if ((*s | ' ') == 'e')
{
s++;
// read exponent sign
int expsign = (*s == '-') ? -1 : 1;
s += (*s == '-' || *s == '+');
// read exponent
int exppower = 0;
while (static_cast<unsigned int>(*s - '0') < 10)
{
exppower = exppower * 10 + (*s - '0');
s++;
}
// done!
power += expsign * exppower;
}
// done!
*end = s;
if (static_cast<unsigned int>(-power) < sizeof(powers) / sizeof(powers[0]))
return sign * result / powers[-power];
else if (static_cast<unsigned int>(power) < sizeof(powers) / sizeof(powers[0]))
return sign * result * powers[power];
else
return sign * result * powf(10.0, power);
}
inline const char* readToken(const char* data, char terminator)
{
while (*data == ' ' || *data == '\t' || *data == '\r' || *data == '\n')
++data;
if (*data != terminator)
throw RBX::runtime_error("Error reading mesh data: expected %c", terminator);
return data + 1;
}
inline const char* readFloatToken(const char* data, char terminator, float* output)
{
const char* end;
double value = atofFast(data, &end);
if (*end != terminator)
throw RBX::runtime_error("Error reading mesh data: expected %c", terminator);
*output = value;
return end + 1;
}
shared_ptr<FileMeshData> readMeshFromV1(const std::string& data, size_t offset_, float scaler)
{
shared_ptr<FileMeshData> mesh(new FileMeshData());
const char* offset = data.c_str() + offset_;
unsigned int num_faces = atouFast(offset, &offset);
mesh->vnts.reserve(num_faces * 3);
mesh->faces.reserve(num_faces);
for (unsigned int i = 0; i < num_faces; i++)
{
for (int v = 0; v < 3; v++)
{
float vx, vy, vz, nx, ny, nz, tu, tv, tw;
offset = readToken(offset, '[');
offset = readFloatToken(offset, ',', &vx);
offset = readFloatToken(offset, ',', &vy);
offset = readFloatToken(offset, ']', &vz);
offset = readToken(offset, '[');
offset = readFloatToken(offset, ',', &nx);
offset = readFloatToken(offset, ',', &ny);
offset = readFloatToken(offset, ']', &nz);
offset = readToken(offset, '[');
offset = readFloatToken(offset, ',', &tu);
offset = readFloatToken(offset, ',', &tv);
offset = readFloatToken(offset, ']', &tw);
G3D::Vector3 normal = G3D::Vector3(nx, ny, nz).unit();
if (!normal.isFinite())
normal = G3D::Vector3::zero();
FileMeshVertexNormalTexture3d vtx =
{
vx * scaler, vy * scaler, vz * scaler,
normal.x, normal.y, normal.z,
tu, 1.f - tv, tw
};
mesh->vnts.push_back(vtx);
}
FileMeshFace face = {i * 3 + 0, i * 3 + 1, i * 3 + 2};
mesh->faces.push_back(face);
}
optimizeMesh(*mesh);
return mesh;
}
static void readData(const std::string& data, size_t& offset, void* buffer, size_t size)
{
if (offset + size > data.size())
throw RBX::runtime_error("Error reading mesh data: offset is out of bounds while reading %d bytes", (int)size);
memcpy(buffer, data.data() + offset, size);
offset += size;
}
static shared_ptr<FileMeshData> readMeshFromV2(const std::string& data, size_t offset)
{
shared_ptr<FileMeshData> mesh(new FileMeshData());
FileMeshHeader header;
readData(data, offset, &header, sizeof(header));
if (header.cbSize != sizeof(FileMeshHeader) || header.cbVerticesStride != sizeof(FileMeshVertexNormalTexture3d) || header.cbFaceStride != sizeof(FileMeshFace))
throw std::runtime_error("Error reading mesh data: incompatible stride");
if (header.num_vertices == 0 || header.num_faces == 0)
throw std::runtime_error("Error reading mesh data: empty mesh");
mesh->vnts.resize(header.num_vertices);
readData(data, offset, &mesh->vnts[0], header.num_vertices * header.cbVerticesStride);
mesh->faces.resize(header.num_faces);
readData(data, offset, &mesh->faces[0], header.num_faces * header.cbFaceStride);
if (offset != data.size())
throw std::runtime_error("Error reading mesh data: unexpected data at end of file");
// validate indices to avoid buffer overruns later
for (auto& face: mesh->faces)
if (face.a >= header.num_vertices || face.b >= header.num_vertices || face.c >= header.num_vertices)
throw std::runtime_error("Error reading mesh data: index value out of range");
return mesh;
}
FileMeshData* computeAABB(FileMeshData* mesh)
{
if (mesh->vnts.empty())
{
mesh->aabb = AABox(Vector3::zero());
}
else
{
AABox result = AABox(Vector3(mesh->vnts[0].vx, mesh->vnts[0].vy, mesh->vnts[0].vz));
for (size_t i = 1; i < mesh->vnts.size(); ++i)
result.merge(Vector3(mesh->vnts[i].vx, mesh->vnts[i].vy, mesh->vnts[i].vz));
mesh->aabb = result;
}
return mesh;
}
}
namespace RBX
{
shared_ptr<FileMeshData> ReadFileMesh(const std::string& data)
{
std::string::size_type versionEnd = data.find('\n');
if (versionEnd == std::string::npos)
throw std::runtime_error("Error reading mesh data: unknown version");
shared_ptr<FileMeshData> result;
if (data.compare(0, 12, "version 1.00") == 0)
result = readMeshFromV1(data, versionEnd + 1, 0.5f);
else if (data.compare(0, 12, "version 1.01") == 0)
result = readMeshFromV1(data, versionEnd + 1, 1.0f);
else if (data.compare(0, 12, "version 2.00") == 0)
result = readMeshFromV2(data, versionEnd + 1);
else
throw std::runtime_error("Error reading mesh data: unknown version");
computeAABB(result.get());
return result;
}
void WriteFileMesh(std::ostream& f, const FileMeshData& data)
{
f << "version 2.00" << std::endl;
FileMeshHeader header;
header.num_faces = data.faces.size();
header.num_vertices = data.vnts.size();
header.cbFaceStride = (unsigned char)sizeof(data.faces[0]);
header.cbVerticesStride = (unsigned char)sizeof(data.vnts[0]);
header.cbSize = sizeof(header);
f.write(reinterpret_cast<char*>(&header), sizeof(header));
f.write(reinterpret_cast<const char*>(&data.vnts[0]), sizeof(data.vnts[0]) * data.vnts.size());
f.write(reinterpret_cast<const char*>(&data.faces[0]), sizeof(data.faces[0]) * data.faces.size());
}
}