/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2011 by Wenzel Jakob and others. Mitsuba 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. Mitsuba 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 . */ #include #include #include #include #include #include MTS_NAMESPACE_BEGIN #if !defined(__OSX__) #define XMLLog(level, fmt, ...) Thread::getThread()->getLogger()->log(\ level, NULL, __FILE__, __LINE__, "Near file offset %i: " fmt, \ (int) m_parser->getSrcOffset(), ## __VA_ARGS__) #else #define XMLLog(level, fmt, ...) Thread::getThread()->getLogger()->log(\ level, NULL, __FILE__, __LINE__, fmt, ## __VA_ARGS__) #endif SceneHandler::SceneHandler(const SAXParser *parser, const ParameterMap ¶ms, NamedObjectMap *namedObjects, bool isIncludedFile) : m_parser(parser), m_params(params), m_namedObjects(namedObjects), m_isIncludedFile(isIncludedFile) { m_pluginManager = PluginManager::getInstance(); if (m_isIncludedFile) { SAssert(namedObjects != NULL); } else { SAssert(namedObjects == NULL); m_namedObjects = new NamedObjectMap(); } #if !defined(WIN32) setlocale(LC_NUMERIC, "C"); #endif } SceneHandler::~SceneHandler() { clear(); if (!m_isIncludedFile) delete m_namedObjects; } void SceneHandler::clear() { if (!m_isIncludedFile) { for (NamedObjectMap::iterator it = m_namedObjects->begin(); it != m_namedObjects->end(); ++it) if (it->second) it->second->decRef(); m_namedObjects->clear(); } } // ----------------------------------------------------------------------- // Implementation of the SAX DocumentHandler interface // ----------------------------------------------------------------------- void SceneHandler::startDocument() { clear(); } void SceneHandler::endDocument() { SAssert(m_scene != NULL); } void SceneHandler::characters(const XMLCh* const name, const unsigned int length) { } Float SceneHandler::parseFloat(const std::string &name, const std::string &str, Float defVal) const { char *end_ptr = NULL; if (str == "") { if (defVal == -1) XMLLog(EError, "Missing floating point value (in <%s>)", name.c_str()); return defVal; } Float result = (Float) std::strtod(str.c_str(), &end_ptr); if (*end_ptr != '\0') XMLLog(EError, "Invalid floating point value specified (in <%s>)", name.c_str()); return result; } void SceneHandler::startElement(const XMLCh* const xmlName, AttributeList &xmlAttributes) { std::string name = transcode(xmlName); ParseContext context((name == "scene") ? NULL : &m_context.top()); /* Convert attributes to ISO-8859-1 */ for (size_t i=0; i 0 && attrValue.find('$') != attrValue.npos) { for (ParameterMap::const_reverse_iterator it = m_params.rbegin(); it != m_params.rend(); ++it) { std::string::size_type pos = 0; std::string searchString = "$" + it->first; while ((pos = attrValue.find(searchString, pos)) != std::string::npos) { attrValue.replace(pos, searchString.size(), it->second); ++pos; } } if (attrValue.find('$') != attrValue.npos) XMLLog(EError, "The scene referenced an undefined parameter: \"%s\"", attrValue.c_str()); } context.attributes[transcode(xmlAttributes.getName(i))] = attrValue; } if (name == "transform") m_transform = Transform(); if (name == "scene") { std::string versionString = context.attributes["version"]; if (versionString == "") throw VersionException(formatString("The requested scene cannot be loaded, since it " "is missing version information! Since Mitsuba 0.3.0, it is " "mandatory that scene XML files specify the version of Mitsuba " "that was used at the time of their creation.\nThis makes it clear " "how to interpret them in the presence of a changing file format. " "The version should be specified within the 'scene' tag, " "e.g.\n\t\n" "Please update your scene file with the right version number and try reloading it."), Version()); Version fileVersion(versionString), currentVersion(MTS_VERSION); if (!fileVersion.isCompatible(currentVersion)) { if (fileVersion < currentVersion) { throw VersionException(formatString("The requested scene is from an older version of Mitsuba " "(file version: %s, current version: %s), hence the loading process was stopped. " "Please open the scene from within Mitsuba's graphical user interface (mtsgui) -- " "it will then be upgraded to the current format.", fileVersion.toString().c_str(), MTS_VERSION), fileVersion); } else { XMLLog(EError, "The requested scene is from an incompatible future version of Mitsuba " "(file version: %s, current version: %s). Giving up.", fileVersion.toString().c_str(), MTS_VERSION); } } } m_context.push(context); } void SceneHandler::endElement(const XMLCh* const xmlName) { std::string name = transcode(xmlName); ParseContext &context = m_context.top(); std::string type = boost::to_lower_copy(context.attributes["type"]); context.properties.setPluginName(type); if (context.attributes.find("id") != context.attributes.end()) context.properties.setID(context.attributes["id"]); ref object = NULL; /* Construct configurable objects */ if (name == "scene") { object = m_scene = new Scene(context.properties); } else if (name == "shape") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Shape), context.properties)); } else if (name == "sampler") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Sampler), context.properties)); } else if (name == "film") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Film), context.properties)); } else if (name == "integrator") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Integrator), context.properties)); } else if (name == "texture") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Texture), context.properties)); } else if (name == "camera") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Camera), context.properties)); } else if (name == "subsurface") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Subsurface), context.properties)); } else if (name == "luminaire") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Luminaire), context.properties)); } else if (name == "medium") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(Medium), context.properties)); } else if (name == "volume") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(VolumeDataSource), context.properties)); } else if (name == "phase") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(PhaseFunction), context.properties)); } else if (name == "bsdf") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(BSDF), context.properties)); } else if (name == "rfilter") { object = static_cast (m_pluginManager->createObject( MTS_CLASS(ReconstructionFilter), context.properties)); } else if (name == "null") { object = NULL; } else if (name == "ref") { std::string id = context.attributes["id"]; if (m_namedObjects->find(id) == m_namedObjects->end()) XMLLog(EError, "Referenced object '%s' not found!", id.c_str()); object = (*m_namedObjects)[id]; /* Construct properties */ } else if (name == "integer") { char *end_ptr = NULL; #ifdef WIN32 int64_t i = _strtoi64(context.attributes["value"].c_str(), &end_ptr, 10); #else int64_t i = strtoll(context.attributes["value"].c_str(), &end_ptr, 10); #endif if (*end_ptr != '\0') XMLLog(EError, "Invalid integer value specified (in <%s>)", context.attributes["name"].c_str()); context.parent->properties.setLong(context.attributes["name"], i); } else if (name == "float") { Float f = parseFloat(name, context.attributes["value"]); context.parent->properties.setFloat(context.attributes["name"], f); } else if (name == "boolean") { bool value = false; if (context.attributes["value"] == "true") value = true; context.parent->properties.setBoolean(context.attributes["name"], value); } else if (name == "string") { context.parent->properties.setString(context.attributes["name"], context.attributes["value"]); } else if (name == "translate") { Float x = parseFloat(name, context.attributes["x"], 0); Float y = parseFloat(name, context.attributes["y"], 0); Float z = parseFloat(name, context.attributes["z"], 0); m_transform = Transform::translate(Vector(x, y, z)) * m_transform; } else if (name == "rotate") { Float x = parseFloat(name, context.attributes["x"], 0); Float y = parseFloat(name, context.attributes["y"], 0); Float z = parseFloat(name, context.attributes["z"], 0); Float angle = parseFloat(name, context.attributes["angle"]); m_transform = Transform::rotate(Vector(x, y, z), angle) * m_transform; } else if (name == "lookAt") { std::vector tokens = tokenize(context.attributes["origin"], ", "); if (tokens.size() != 3) XMLLog(EError, ": invalid 'origin' argument"); Point o( parseFloat(name, tokens[0]), parseFloat(name, tokens[1]), parseFloat(name, tokens[2])); tokens = tokenize(context.attributes["target"], ", "); if (tokens.size() != 3) XMLLog(EError, ": invalid 'target' argument"); Point t( parseFloat(name, tokens[0]), parseFloat(name, tokens[1]), parseFloat(name, tokens[2])); Vector u(0.0f); tokens = tokenize(context.attributes["up"], ", "); if (tokens.size() == 3) u = Vector( parseFloat(name, tokens[0]), parseFloat(name, tokens[1]), parseFloat(name, tokens[2])); else if (tokens.size() == 0) ; else XMLLog(EError, ": invalid 'up' argument"); if (u.lengthSquared() == 0) { /* If 'up' was not specified, use an arbitrary axis */ Vector unused; coordinateSystem(normalize(t-o), u, unused); } m_transform = Transform::lookAt(o, t, u) * m_transform; } else if (name == "scale") { bool hasXYZ = context.attributes["x"] != "" || context.attributes["y"] != "" || context.attributes["z"] != ""; bool hasValue = context.attributes["value"] != ""; Float x=0, y=0, z=0; if (hasXYZ && hasValue) { XMLLog(EError, ": provided both xyz and value arguments!"); } else if (hasXYZ) { x = parseFloat(name, context.attributes["x"], 1); y = parseFloat(name, context.attributes["y"], 1); z = parseFloat(name, context.attributes["z"], 1); } else if (hasValue) { x = y = z = parseFloat(name, context.attributes["value"]); } else { XMLLog(EError, ": provided neither xyz nor value arguments!"); } m_transform = Transform::scale(Vector(x, y, z)) * m_transform; } else if (name == "matrix") { std::vector tokens = tokenize( context.attributes["value"], ", "); if (tokens.size() != 16) XMLLog(EError, "Invalid matrix specified"); int index = 0; Matrix4x4 mtx; for (int i=0; i<4; ++i) for (int j=0; j<4; ++j) mtx.m[i][j] = parseFloat(name, tokens[index++]); m_transform = Transform(mtx) * m_transform; } else if (name == "point") { Float x = parseFloat(name, context.attributes["x"]); Float y = parseFloat(name, context.attributes["y"]); Float z = parseFloat(name, context.attributes["z"]); context.parent->properties.setPoint(context.attributes["name"], Point(x, y, z)); } else if (name == "vector") { Float x = parseFloat(name, context.attributes["x"]); Float y = parseFloat(name, context.attributes["y"]); Float z = parseFloat(name, context.attributes["z"]); context.parent->properties.setVector(context.attributes["name"], Vector(x, y, z)); } else if (name == "rgb") { Spectrum::EConversionIntent intent = Spectrum::EReflectance; if (context.attributes.find("intent") != context.attributes.end()) { std::string intentString = boost::to_lower_copy(context.attributes["intent"]); if (intentString == "reflectance") intent = Spectrum::EReflectance; else if (intentString == "illuminant") intent = Spectrum::EIlluminant; else XMLLog(EError, "Invalid intent \"%s\", must be " "\"reflectance\" or \"illuminant\"", intentString.c_str()); } std::string valueStr = context.attributes["value"]; std::vector tokens = tokenize(valueStr, ", "); Float value[3]; if (tokens.size() == 1 && tokens[0].length() == 7 && tokens[0][0] == '#') { char *end_ptr = NULL; /* Parse HTML-style hexadecimal colors */ int encoded = strtol(tokens[0].c_str()+1, &end_ptr, 16); if (*end_ptr != '\0') XMLLog(EError, "Invalid rgb value specified (in <%s>)", context.attributes["name"].c_str()); value[0] = ((encoded & 0xFF0000) >> 16) / 255.0f; value[1] = ((encoded & 0x00FF00) >> 8) / 255.0f; value[2] = (encoded & 0x0000FF) / 255.0f; } else if (tokens.size() == 1) { value[0] = value[1] = value[2] = parseFloat(name, tokens[0]); } else if (tokens.size() == 3) { for (int i=0; i<3; i++) value[i] = parseFloat(name, tokens[i]); } else { value[0] = value[1] = value[2] = 0; // avoid warning XMLLog(EError, "Invalid RGB value specified"); } Spectrum specValue; specValue.fromLinearRGB(value[0], value[1], value[2], intent); context.parent->properties.setSpectrum(context.attributes["name"], specValue); } else if (name == "srgb") { std::string valueStr = context.attributes["value"]; std::vector tokens = tokenize(valueStr, ", "); Float value[3]; if (tokens.size() == 1 && tokens[0].length() == 7 && tokens[0][0] == '#') { char *end_ptr = NULL; /* Parse HTML-style hexadecimal colors */ int encoded = strtol(tokens[0].c_str()+1, &end_ptr, 16); if (*end_ptr != '\0') XMLLog(EError, "Invalid sRGB value specified (in <%s>)", context.attributes["name"].c_str()); value[0] = ((encoded & 0xFF0000) >> 16) / 255.0f; value[1] = ((encoded & 0x00FF00) >> 8) / 255.0f; value[2] = (encoded & 0x0000FF) / 255.0f; } else if (tokens.size() == 1) { value[0] = value[1] = value[2] = parseFloat(name, tokens[0]); } else if (tokens.size() == 3) { for (int i=0; i<3; i++) value[i] = parseFloat(name, tokens[i]); } else { value[0] = value[1] = value[2] = 0; // avoid warning XMLLog(EError, "Invalid sRGB value specified"); } Spectrum specValue; specValue.fromSRGB(value[0], value[1], value[2]); context.parent->properties.setSpectrum(context.attributes["name"], specValue); } else if (name == "blackbody") { std::string temperature = trim(context.attributes["temperature"]); if (temperature.length() > 0 && std::toupper(temperature[temperature.length()-1]) == 'K') temperature = temperature.substr(0, temperature.length()-1); Float temperatureValue = parseFloat(name, temperature); Float multiplier = 1; if (context.attributes.find("multiplier") != context.attributes.end()) multiplier = parseFloat(name, context.attributes["multiplier"]); BlackBodySpectrum bb(temperatureValue); Spectrum discrete; discrete.fromContinuousSpectrum(bb); context.parent->properties.setSpectrum(context.attributes["name"], discrete * multiplier); } else if (name == "spectrum") { bool hasValue = context.attributes.find("value") != context.attributes.end(); bool hasFilename = context.attributes.find("filename") != context.attributes.end(); if (hasValue == hasFilename) { SLog(EError, "Spectrum: please provide one of 'value' or 'filename'"); } else if (hasFilename) { FileResolver *resolver = Thread::getThread()->getFileResolver(); fs::path path = resolver->resolve(context.attributes["filename"]); InterpolatedSpectrum interp(path); interp.zeroExtend(); Spectrum discrete; discrete.fromContinuousSpectrum(interp); context.parent->properties.setSpectrum(context.attributes["name"], discrete); } else if (hasValue) { std::vector tokens = tokenize( context.attributes["value"], ", "); Float value[SPECTRUM_SAMPLES]; if (tokens.size() == 1) { value[0] = parseFloat(name, tokens[0]); context.parent->properties.setSpectrum(context.attributes["name"], Spectrum(value[0])); } else { if (tokens[0].find(':') != std::string::npos) { InterpolatedSpectrum interp(tokens.size()); /* Wavelength -> Value mapping */ for (size_t i=0; i tokens2 = tokenize(tokens[i], ":"); if (tokens2.size() != 2) XMLLog(EError, "Invalid spectrum->value mapping specified"); Float wavelength = parseFloat(name, tokens2[0]); Float value = parseFloat(name, tokens2[1]); interp.append(wavelength, value); } interp.zeroExtend(); Spectrum discrete; discrete.fromContinuousSpectrum(interp); context.parent->properties.setSpectrum(context.attributes["name"], discrete); } else { if (tokens.size() != SPECTRUM_SAMPLES) XMLLog(EError, "Invalid spectrum value specified (incorrect length)"); for (int i=0; iproperties.setSpectrum(context.attributes["name"], Spectrum(value)); } } } } else if (name == "transform") { context.parent->properties.setTransform(context.attributes["name"], m_transform); } else if (name == "include") { SAXParser* parser = new SAXParser(); FileResolver *resolver = Thread::getThread()->getFileResolver(); fs::path schemaPath = resolver->resolveAbsolute("data/schema/scene.xsd"); /* Check against the 'scene.xsd' XML Schema */ parser->setDoSchema(true); parser->setValidationSchemaFullChecking(true); parser->setValidationScheme(SAXParser::Val_Always); parser->setExternalNoNamespaceSchemaLocation(schemaPath.file_string().c_str()); /* Set the handler and start parsing */ SceneHandler *handler = new SceneHandler(parser, m_params, m_namedObjects, true); parser->setDoNamespaces(true); parser->setDocumentHandler(handler); parser->setErrorHandler(handler); #if !defined(__OSX__) /// Not supported on OSX parser->setCalculateSrcOfs(true); #endif fs::path path = resolver->resolve(context.attributes["filename"]); XMLLog(EInfo, "Parsing included file \"%s\" ..", path.filename().c_str()); parser->parse(path.file_string().c_str()); object = handler->getScene(); delete parser; delete handler; } else { XMLLog(EError, "Unhandled tag \"%s\" encountered!", name.c_str()); } if (object != NULL || name == "null") { std::string id = context.attributes["id"]; std::string nodeName = context.attributes["name"]; if (id != "" && name != "ref") { if (m_namedObjects->find(id) != m_namedObjects->end()) XMLLog(EError, "Duplicate ID '%s' used in scene description!", id.c_str()); (*m_namedObjects)[id] = object; if (object) object->incRef(); } if (object) { /* If the object has a parent, add it to the parent's children list */ if (context.parent != NULL) { object->incRef(); context.parent->children.push_back( std::pair(nodeName, object)); } /* If the object has children, append them */ for (std::vector > ::iterator it = context.children.begin(); it != context.children.end(); ++it) { if (it->second != NULL) { object->addChild(it->first, it->second); it->second->setParent(object); it->second->decRef(); } } /* Don't configure a scene object if it is from an included file */ if (name != "include" && (!m_isIncludedFile || !object->getClass()->derivesFrom(MTS_CLASS(Scene)))) object->configure(); } } /* Warn about unqueried properties */ std::vector unq = context.properties.getUnqueried(); for (unsigned int i=0; i SceneHandler::loadScene(const fs::path &filename, const ParameterMap ¶ms) { /* Prepare for parsing scene descriptions */ FileResolver *resolver = Thread::getThread()->getFileResolver(); SAXParser* parser = new SAXParser(); fs::path schemaPath = resolver->resolveAbsolute("data/schema/scene.xsd"); SLog(EDebug, "Loading scene \"%s\" ..", filename.file_string().c_str()); /* Check against the 'scene.xsd' XML Schema */ parser->setDoSchema(true); parser->setValidationSchemaFullChecking(true); parser->setValidationScheme(SAXParser::Val_Always); parser->setExternalNoNamespaceSchemaLocation(schemaPath.file_string().c_str()); parser->setCalculateSrcOfs(true); SceneHandler *handler = new SceneHandler(parser, params); parser->setDoNamespaces(true); parser->setDocumentHandler(handler); parser->setErrorHandler(handler); parser->parse(filename.file_string().c_str()); ref scene = handler->getScene(); delete parser; delete handler; return scene; } void SceneHandler::staticInitialization() { /* Initialize Xerces-C */ try { XMLPlatformUtils::Initialize(); } catch(const XMLException &toCatch) { SLog(EError, "Error during Xerces initialization: %s", XMLString::transcode(toCatch.getMessage())); } } void SceneHandler::staticShutdown() { XMLPlatformUtils::Terminate(); } MTS_NAMESPACE_END