338 lines
9.8 KiB
C++
338 lines
9.8 KiB
C++
#include <mitsuba/render/trimesh.h>
|
|
#include <mitsuba/core/fresolver.h>
|
|
#include <mitsuba/core/plugin.h>
|
|
#include <fstream>
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* Wavefront OBJ triangle mesh loader
|
|
*/
|
|
class WavefrontOBJ : public TriMesh {
|
|
public:
|
|
struct OBJTriangle {
|
|
unsigned int v[3];
|
|
unsigned int n[3];
|
|
unsigned int uv[3];
|
|
};
|
|
|
|
WavefrontOBJ(const Properties &props) : TriMesh(props) {
|
|
m_name = FileResolver::getInstance()->resolve(props.getString("filename"));
|
|
|
|
/* Load the geometry */
|
|
Log(EInfo, "Loading geometry from \"%s\" ..", m_name.c_str());
|
|
std::ifstream is(m_name.c_str());
|
|
if (is.bad() || is.fail())
|
|
Log(EError, "Geometry file '%s' not found!", m_name.c_str());
|
|
|
|
std::string buf;
|
|
std::vector<Point> vertices;
|
|
std::vector<Normal> normals;
|
|
std::vector<Point2> texcoords;
|
|
std::vector<OBJTriangle> triangles;
|
|
bool hasNormals = false, hasTexcoords = false;
|
|
bool firstVertex = true;
|
|
BSDF *currentMaterial = NULL;
|
|
|
|
std::string name = m_name;
|
|
|
|
while (is >> buf) {
|
|
if (buf == "v") {
|
|
/* Parse + transform vertices */
|
|
Point p;
|
|
is >> p.x >> p.y >> p.z;
|
|
vertices.push_back(m_objectToWorld(p));
|
|
if (firstVertex) {
|
|
if (triangles.size() > 0) {
|
|
generateGeometry(name, vertices, normals, texcoords,
|
|
triangles, hasNormals, hasTexcoords, currentMaterial);
|
|
triangles.clear();
|
|
}
|
|
hasNormals = false;
|
|
hasTexcoords = false;
|
|
firstVertex = false;
|
|
}
|
|
} else if (buf == "vn") {
|
|
Normal n;
|
|
is >> n.x >> n.y >> n.z;
|
|
if (!n.isZero())
|
|
normals.push_back(normalize(m_objectToWorld(n)));
|
|
else
|
|
normals.push_back(n);
|
|
hasNormals = true;
|
|
} else if (buf == "g") {
|
|
std::string line;
|
|
std::getline(is, line);
|
|
if (line.length() > 2) {
|
|
name = trim(line.substr(1, line.length()-1));
|
|
Log(EInfo, "Loading geometry \"%s\"", name.c_str());
|
|
}
|
|
} else if (buf == "usemtl") {
|
|
std::string line;
|
|
std::getline(is, line);
|
|
std::string materialName = trim(line.substr(1, line.length()-1));
|
|
if (m_materials.find(materialName) != m_materials.end())
|
|
currentMaterial = m_materials[materialName];
|
|
else
|
|
currentMaterial = NULL;
|
|
} else if (buf == "mtllib") {
|
|
std::string line;
|
|
std::getline(is, line);
|
|
std::string mtlName = trim(line.substr(1, line.length()-1));
|
|
ref<FileResolver> fRes = FileResolver::getInstance()->clone();
|
|
fRes->addPathFromFile(fRes->resolveAbsolute(props.getString("filename")));
|
|
std::string fullMtlName = fRes->resolve(mtlName);
|
|
if (FileStream::exists(fullMtlName))
|
|
parseMaterials(fullMtlName);
|
|
else
|
|
Log(EWarn, "Could not find referenced material library '%s'", mtlName.c_str());
|
|
} else if (buf == "vt") {
|
|
std::string line;
|
|
Float u, v, w;
|
|
std::getline(is, line);
|
|
std::istringstream iss(line);
|
|
iss >> u >> v >> w;
|
|
texcoords.push_back(Point2(u, v));
|
|
hasTexcoords = true;
|
|
} else if (buf == "f") {
|
|
std::string line, tmp;
|
|
std::getline(is, line);
|
|
std::istringstream iss(line);
|
|
firstVertex = true;
|
|
OBJTriangle t;
|
|
iss >> tmp; parse(t, 0, tmp);
|
|
iss >> tmp; parse(t, 1, tmp);
|
|
iss >> tmp; parse(t, 2, tmp);
|
|
triangles.push_back(t);
|
|
if (iss >> tmp) {
|
|
parse(t, 1, tmp);
|
|
std::swap(t.v[0], t.v[1]);
|
|
std::swap(t.uv[0], t.uv[1]);
|
|
std::swap(t.n[0], t.n[1]);
|
|
triangles.push_back(t);
|
|
}
|
|
} else {
|
|
/* Ignore */
|
|
std::string line;
|
|
std::getline(is, line);
|
|
}
|
|
}
|
|
|
|
generateGeometry(name, vertices, normals, texcoords,
|
|
triangles, hasNormals, hasTexcoords, currentMaterial);
|
|
}
|
|
|
|
void parse(OBJTriangle &t, int i, const std::string &str) {
|
|
std::vector<std::string> tokens = tokenize(str, "/");
|
|
if (tokens.size() == 1) {
|
|
t.v[i] = atoi(tokens[0].c_str())-1;
|
|
} else if (tokens.size() == 2) {
|
|
if (str.find("//") == std::string::npos) {
|
|
t.v[i] = atoi(tokens[0].c_str())-1;
|
|
t.uv[i] = atoi(tokens[1].c_str())-1;
|
|
} else {
|
|
t.v[i] = atoi(tokens[0].c_str())-1;
|
|
t.n[i] = atoi(tokens[1].c_str())-1;
|
|
}
|
|
} else if (tokens.size() == 3) {
|
|
t.v[i] = atoi(tokens[0].c_str())-1;
|
|
t.uv[i] = atoi(tokens[1].c_str())-1;
|
|
t.n[i] = atoi(tokens[2].c_str())-1;
|
|
} else {
|
|
Log(EError, "Invalid OBJ face format!");
|
|
}
|
|
}
|
|
|
|
void parseMaterials(const std::string &mtlFileName) {
|
|
Log(EInfo, "Loading OBJ materials from \"%s\" ..", mtlFileName.c_str());
|
|
std::ifstream is(mtlFileName.c_str());
|
|
if (is.bad() || is.fail())
|
|
Log(EError, "Unexpected I/O error while accessing material file '%s'!", mtlFileName.c_str());
|
|
std::string buf;
|
|
std::string mtlName;
|
|
Spectrum diffuse;
|
|
|
|
while (is >> buf) {
|
|
if (buf == "newmtl") {
|
|
if (mtlName != "")
|
|
addMaterial(mtlName, diffuse);
|
|
|
|
std::string line, tmp;
|
|
std::getline(is, line);
|
|
mtlName = trim(line.substr(1, line.length()-1));
|
|
} else if (buf == "Kd") {
|
|
Float r, g, b;
|
|
is >> r >> g >> b;
|
|
diffuse.fromSRGB(r, g, b);
|
|
} else {
|
|
/* Ignore */
|
|
std::string line;
|
|
std::getline(is, line);
|
|
}
|
|
}
|
|
addMaterial(mtlName, diffuse);
|
|
}
|
|
|
|
void addMaterial(const std::string &name, const Spectrum &diffuse) {
|
|
Properties props("lambertian");
|
|
props.setSpectrum("reflectance", diffuse);
|
|
props.setID(name);
|
|
BSDF *bsdf = static_cast<BSDF *> (PluginManager::getInstance()->
|
|
createObject(BSDF::m_theClass, props));
|
|
bsdf->incRef();
|
|
m_materials[name] = bsdf;
|
|
}
|
|
|
|
/// For using vertices as keys in an associative structure
|
|
struct vertex_key_order : public
|
|
std::binary_function<Vertex, Vertex, bool> {
|
|
public:
|
|
bool operator()(const Vertex &v1, const Vertex &v2) const {
|
|
if (v1.v.x < v2.v.x) return true;
|
|
else if (v1.v.x > v2.v.x) return false;
|
|
if (v1.v.y < v2.v.y) return true;
|
|
else if (v1.v.y > v2.v.y) return false;
|
|
if (v1.v.z < v2.v.z) return true;
|
|
else if (v1.v.z > v2.v.z) return false;
|
|
if (v1.n.x < v2.n.x) return true;
|
|
else if (v1.n.x > v2.n.x) return false;
|
|
if (v1.n.y < v2.n.y) return true;
|
|
else if (v1.n.y > v2.n.y) return false;
|
|
if (v1.n.z < v2.n.z) return true;
|
|
else if (v1.n.z > v2.n.z) return false;
|
|
if (v1.uv.x < v2.uv.x) return true;
|
|
else if (v1.uv.x > v2.uv.x) return false;
|
|
if (v1.uv.y < v2.uv.y) return true;
|
|
else if (v1.uv.y > v2.uv.y) return false;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
void generateGeometry(const std::string &name,
|
|
const std::vector<Point> &vertices,
|
|
const std::vector<Normal> &normals,
|
|
const std::vector<Point2> &texcoords,
|
|
const std::vector<OBJTriangle> &triangles,
|
|
bool hasNormals, bool hasTexcoords,
|
|
BSDF *currentMaterial) {
|
|
if (triangles.size() == 0)
|
|
return;
|
|
|
|
std::map<Vertex, int, vertex_key_order> vertexMap;
|
|
std::vector<Vertex> vertexBuffer;
|
|
size_t numMerged = 0;
|
|
|
|
/* Collapse the mesh into a more usable form */
|
|
Triangle *triangleArray = new Triangle[triangles.size()];
|
|
for (unsigned int i=0; i<triangles.size(); i++) {
|
|
Triangle tri;
|
|
for (unsigned int j=0; j<3; j++) {
|
|
unsigned int vertexId = triangles[i].v[j];
|
|
unsigned int normalId = triangles[i].n[j];
|
|
unsigned int uvId = triangles[i].uv[j];
|
|
int key;
|
|
|
|
Vertex vertex;
|
|
vertex.v = vertices.at(vertexId);
|
|
if (hasNormals)
|
|
vertex.n = normals.at(normalId);
|
|
if (hasTexcoords)
|
|
vertex.uv = texcoords.at(uvId);
|
|
|
|
if (vertexMap.find(vertex) != vertexMap.end()) {
|
|
key = vertexMap[vertex];
|
|
numMerged++;
|
|
} else {
|
|
key = (int) vertexBuffer.size();
|
|
vertexMap[vertex] = (int) key;
|
|
vertexBuffer.push_back(vertex);
|
|
}
|
|
|
|
tri.idx[j] = key;
|
|
}
|
|
triangleArray[i] = tri;
|
|
}
|
|
|
|
Vertex *vertexArray = new Vertex[vertexBuffer.size()];
|
|
for (unsigned int i=0; i<vertexBuffer.size(); i++)
|
|
vertexArray[i] = vertexBuffer[i];
|
|
|
|
ref<TriMesh> mesh = new TriMesh(name, m_worldToObject, triangleArray,
|
|
triangles.size(), vertexArray, vertexBuffer.size());
|
|
mesh->incRef();
|
|
mesh->calculateTangentSpaceBasis(hasNormals, hasTexcoords);
|
|
if (currentMaterial)
|
|
mesh->addChild("", currentMaterial);
|
|
m_meshes.push_back(mesh);
|
|
SLog(EInfo, "%s: Loaded " SIZE_T_FMT " triangles, " SIZE_T_FMT
|
|
" vertices (merged " SIZE_T_FMT " vertices).", name.c_str(),
|
|
triangles.size(), vertexBuffer.size(), numMerged);
|
|
}
|
|
|
|
WavefrontOBJ(Stream *stream, InstanceManager *manager) : TriMesh(stream, manager) {
|
|
}
|
|
|
|
virtual ~WavefrontOBJ() {
|
|
for (size_t i=0; i<m_meshes.size(); ++i)
|
|
m_meshes[i]->decRef();
|
|
for (std::map<std::string, BSDF *>::iterator it = m_materials.begin();
|
|
it != m_materials.end(); ++it) {
|
|
(*it).second->decRef();
|
|
}
|
|
}
|
|
|
|
void configure() {
|
|
Shape::configure();
|
|
|
|
m_aabb.reset();
|
|
for (size_t i=0; i<m_meshes.size(); ++i) {
|
|
m_meshes[i]->configure();
|
|
m_aabb.expandBy(m_meshes[i]->getAABB());
|
|
}
|
|
}
|
|
|
|
void addChild(const std::string &name, ConfigurableObject *child) {
|
|
const Class *cClass = child->getClass();
|
|
if (cClass->derivesFrom(BSDF::m_theClass)) {
|
|
m_bsdf = static_cast<BSDF *>(child);
|
|
for (size_t i=0; i<m_meshes.size(); ++i)
|
|
m_meshes[i]->addChild(name, child);
|
|
} else if (cClass->derivesFrom(Luminaire::m_theClass)) {
|
|
Assert(m_luminaire == NULL && m_meshes.size() == 1);
|
|
m_luminaire = static_cast<Luminaire *>(child);
|
|
for (size_t i=0; i<m_meshes.size(); ++i) {
|
|
child->setParent(m_meshes[i]);
|
|
m_meshes[i]->addChild(name, child);
|
|
}
|
|
} else if (cClass->derivesFrom(Subsurface::m_theClass)) {
|
|
Assert(m_subsurface == NULL);
|
|
m_subsurface = static_cast<Subsurface *>(child);
|
|
for (size_t i=0; i<m_meshes.size(); ++i) {
|
|
child->setParent(m_meshes[i]);
|
|
m_meshes[i]->addChild(name, child);
|
|
}
|
|
} else {
|
|
Shape::addChild(name, child);
|
|
}
|
|
}
|
|
|
|
bool isCompound() const {
|
|
return true;
|
|
}
|
|
|
|
Shape *getElement(int index) {
|
|
if (index >= (int) m_meshes.size())
|
|
return NULL;
|
|
return m_meshes[index];
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
private:
|
|
std::vector<TriMesh *> m_meshes;
|
|
std::map<std::string, BSDF *> m_materials;
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS_S(WavefrontOBJ, false, TriMesh)
|
|
MTS_EXPORT_PLUGIN(WavefrontOBJ, "OBJ triangle mesh loader");
|
|
MTS_NAMESPACE_END
|