//////////////////////////////////////////////////////////////////// // OBJ.cpp // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) #include "Common.h" #include "OBJ.h" using namespace SEACAVE; // D E F I N E S /////////////////////////////////////////////////// // uncomment to enable multi-threading based on OpenMP #ifdef _USE_OPENMP #define OBJ_USE_OPENMP #endif #define OBJ_INDEX_OFFSET 1 // S T R U C T S /////////////////////////////////////////////////// ObjModel::MaterialLib::Material::Material(const Image8U3& _diffuse_map, const Color& _Kd) : diffuse_map(_diffuse_map), Kd(_Kd) { } bool ObjModel::MaterialLib::Material::LoadDiffuseMap() { if (diffuse_map.empty()) return diffuse_map.Load(diffuse_name); return true; } /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// ObjModel::MaterialLib::MaterialLib() { } bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const { DEBUG_EXTRA("MaterialLib::Save %s", prefix.c_str()); std::ofstream out((prefix+".mtl").c_str()); if (!out.good()) return false; const String pathName(Util::getFilePath(prefix)); const String name(Util::getFileNameExt(prefix)); #ifdef OBJ_USE_OPENMP bool bSuccess(true); #pragma omp parallel for #endif for (int_t i = 0; i < (int_t)materials.size(); ++i) { const Material& mat = materials[i]; // save material description std::stringstream ss; ss << "newmtl " << mat.name << "\n" << "Ka 1.000000 1.000000 1.000000" << "\n" << "Kd " << mat.Kd.r << " " << mat.Kd.g << " " << mat.Kd.b << "\n" << "Ks 0.000000 0.000000 0.000000" << "\n" << "Tr 1.000000" << "\n" << "illum 1" << "\n" << "Ns 1.000000" << "\n"; // save material maps if (mat.diffuse_map.empty()) { #ifdef OBJ_USE_OPENMP #pragma omp critical #endif out << ss.str(); continue; } if (mat.diffuse_name.IsEmpty()) const_cast(mat.diffuse_name) = name+"_"+mat.name+"_map_Kd."+(texLossless?"png":"jpg"); ss << "map_Kd " << mat.diffuse_name << "\n"; #ifdef OBJ_USE_OPENMP #pragma omp critical #endif out << ss.str(); const bool bRet(mat.diffuse_map.Save(pathName+mat.diffuse_name)); #ifdef OBJ_USE_OPENMP #pragma omp critical if (!bRet) bSuccess = false; #else if (!bRet) return false; #endif } #ifdef OBJ_USE_OPENMP return bSuccess; #else return true; #endif } bool ObjModel::MaterialLib::Load(const String& fileName) { const size_t numMaterials(materials.size()); std::ifstream in(fileName.c_str()); String keyword; while (in.good() && in >> keyword) { if (keyword == "newmtl") { in >> keyword; materials.push_back(Material(keyword)); } else if (keyword == "Kd") { ASSERT(numMaterials < materials.size()); Color c; in >> c.r >> c.g >> c.b; materials.back().Kd = c; } else if (keyword == "map_Kd") { ASSERT(numMaterials < materials.size()); String& diffuse_name = materials.back().diffuse_name; in >> diffuse_name; diffuse_name = Util::getFilePath(fileName) + diffuse_name; } } return numMaterials < materials.size(); } /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless, bool bUseExistingUV) const { if (vertices.empty()) return false; const String prefix(Util::getFileFullName(fileName)); const String name(Util::getFileNameExt(prefix)); if (!material_lib.Save(prefix, texLossless)) return false; if (bUseExistingUV) return true; std::ofstream out((prefix + ".obj").c_str()); if (!out.good()) return false; out << "mtllib " << name << ".mtl" << "\n"; out << std::fixed << std::setprecision(precision); for (size_t i = 0; i < vertices.size(); ++i) { out << "v " << vertices[i][0] << " " << vertices[i][1] << " " << vertices[i][2] << "\n"; } for (size_t i = 0; i < texcoords.size(); ++i) { out << "vt " << texcoords[i][0] << " " << texcoords[i][1] << "\n"; } for (size_t i = 0; i < normals.size(); ++i) { out << "vn " << normals[i][0] << " " << normals[i][1] << " " << normals[i][2] << "\n"; } for (size_t i = 0; i < groups.size(); ++i) { out << "usemtl " << groups[i].material_name << "\n"; for (size_t j = 0; j < groups[i].faces.size(); ++j) { const Face& face = groups[i].faces[j]; out << "f"; for (size_t k = 0; k < 3; ++k) { out << " " << face.vertices[k] + OBJ_INDEX_OFFSET; if (!texcoords.empty()) { out << "/" << face.texcoords[k] + OBJ_INDEX_OFFSET; if (!normals.empty()) out << "/" << face.normals[k] + OBJ_INDEX_OFFSET; } else if (!normals.empty()) out << "//" << face.normals[k] + OBJ_INDEX_OFFSET; } out << "\n"; } } return true; } bool ObjModel::Load(const String& fileName) { ASSERT(vertices.empty() && groups.empty() && material_lib.materials.empty()); std::ifstream fin(fileName.c_str()); String line, keyword; std::istringstream in; while (fin.good()) { std::getline(fin, line); if (line.empty() || line[0u] == '#') continue; in.str(line); in >> keyword; if (keyword == "v") { Vertex v; in >> v[0] >> v[1] >> v[2]; if (in.fail()) return false; vertices.push_back(v); } else if (keyword == "vt") { TexCoord vt; in >> vt[0] >> vt[1]; if (in.fail()) return false; texcoords.push_back(vt); } else if (keyword == "vn") { Normal vn; in >> vn[0] >> vn[1] >> vn[2]; if (in.fail()) return false; normals.push_back(vn); } else if (keyword == "f") { Face f; memset(&f, 0xFF, sizeof(Face)); for (size_t k = 0; k < 3; ++k) { in >> keyword; switch (sscanf(keyword, "%u/%u/%u", f.vertices+k, f.texcoords+k, f.normals+k)) { case 1: f.vertices[k] -= OBJ_INDEX_OFFSET; break; case 2: f.vertices[k] -= OBJ_INDEX_OFFSET; if (f.texcoords[k] != NO_ID) f.texcoords[k] -= OBJ_INDEX_OFFSET; if (f.normals[k] != NO_ID) f.normals[k] -= OBJ_INDEX_OFFSET; break; case 3: f.vertices[k] -= OBJ_INDEX_OFFSET; f.texcoords[k] -= OBJ_INDEX_OFFSET; f.normals[k] -= OBJ_INDEX_OFFSET; break; default: return false; } } if (in.fail()) return false; if (groups.empty()) AddGroup(""); groups.back().faces.push_back(f); } else if (keyword == "mtllib") { in >> keyword; if (!material_lib.Load(keyword)) return false; } else if (keyword == "usemtl") { Group group; in >> group.material_name; if (in.fail()) return false; groups.push_back(group); } in.clear(); } return !vertices.empty(); } bool ObjModel::LoadUV(const String& fileName, SEACAVE::cList &faceTexcoords, bool bLoadUV) { DEBUG_EXTRA("LoadUV bLoadUV=%b", bLoadUV); ASSERT(vertices.empty() && groups.empty() && material_lib.materials.empty()); std::ifstream fin(fileName.c_str()); String line, keyword; std::istringstream in; while (fin.good()) { std::getline(fin, line); if (line.empty() || line[0u] == '#') continue; in.str(line); in >> keyword; if (keyword == "v") { Vertex v; in >> v[0] >> v[1] >> v[2]; if (in.fail()) { DEBUG_EXTRA("1"); return false; } vertices.push_back(v); // DEBUG_EXTRA("10, %d", vertices.size()); } else if (keyword == "vt") { TexCoord vt; in >> vt[0] >> vt[1]; if (in.fail()) { DEBUG_EXTRA("2"); return false; } texcoords.push_back(vt); } else if (keyword == "vn") { Normal vn; in >> vn[0] >> vn[1] >> vn[2]; if (in.fail()) { DEBUG_EXTRA("3"); return false; } normals.push_back(vn); } else if (keyword == "f") { Face f; memset(&f, 0xFF, sizeof(Face)); for (size_t k = 0; k < 3; ++k) { in >> keyword; // 更健壮的解析方法,处理各种索引格式 std::vector parts; size_t start = 0, end = 0; // 按'/'分割字符串 while ((end = keyword.find('/', start)) != String::npos) { parts.push_back(keyword.substr(start, end - start)); start = end + 1; } parts.push_back(keyword.substr(start)); // 根据分割后的部分数量处理不同情况 if (parts.size() >= 1 && !parts[0].empty()) { f.vertices[k] = std::stoi(parts[0]) - OBJ_INDEX_OFFSET; } if (parts.size() >= 2 && !parts[1].empty()) { f.texcoords[k] = std::stoi(parts[1]) - OBJ_INDEX_OFFSET; } if (parts.size() >= 3 && !parts[2].empty()) { f.normals[k] = std::stoi(parts[2]) - OBJ_INDEX_OFFSET; } } if (in.fail()) { DEBUG_EXTRA("4"); return false; } if (bLoadUV) { for (int i = 0; i < 3; ++i) { if (f.texcoords[i] != NO_ID && f.texcoords[i] < texcoords.size()) { faceTexcoords.push_back(texcoords[f.texcoords[i]]); } else { // 索引无效(例如面定义中未提供vt索引或索引越界),使用默认值 faceTexcoords.push_back(Point2f(0.0f, 0.0f)); // 默认UV DEBUG_EXTRA("Invalid texcoords %d", f.texcoords[i]) } } // DEBUG_EXTRA("faceTexcoords push_back [(%f,%f),(%f,%f),(%f,%f)]", texcoords[f.texcoords[0]].x, texcoords[f.texcoords[0]].y, // texcoords[f.texcoords[1]].x, texcoords[f.texcoords[1]].y, texcoords[f.texcoords[2]].x, texcoords[f.texcoords[2]].y); } if (groups.empty()) AddGroup(""); groups.back().faces.push_back(f); // } else if (keyword == "mtllib") { // in >> keyword; // if (!material_lib.Load(keyword)) // DEBUG_EXTRA("3"); // return false; } else if (keyword == "usemtl") { Group group; in >> group.material_name; if (in.fail()) { DEBUG_EXTRA("5"); return false; } groups.push_back(group); } in.clear(); } DEBUG_EXTRA("6, vertices.size=%d, faceTexcoords.size=%d", vertices.size(), faceTexcoords.size()); return !vertices.empty(); } ObjModel::Group& ObjModel::AddGroup(const String& material_name) { groups.push_back(Group()); Group& group = groups.back(); group.material_name = material_name; if (!GetMaterial(material_name)) material_lib.materials.push_back(MaterialLib::Material(material_name)); return group; } ObjModel::MaterialLib::Material* ObjModel::GetMaterial(const String& name) { MaterialLib::Materials::iterator it(std::find_if(material_lib.materials.begin(), material_lib.materials.end(), [&name](const MaterialLib::Material& mat) { return mat.name == name; })); if (it == material_lib.materials.end()) return NULL; return &(*it); } /*----------------------------------------------------------------*/