Nori  23
obj.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob
5 
6  Nori is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Nori is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <nori/mesh.h>
20 #include <nori/timer.h>
21 #include <filesystem/resolver.h>
22 #include <unordered_map>
23 #include <fstream>
24 
25 NORI_NAMESPACE_BEGIN
26 
30 class WavefrontOBJ : public Mesh {
31 public:
32  WavefrontOBJ(const PropertyList &propList) {
33  typedef std::unordered_map<OBJVertex, uint32_t, OBJVertexHash> VertexMap;
34 
35  filesystem::path filename =
36  getFileResolver()->resolve(propList.getString("filename"));
37 
38  std::ifstream is(filename.str());
39  if (is.fail())
40  throw NoriException("Unable to open OBJ file \"%s\"!", filename);
41  Transform trafo = propList.getTransform("toWorld", Transform());
42 
43  cout << "Loading \"" << filename << "\" .. ";
44  cout.flush();
45  Timer timer;
46 
47  std::vector<Vector3f> positions;
48  std::vector<Vector2f> texcoords;
49  std::vector<Vector3f> normals;
50  std::vector<uint32_t> indices;
51  std::vector<OBJVertex> vertices;
52  VertexMap vertexMap;
53 
54  std::string line_str;
55  while (std::getline(is, line_str)) {
56  std::istringstream line(line_str);
57 
58  std::string prefix;
59  line >> prefix;
60 
61  if (prefix == "v") {
62  Point3f p;
63  line >> p.x() >> p.y() >> p.z();
64  p = trafo * p;
65  m_bbox.expandBy(p);
66  positions.push_back(p);
67  } else if (prefix == "vt") {
68  Point2f tc;
69  line >> tc.x() >> tc.y();
70  texcoords.push_back(tc);
71  } else if (prefix == "vn") {
72  Normal3f n;
73  line >> n.x() >> n.y() >> n.z();
74  normals.push_back((trafo * n).normalized());
75  } else if (prefix == "f") {
76  std::string v1, v2, v3, v4;
77  line >> v1 >> v2 >> v3 >> v4;
78  OBJVertex verts[6];
79  int nVertices = 3;
80 
81  verts[0] = OBJVertex(v1);
82  verts[1] = OBJVertex(v2);
83  verts[2] = OBJVertex(v3);
84 
85  if (!v4.empty()) {
86  /* This is a quad, split into two triangles */
87  verts[3] = OBJVertex(v4);
88  verts[4] = verts[0];
89  verts[5] = verts[2];
90  nVertices = 6;
91  }
92  /* Convert to an indexed vertex list */
93  for (int i=0; i<nVertices; ++i) {
94  const OBJVertex &v = verts[i];
95  VertexMap::const_iterator it = vertexMap.find(v);
96  if (it == vertexMap.end()) {
97  vertexMap[v] = (uint32_t) vertices.size();
98  indices.push_back((uint32_t) vertices.size());
99  vertices.push_back(v);
100  } else {
101  indices.push_back(it->second);
102  }
103  }
104  }
105  }
106 
107  m_F.resize(3, indices.size()/3);
108  memcpy(m_F.data(), indices.data(), sizeof(uint32_t)*indices.size());
109 
110  m_V.resize(3, vertices.size());
111  for (uint32_t i=0; i<vertices.size(); ++i)
112  m_V.col(i) = positions.at(vertices[i].p-1);
113 
114  if (!normals.empty()) {
115  m_N.resize(3, vertices.size());
116  for (uint32_t i=0; i<vertices.size(); ++i)
117  m_N.col(i) = normals.at(vertices[i].n-1);
118  }
119 
120  if (!texcoords.empty()) {
121  m_UV.resize(2, vertices.size());
122  for (uint32_t i=0; i<vertices.size(); ++i)
123  m_UV.col(i) = texcoords.at(vertices[i].uv-1);
124  }
125 
126  size_t meshSize = m_F.size() * sizeof(uint32_t) +
127  sizeof(float) * (m_V.size() + m_N.size() + m_UV.size());
128 
129  if (meshSize == 0) {
130  cout << endl;
131  throw NoriException("OBJ file \"%s\" contains no data! Make sure you have Git LFS installed", filename);
132  }
133 
134  m_name = filename.str();
135  cout << "done. (V=" << m_V.cols() << ", F=" << m_F.cols() << ", took "
136  << timer.elapsedString() << " and "
137  << memString(meshSize)
138  << ")" << endl;
139  }
140 
141 protected:
143  struct OBJVertex {
144  uint32_t p = (uint32_t) -1;
145  uint32_t n = (uint32_t) -1;
146  uint32_t uv = (uint32_t) -1;
147 
148  inline OBJVertex() { }
149 
150  inline OBJVertex(const std::string &string) {
151  std::vector<std::string> tokens = tokenize(string, "/", true);
152 
153  if (tokens.size() < 1 || tokens.size() > 3)
154  throw NoriException("Invalid vertex data: \"%s\"", string);
155 
156  p = toUInt(tokens[0]);
157 
158  if (tokens.size() >= 2 && !tokens[1].empty())
159  uv = toUInt(tokens[1]);
160 
161  if (tokens.size() >= 3 && !tokens[2].empty())
162  n = toUInt(tokens[2]);
163  }
164 
165  inline bool operator==(const OBJVertex &v) const {
166  return v.p == p && v.n == n && v.uv == uv;
167  }
168  };
169 
171  struct OBJVertexHash {
172  std::size_t operator()(const OBJVertex& v) const {
173  size_t hash = std::hash<uint32_t>()(v.p);
174  hash = hash * 37 + std::hash<uint32_t>()(v.uv);
175  hash = hash * 37 + std::hash<uint32_t>()(v.n);
176  return hash;
177  }
178  };
179 };
180 
181 NORI_REGISTER_CLASS(WavefrontOBJ, "obj");
182 NORI_NAMESPACE_END
Triangle mesh.
Definition: mesh.h:35
MatrixXf m_UV
Vertex texture coordinates.
Definition: mesh.h:122
MatrixXf m_N
Vertex normals.
Definition: mesh.h:121
std::string m_name
Identifying name.
Definition: mesh.h:119
MatrixXf m_V
Vertex positions.
Definition: mesh.h:120
MatrixXu m_F
Faces.
Definition: mesh.h:123
Simple exception class, which stores a human-readable error description.
Definition: common.h:148
This is an associative container used to supply the constructors of NoriObject subclasses with parame...
Definition: proplist.h:32
Transform getTransform(const std::string &name) const
Get a transform property, and throw an exception if it does not exist.
std::string getString(const std::string &name) const
Get a string property, and throw an exception if it does not exist.
BoundingBox3f m_bbox
Bounding box of the mesh.
Definition: shape.h:159
Simple timer with millisecond precision.
Definition: timer.h:32
std::string elapsedString(bool precise=false) const
Like elapsed(), but return a human-readable string.
Definition: timer.h:48
Loader for Wavefront OBJ triangle meshes.
Definition: obj.cpp:30
3-dimensional surface normal representation
Definition: vector.h:133
void expandBy(const PointType &p)
Expand the bounding box to contain another point.
Definition: bbox.h:288
Homogeneous coordinate transformation.
Definition: transform.h:35
Hash function for OBJVertex.
Definition: obj.cpp:171
Vertex indices used by the OBJ format.
Definition: obj.cpp:143