418 lines
11 KiB
C++
418 lines
11 KiB
C++
////////////////////////////////////////////////////////////////////
|
|
// 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<String&>(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<TexCoord,const TexCoord&,0,8192,uint32_t> &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<String> 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);
|
|
}
|
|
/*----------------------------------------------------------------*/
|