146 lines
4.7 KiB
C++
146 lines
4.7 KiB
C++
#include "vde/foundation/io_obj.h"
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
namespace vde::foundation {
|
|
|
|
ObjMeshData read_obj(const std::string& filepath) {
|
|
ObjMeshData data;
|
|
std::ifstream file(filepath);
|
|
if (!file) return data;
|
|
|
|
std::string line;
|
|
std::string current_material;
|
|
|
|
while (std::getline(file, line)) {
|
|
// Trim trailing \r
|
|
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
|
|
if (line.empty() || line[0] == '#') continue;
|
|
std::istringstream iss(line);
|
|
std::string token;
|
|
iss >> token;
|
|
|
|
if (token == "v") {
|
|
double x, y, z;
|
|
iss >> x >> y >> z;
|
|
data.vertices.emplace_back(x, y, z);
|
|
}
|
|
else if (token == "vt") {
|
|
double u, v, w = 0.0;
|
|
iss >> u >> v;
|
|
if (iss >> w) { /* 1D/3D texcoord — keep only uv */ }
|
|
data.texcoords.emplace_back(u, v);
|
|
}
|
|
else if (token == "vn") {
|
|
double nx, ny, nz;
|
|
iss >> nx >> ny >> nz;
|
|
data.normals.emplace_back(nx, ny, nz);
|
|
}
|
|
else if (token == "usemtl") {
|
|
iss >> current_material;
|
|
}
|
|
else if (token == "f") {
|
|
std::vector<int> face_v;
|
|
std::vector<int> face_vt;
|
|
std::vector<int> face_vn;
|
|
|
|
std::string vertex;
|
|
while (iss >> vertex) {
|
|
// Parse v/vt/vn, v//vn, v/vt formats
|
|
int v_idx = -1;
|
|
int vt_idx = -1;
|
|
int vn_idx = -1;
|
|
|
|
// Count slashes
|
|
size_t slash1 = vertex.find('/');
|
|
if (slash1 == std::string::npos) {
|
|
// "v" only
|
|
v_idx = std::stoi(vertex) - 1;
|
|
} else {
|
|
v_idx = std::stoi(vertex.substr(0, slash1)) - 1;
|
|
size_t slash2 = vertex.find('/', slash1 + 1);
|
|
if (slash2 == std::string::npos) {
|
|
// "v/vt"
|
|
vt_idx = std::stoi(vertex.substr(slash1 + 1)) - 1;
|
|
} else {
|
|
// "v/vt/vn" or "v//vn"
|
|
std::string mid = vertex.substr(slash1 + 1, slash2 - slash1 - 1);
|
|
if (!mid.empty()) vt_idx = std::stoi(mid) - 1;
|
|
vn_idx = std::stoi(vertex.substr(slash2 + 1)) - 1;
|
|
}
|
|
}
|
|
|
|
face_v.push_back(v_idx);
|
|
face_vt.push_back(vt_idx);
|
|
face_vn.push_back(vn_idx);
|
|
}
|
|
|
|
data.faces.push_back(std::move(face_v));
|
|
data.face_texcoords.push_back(std::move(face_vt));
|
|
data.face_normals.push_back(std::move(face_vn));
|
|
data.face_materials.push_back(current_material);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void write_obj(const std::string& filepath, const ObjMeshData& data) {
|
|
std::ofstream file(filepath);
|
|
if (!file) return;
|
|
|
|
file.precision(12);
|
|
|
|
// Vertices
|
|
for (const auto& v : data.vertices)
|
|
file << "v " << v.x() << " " << v.y() << " " << v.z() << "\n";
|
|
|
|
// Texture coordinates
|
|
for (const auto& vt : data.texcoords)
|
|
file << "vt " << vt.x() << " " << vt.y() << "\n";
|
|
|
|
// Normals
|
|
bool has_vt = data.has_texcoords();
|
|
bool has_vn = !data.normals.empty();
|
|
|
|
for (const auto& n : data.normals)
|
|
file << "vn " << n.x() << " " << n.y() << " " << n.z() << "\n";
|
|
|
|
// Faces with material tracking
|
|
std::string current_mtl;
|
|
for (size_t fi = 0; fi < data.faces.size(); ++fi) {
|
|
// Emit material change
|
|
if (fi < data.face_materials.size() && data.face_materials[fi] != current_mtl) {
|
|
current_mtl = data.face_materials[fi];
|
|
if (!current_mtl.empty())
|
|
file << "usemtl " << current_mtl << "\n";
|
|
}
|
|
|
|
file << "f";
|
|
const auto& fv = data.faces[fi];
|
|
for (size_t vi = 0; vi < fv.size(); ++vi) {
|
|
file << " " << (fv[vi] + 1); // 0-based → 1-based
|
|
|
|
bool wrote_slash = false;
|
|
if (has_vt && fi < data.face_texcoords.size() && vi < data.face_texcoords[fi].size()) {
|
|
int vt = data.face_texcoords[fi][vi];
|
|
if (vt >= 0) {
|
|
if (!wrote_slash) { file << "/"; wrote_slash = true; }
|
|
file << (vt + 1);
|
|
}
|
|
}
|
|
|
|
if (has_vn && fi < data.face_normals.size() && vi < data.face_normals[fi].size()) {
|
|
int vn = data.face_normals[fi][vi];
|
|
if (vn >= 0) {
|
|
if (!wrote_slash) file << "/";
|
|
file << "/" << (vn + 1);
|
|
}
|
|
}
|
|
}
|
|
file << "\n";
|
|
}
|
|
}
|
|
|
|
} // namespace vde::foundation
|