nori/src/obj.cpp

176 lines
5.8 KiB
C++

/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <nori/mesh.h>
#include <nori/timer.h>
#include <filesystem/resolver.h>
#include <unordered_map>
#include <fstream>
NORI_NAMESPACE_BEGIN
/**
* \brief Loader for Wavefront OBJ triangle meshes
*/
class WavefrontOBJ : public Mesh {
public:
WavefrontOBJ(const PropertyList &propList) {
typedef std::unordered_map<OBJVertex, uint32_t, OBJVertexHash> VertexMap;
filesystem::path filename =
getFileResolver()->resolve(propList.getString("filename"));
std::ifstream is(filename.str());
if (is.fail())
throw NoriException("Unable to open OBJ file \"%s\"!", filename);
Transform trafo = propList.getTransform("toWorld", Transform());
cout << "Loading \"" << filename << "\" .. ";
cout.flush();
Timer timer;
std::vector<Vector3f> positions;
std::vector<Vector2f> texcoords;
std::vector<Vector3f> normals;
std::vector<uint32_t> indices;
std::vector<OBJVertex> vertices;
VertexMap vertexMap;
std::string line_str;
while (std::getline(is, line_str)) {
std::istringstream line(line_str);
std::string prefix;
line >> prefix;
if (prefix == "v") {
Point3f p;
line >> p.x() >> p.y() >> p.z();
p = trafo * p;
m_bbox.expandBy(p);
positions.push_back(p);
} else if (prefix == "vt") {
Point2f tc;
line >> tc.x() >> tc.y();
texcoords.push_back(tc);
} else if (prefix == "vn") {
Normal3f n;
line >> n.x() >> n.y() >> n.z();
normals.push_back((trafo * n).normalized());
} else if (prefix == "f") {
std::string v1, v2, v3, v4;
line >> v1 >> v2 >> v3 >> v4;
OBJVertex verts[6];
int nVertices = 3;
verts[0] = OBJVertex(v1);
verts[1] = OBJVertex(v2);
verts[2] = OBJVertex(v3);
if (!v4.empty()) {
/* This is a quad, split into two triangles */
verts[3] = OBJVertex(v4);
verts[4] = verts[0];
verts[5] = verts[2];
nVertices = 6;
}
/* Convert to an indexed vertex list */
for (int i=0; i<nVertices; ++i) {
const OBJVertex &v = verts[i];
VertexMap::const_iterator it = vertexMap.find(v);
if (it == vertexMap.end()) {
vertexMap[v] = (uint32_t) vertices.size();
indices.push_back((uint32_t) vertices.size());
vertices.push_back(v);
} else {
indices.push_back(it->second);
}
}
}
}
m_F.resize(3, indices.size()/3);
memcpy(m_F.data(), indices.data(), sizeof(uint32_t)*indices.size());
m_V.resize(3, vertices.size());
for (uint32_t i=0; i<vertices.size(); ++i)
m_V.col(i) = positions.at(vertices[i].p-1);
if (!normals.empty()) {
m_N.resize(3, vertices.size());
for (uint32_t i=0; i<vertices.size(); ++i)
m_N.col(i) = normals.at(vertices[i].n-1);
}
if (!texcoords.empty()) {
m_UV.resize(2, vertices.size());
for (uint32_t i=0; i<vertices.size(); ++i)
m_UV.col(i) = texcoords.at(vertices[i].uv-1);
}
m_name = filename.str();
cout << "done. (V=" << m_V.cols() << ", F=" << m_F.cols() << ", took "
<< timer.elapsedString() << " and "
<< memString(m_F.size() * sizeof(uint32_t) +
sizeof(float) * (m_V.size() + m_N.size() + m_UV.size()))
<< ")" << endl;
}
protected:
/// Vertex indices used by the OBJ format
struct OBJVertex {
uint32_t p = (uint32_t) -1;
uint32_t n = (uint32_t) -1;
uint32_t uv = (uint32_t) -1;
inline OBJVertex() { }
inline OBJVertex(const std::string &string) {
std::vector<std::string> tokens = tokenize(string, "/", true);
if (tokens.size() < 1 || tokens.size() > 3)
throw NoriException("Invalid vertex data: \"%s\"", string);
p = toUInt(tokens[0]);
if (tokens.size() >= 2 && !tokens[1].empty())
uv = toUInt(tokens[1]);
if (tokens.size() >= 3 && !tokens[2].empty())
n = toUInt(tokens[2]);
}
inline bool operator==(const OBJVertex &v) const {
return v.p == p && v.n == n && v.uv == uv;
}
};
/// Hash function for OBJVertex
struct OBJVertexHash {
std::size_t operator()(const OBJVertex &v) const {
size_t hash = std::hash<uint32_t>()(v.p);
hash = hash * 37 + std::hash<uint32_t>()(v.uv);
hash = hash * 37 + std::hash<uint32_t>()(v.n);
return hash;
}
};
};
NORI_REGISTER_CLASS(WavefrontOBJ, "obj");
NORI_NAMESPACE_END