#include "vde/foundation/io_obj.h" #include #include 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 face_v; std::vector face_vt; std::vector 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