Files
ViewDesignEngine/src/foundation/io_obj.cpp
T
2026-07-23 12:36:39 +00:00

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