Nori  24
parser.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob, Romain Prévost
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/parser.h>
20 #include <nori/proplist.h>
21 #include <Eigen/Geometry>
22 #include <pugixml.hpp>
23 #include <fstream>
24 #include <set>
25 
26 NORI_NAMESPACE_BEGIN
27 
28 NoriObject *loadFromXML(const std::string &filename) {
29  /* Load the XML file using 'pugi' (a tiny self-contained XML parser implemented in C++) */
30  pugi::xml_document doc;
31  pugi::xml_parse_result result = doc.load_file(filename.c_str());
32 
33  /* Helper function: map a position offset in bytes to a more readable row/column value */
34  auto offset = [&](ptrdiff_t pos) -> std::string {
35  std::fstream is(filename);
36  char buffer[1024];
37  int line = 0, linestart = 0, offset = 0;
38  while (is.good()) {
39  is.read(buffer, sizeof(buffer));
40  for (int i = 0; i < is.gcount(); ++i) {
41  if (buffer[i] == '\n') {
42  if (offset + i >= pos)
43  return tfm::format("row %i, col %i", line + 1, pos - linestart);
44  ++line;
45  linestart = offset + i;
46  }
47  }
48  offset += (int) is.gcount();
49  }
50  return "byte offset " + std::to_string(pos);
51  };
52 
53  if (!result) /* There was a parser / file IO error */
54  throw NoriException("Error while parsing \"%s\": %s (at %s)", filename, result.description(), offset(result.offset));
55 
56  /* Set of supported XML tags */
57  enum ETag {
58  /* Object classes */
59  EScene = NoriObject::EScene,
60  EMesh = NoriObject::EMesh,
61  ETexture = NoriObject::ETexture,
62  EBSDF = NoriObject::EBSDF,
63  EPhaseFunction = NoriObject::EPhaseFunction,
64  EEmitter = NoriObject::EEmitter,
65  EMedium = NoriObject::EMedium,
66  ECamera = NoriObject::ECamera,
67  EIntegrator = NoriObject::EIntegrator,
68  ESampler = NoriObject::ESampler,
69  ETest = NoriObject::ETest,
70  EReconstructionFilter = NoriObject::EReconstructionFilter,
71 
72  /* Properties */
73  EBoolean = NoriObject::EClassTypeCount,
74  EInteger,
75  EFloat,
76  EString,
77  EPoint,
78  EVector,
79  EColor,
80  ETransform,
81  ETranslate,
82  EMatrix,
83  ERotate,
84  EScale,
85  ELookAt,
86 
87  EInvalid
88  };
89 
90  /* Create a mapping from tag names to tag IDs */
91  std::map<std::string, ETag> tags;
92  tags["scene"] = EScene;
93  tags["mesh"] = EMesh;
94  tags["texture"] = ETexture;
95  tags["bsdf"] = EBSDF;
96  tags["emitter"] = EEmitter;
97  tags["camera"] = ECamera;
98  tags["medium"] = EMedium;
99  tags["phase"] = EPhaseFunction;
100  tags["integrator"] = EIntegrator;
101  tags["sampler"] = ESampler;
102  tags["rfilter"] = EReconstructionFilter;
103  tags["test"] = ETest;
104  tags["boolean"] = EBoolean;
105  tags["integer"] = EInteger;
106  tags["float"] = EFloat;
107  tags["string"] = EString;
108  tags["point"] = EPoint;
109  tags["vector"] = EVector;
110  tags["color"] = EColor;
111  tags["transform"] = ETransform;
112  tags["translate"] = ETranslate;
113  tags["matrix"] = EMatrix;
114  tags["rotate"] = ERotate;
115  tags["scale"] = EScale;
116  tags["lookat"] = ELookAt;
117 
118  /* Helper function to check if attributes are fully specified */
119  auto check_attributes = [&](const pugi::xml_node &node, std::set<std::string> attrs) {
120  for (auto attr : node.attributes()) {
121  auto it = attrs.find(attr.name());
122  if (it == attrs.end())
123  throw NoriException("Error while parsing \"%s\": unexpected attribute \"%s\" in \"%s\" at %s",
124  filename, attr.name(), node.name(), offset(node.offset_debug()));
125  attrs.erase(it);
126  }
127  if (!attrs.empty())
128  throw NoriException("Error while parsing \"%s\": missing attribute \"%s\" in \"%s\" at %s",
129  filename, *attrs.begin(), node.name(), offset(node.offset_debug()));
130  };
131 
132  Eigen::Affine3f transform;
133 
134  /* Helper function to parse a Nori XML node (recursive) */
135  std::function<NoriObject *(pugi::xml_node &, PropertyList &, int)> parseTag = [&](
136  pugi::xml_node &node, PropertyList &list, int parentTag) -> NoriObject * {
137  /* Skip over comments */
138  if (node.type() == pugi::node_comment || node.type() == pugi::node_declaration)
139  return nullptr;
140 
141  if (node.type() != pugi::node_element)
142  throw NoriException(
143  "Error while parsing \"%s\": unexpected content at %s",
144  filename, offset(node.offset_debug()));
145 
146  /* Look up the name of the current element */
147  auto it = tags.find(node.name());
148  if (it == tags.end())
149  throw NoriException("Error while parsing \"%s\": unexpected tag \"%s\" at %s",
150  filename, node.name(), offset(node.offset_debug()));
151  int tag = it->second;
152 
153  /* Perform some safety checks to make sure that the XML tree really makes sense */
154  bool hasParent = parentTag != EInvalid;
155  bool parentIsObject = hasParent && parentTag < NoriObject::EClassTypeCount;
156  bool currentIsObject = tag < NoriObject::EClassTypeCount;
157  bool parentIsTransform = parentTag == ETransform;
158  bool currentIsTransformOp = tag == ETranslate || tag == ERotate || tag == EScale || tag == ELookAt || tag == EMatrix;
159 
160  if (!hasParent && !currentIsObject)
161  throw NoriException("Error while parsing \"%s\": root element \"%s\" must be a Nori object (at %s)",
162  filename, node.name(), offset(node.offset_debug()));
163 
164  if (parentIsTransform != currentIsTransformOp)
165  throw NoriException("Error while parsing \"%s\": transform nodes "
166  "can only contain transform operations (at %s)",
167  filename, offset(node.offset_debug()));
168 
169  if (hasParent && !parentIsObject && !(parentIsTransform && currentIsTransformOp))
170  throw NoriException("Error while parsing \"%s\": node \"%s\" requires a Nori object as parent (at %s)",
171  filename, node.name(), offset(node.offset_debug()));
172 
173  if (tag == EScene)
174  node.append_attribute("type") = "scene";
175  else if (tag == ETransform)
176  transform.setIdentity();
177 
178  PropertyList propList;
179  std::vector<NoriObject *> children;
180  for (pugi::xml_node &ch: node.children()) {
181  NoriObject *child = parseTag(ch, propList, tag);
182  if (child)
183  children.push_back(child);
184  }
185 
186  NoriObject *result = nullptr;
187  try {
188  if (currentIsObject) {
189  //check_attributes(node, { "type" });
190 
191  /* This is an object, first instantiate it */
193  node.attribute("type").value(),
194  propList
195  );
196 
197  if (result->getClassType() != (int) tag) {
198  throw NoriException(
199  "Unexpectedly constructed an object "
200  "of type <%s> (expected type <%s>): %s",
202  NoriObject::classTypeName((NoriObject::EClassType) tag),
203  result->toString());
204  }
205 
206  // set the name to help parent decide what to do with this node
207  result->setIdName(node.attribute("name").value());
208 
209  /* Add all children */
210  for (auto ch: children) {
211  result->addChild(ch);
212  ch->setParent(result);
213  }
214 
215  /* Activate / configure the object */
216  result->activate();
217  } else {
218  /* This is a property */
219  switch (tag) {
220  case EString: {
221  check_attributes(node, { "name", "value" });
222  list.setString(node.attribute("name").value(), node.attribute("value").value());
223  }
224  break;
225  case EFloat: {
226  check_attributes(node, { "name", "value" });
227  list.setFloat(node.attribute("name").value(), toFloat(node.attribute("value").value()));
228  }
229  break;
230  case EInteger: {
231  check_attributes(node, { "name", "value" });
232  list.setInteger(node.attribute("name").value(), toInt(node.attribute("value").value()));
233  }
234  break;
235  case EBoolean: {
236  check_attributes(node, { "name", "value" });
237  list.setBoolean(node.attribute("name").value(), toBool(node.attribute("value").value()));
238  }
239  break;
240  case EPoint: {
241  check_attributes(node, { "name", "value" });
242  auto name = node.attribute("name").value();
243  auto val = node.attribute("value").value();
244  auto n = vectorSize(val);
245  if(n == 2)
246  list.setPoint2(name, Point2f(toVector2f(val)));
247  else if(n == 3)
248  list.setPoint3(name, Point3f(toVector3f(val)));
249  else
250  throw NoriException("Point %s (value: %s) is not of size 2 or 3", name, val);
251  }
252  break;
253  case EVector: {
254  check_attributes(node, { "name", "value" });
255  auto name = node.attribute("name").value();
256  auto val = node.attribute("value").value();
257  auto n = vectorSize(val);
258  if(n == 2)
259  list.setVector2(name, Vector2f(toVector2f(val)));
260  else if(n == 3)
261  list.setVector3(name, Vector3f(toVector3f(val)));
262  else
263  throw NoriException("Vector %s (value: %s) is not of size 2 or 3", name, val);
264  }
265  break;
266  case EColor: {
267  check_attributes(node, { "name", "value" });
268  list.setColor(node.attribute("name").value(), Color3f(toVector3f(node.attribute("value").value()).array()));
269  }
270  break;
271  case ETransform: {
272  check_attributes(node, { "name" });
273  list.setTransform(node.attribute("name").value(), transform.matrix());
274  }
275  break;
276  case ETranslate: {
277  check_attributes(node, { "value" });
278  Eigen::Vector3f v = toVector3f(node.attribute("value").value());
279  transform = Eigen::Translation<float, 3>(v.x(), v.y(), v.z()) * transform;
280  }
281  break;
282  case EMatrix: {
283  check_attributes(node, { "value" });
284  std::vector<std::string> tokens = tokenize(node.attribute("value").value());
285  if (tokens.size() != 16)
286  throw NoriException("Expected 16 values");
287  Eigen::Matrix4f matrix;
288  for (int i=0; i<4; ++i)
289  for (int j=0; j<4; ++j)
290  matrix(i, j) = toFloat(tokens[i*4+j]);
291  transform = Eigen::Affine3f(matrix) * transform;
292  }
293  break;
294  case EScale: {
295  check_attributes(node, { "value" });
296  Eigen::Vector3f v = toVector3f(node.attribute("value").value());
297  transform = Eigen::DiagonalMatrix<float, 3>(v) * transform;
298  }
299  break;
300  case ERotate: {
301  check_attributes(node, { "angle", "axis" });
302  float angle = degToRad(toFloat(node.attribute("angle").value()));
303  Eigen::Vector3f axis = toVector3f(node.attribute("axis").value());
304  transform = Eigen::AngleAxis<float>(angle, axis) * transform;
305  }
306  break;
307  case ELookAt: {
308  check_attributes(node, { "origin", "target", "up" });
309  Eigen::Vector3f origin = toVector3f(node.attribute("origin").value());
310  Eigen::Vector3f target = toVector3f(node.attribute("target").value());
311  Eigen::Vector3f up = toVector3f(node.attribute("up").value());
312 
313  Vector3f dir = (target - origin).normalized();
314  Vector3f left = up.normalized().cross(dir).normalized();
315  Vector3f newUp = dir.cross(left).normalized();
316 
317  Eigen::Matrix4f trafo;
318  trafo << left, newUp, dir, origin,
319  0, 0, 0, 1;
320 
321  transform = Eigen::Affine3f(trafo) * transform;
322  }
323  break;
324 
325  default: throw NoriException("Unhandled element \"%s\"", node.name());
326  };
327  }
328  } catch (const NoriException &e) {
329  throw NoriException("Error while parsing \"%s\": %s (at %s)", filename,
330  e.what(), offset(node.offset_debug()));
331  }
332 
333  return result;
334  };
335 
336  PropertyList list;
337  return parseTag(*doc.begin(), list, EInvalid);
338 }
339 
340 NORI_NAMESPACE_END
Simple exception class, which stores a human-readable error description.
Definition: common.h:148
static NoriObject * createInstance(const std::string &name, const PropertyList &propList)
Construct an instance from the class of the given name.
Definition: object.h:158
Base class of all objects.
Definition: object.h:32
virtual void activate()
Perform some action associated with the object.
Definition: object.h:107
void setIdName(const std::string &name)
Allow to assign a name to the object.
Definition: object.h:114
virtual void addChild(NoriObject *child)
Add a child object to the current instance.
Definition: object.h:81
virtual std::string toString() const =0
Return a brief string summary of the instance (for debugging purposes)
static std::string classTypeName(EClassType type)
Turn a class type into a human-readable string.
Definition: object.h:51
virtual EClassType getClassType() const =0
Return the type of object (i.e. Mesh/BSDF/etc.) provided by this instance.
This is an associative container used to supply the constructors of NoriObject subclasses with parame...
Definition: proplist.h:32
Represents a linear RGB color value.
Definition: color.h:29