/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2010 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 "save.h" #include static QDomElement findUniqueChild(QDomElement element, const char *tagName) { QDomElement result; QDomNode n = element.firstChild(); bool found = false; while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == tagName) { if (found) SLog(EError, "Unexpectedly found multiple tags named \"%s\"", tagName); found = true; result = e; } } n = n.nextSibling(); } return result; } static QDomElement findProperty(QDomElement element, const QString &propertyName) { QDomElement result; QDomNode n = element.firstChild(); bool found = false; while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { if (e.attribute("name") == propertyName) { if (found) SLog(EError, "Unexpectedly found multiple properties named \"%s\"", qPrintable(propertyName)); found = true; result = e; } } n = n.nextSibling(); } return result; } static void removeChildren(QDomElement element) { while (!element.lastChild().isNull()) element.removeChild(element.lastChild()); } static void setProperties(QDomDocument &doc, QDomElement &element, const Properties &props) { element.setAttribute("type", props.getPluginName().c_str()); std::vector propertyNames; props.putPropertyNames(propertyNames); for (std::vector::const_iterator it = propertyNames.begin(); it != propertyNames.end(); ++it) { QDomElement property; switch (props.getType(*it)) { case Properties::EBoolean: property = doc.createElement("boolean"); property.setAttribute("value", props.getBoolean(*it) ? "true" : "false"); break; case Properties::EInteger: property = doc.createElement("integer"); property.setAttribute("value", QString::number(props.getInteger(*it))); break; case Properties::EFloat: property = doc.createElement("float"); property.setAttribute("value", QString::number(props.getFloat(*it))); break; case Properties::EString: property = doc.createElement("string"); property.setAttribute("value", props.getString(*it).c_str()); break; default: SLog(EError, "setProperties(): \"%s\": Unable to handle elements of type %i", (*it).c_str(), props.getType(*it)); } property.setAttribute("name", (*it).c_str()); element.appendChild(property); } } void saveScene(QWidget *parent, SceneContext *ctx, const QString &targetFile) { QDomDocument doc("MitsubaScene"); QFile file(ctx->fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(parent, parent->tr("Unable to save"), parent->tr("Unable to save changes: could not open the original scene file %1. " "Was this file moved or deleted in the meantime?").arg(ctx->fileName), QMessageBox::Ok); return; } if (!doc.setContent(&file)) { QMessageBox::critical(parent, parent->tr("Unable to save"), parent->tr("Unable to save changes: could not parse the original scene file %1. " "Was this file modified in the meantime?").arg(ctx->fileName), QMessageBox::Ok); file.close(); return; } file.close(); QDomElement root = doc.documentElement(); // ==================================================================== // Serialize the camera configuration // ==================================================================== QDomElement camera = findUniqueChild(root, "camera"); if (camera.isNull()) { camera = doc.createElement("camera"); camera.setAttribute("type", "perspective"); root.insertAfter(camera, QDomNode()); } const PinholeCamera *sceneCamera = static_cast(ctx->scene->getCamera()); QDomElement fovProperty = findProperty(camera, "fov"); if (fovProperty.isNull()) { fovProperty = doc.createElement("float"); fovProperty.setAttribute("name", "fov"); camera.insertBefore(fovProperty, QDomNode()); } fovProperty.setAttribute("value", QString::number(sceneCamera->getFov())); QDomElement cameraTransform = findUniqueChild(camera, "transform"); if (cameraTransform.isNull()) { cameraTransform = doc.createElement("transform"); cameraTransform.setAttribute("name", "toWorld"); camera.insertBefore(cameraTransform, QDomNode()); } removeChildren(cameraTransform); QDomElement lookAt = doc.createElement("lookAt"); cameraTransform.insertAfter(lookAt, QDomNode()); Vector direction = sceneCamera->getInverseViewTransform()(Vector(0,0,1)), u = ctx->up; Point t, p = sceneCamera->getInverseViewTransform()(Point(0,0,0)); if (sceneCamera->getViewTransform().det3x3() > 0) { QDomElement scale = doc.createElement("scale"); scale.setAttribute("x", "-1"); cameraTransform.insertBefore(scale, lookAt); } t = p + direction; lookAt.setAttribute("ox", QString::number(p.x)); lookAt.setAttribute("oy", QString::number(p.y)); lookAt.setAttribute("oz", QString::number(p.z)); lookAt.setAttribute("tx", QString::number(t.x)); lookAt.setAttribute("ty", QString::number(t.y)); lookAt.setAttribute("tz", QString::number(t.z)); lookAt.setAttribute("ux", QString::number(u.x)); lookAt.setAttribute("uy", QString::number(u.y)); lookAt.setAttribute("uz", QString::number(u.z)); // ==================================================================== // Serialize the sampler configuration // ==================================================================== QDomElement newSampler, sampler = findUniqueChild(camera, "sampler"); if (sampler.isNull()) { newSampler = doc.createElement("sampler"); camera.appendChild(newSampler); } else { newSampler = doc.createElement("sampler"); camera.insertAfter(newSampler, sampler); camera.removeChild(sampler); } setProperties(doc, newSampler, ctx->scene->getSampler()->getProperties()); // ==================================================================== // Serialize the film configuration // ==================================================================== QDomElement film = findUniqueChild(camera, "film"); if (film.isNull()) { film = doc.createElement("film"); film.setAttribute("type", "exrfilm"); camera.insertAfter(film, QDomNode()); } QDomElement widthProperty = findProperty(film, "width"); QDomElement heightProperty = findProperty(film, "height"); if (widthProperty.isNull()) { widthProperty = doc.createElement("integer"); widthProperty.setAttribute("name", "width"); film.insertBefore(widthProperty, QDomNode()); } if (heightProperty.isNull()) { heightProperty = doc.createElement("integer"); heightProperty.setAttribute("name", "height"); film.insertBefore(heightProperty, QDomNode()); } if (film.attribute("type") == "pngfilm") { /* Set tonemapping attributes */ QDomElement method = findProperty(film, "toneMappingMethod"); QDomElement reinhardBurn = findProperty(film, "reinhardBurn"); QDomElement reinhardKey = findProperty(film, "reinhardKey"); QDomElement exposure = findProperty(film, "exposure"); QDomElement gamma = findProperty(film, "gamma"); if (method.isNull()) { method = doc.createElement("string"); method.setAttribute("name", "toneMappingMethod"); film.insertBefore(method, QDomNode()); } method.setAttribute("value", ctx->toneMappingMethod == EReinhard ? "reinhard" : "gamma"); if (gamma.isNull()) { gamma = doc.createElement("float"); gamma.setAttribute("name", "gamma"); film.insertBefore(gamma, QDomNode()); } gamma.setAttribute("value", QString::number(ctx->srgb ? (Float) -1 : ctx->gamma)); if (ctx->toneMappingMethod == EGamma) { if (exposure.isNull()) { exposure = doc.createElement("float"); exposure.setAttribute("name", "exposure"); film.insertBefore(exposure, QDomNode()); } exposure.setAttribute("value", QString::number(ctx->exposure)); if (!reinhardKey.isNull()) film.removeChild(reinhardKey); if (!reinhardBurn.isNull()) film.removeChild(reinhardBurn); } else { if (reinhardKey.isNull()) { reinhardKey = doc.createElement("float"); reinhardKey.setAttribute("name", "reinhardKey"); film.insertBefore(reinhardKey, QDomNode()); } if (reinhardBurn.isNull()) { reinhardBurn = doc.createElement("float"); reinhardBurn.setAttribute("name", "reinhardBurn"); film.insertBefore(reinhardBurn, QDomNode()); } reinhardKey.setAttribute("value", QString::number(ctx->reinhardKey)); reinhardBurn.setAttribute("value", QString::number(ctx->reinhardBurn)); if (!exposure.isNull()) film.removeChild(exposure); } } Vector2i filmSize = sceneCamera->getFilm()->getSize(); widthProperty.setAttribute("value", QString::number(filmSize.x)); heightProperty.setAttribute("value", QString::number(filmSize.y)); // ==================================================================== // Serialize the reconstruction filter configuration // ==================================================================== QDomElement newRFilter, rfilter = findUniqueChild(film, "rfilter"); if (rfilter.isNull()) { newRFilter = doc.createElement("rfilter"); film.appendChild(rfilter); } else { newRFilter = doc.createElement("rfilter"); film.insertAfter(newRFilter, rfilter); film.removeChild(rfilter); } setProperties(doc, newRFilter, ctx->scene->getCamera()-> getFilm()->getReconstructionFilter()->getProperties()); // ==================================================================== // Serialize the integrator configuration // ==================================================================== QDomElement oldIntegratorNode = findUniqueChild(root, "integrator"); QDomElement newIntegratorNode = doc.createElement("integrator"); if (oldIntegratorNode.isNull()) { film.appendChild(oldIntegratorNode); } else { film.insertAfter(newIntegratorNode, oldIntegratorNode); film.removeChild(oldIntegratorNode); } const Integrator *integrator = ctx->scene->getIntegrator(); setProperties(doc, newIntegratorNode, integrator->getProperties()); QDomElement currentIntegratorNode = newIntegratorNode; while (integrator->getSubIntegrator() != NULL) { integrator = integrator->getSubIntegrator(); QDomElement childIntegratorNode = doc.createElement("integrator"); setProperties(doc, childIntegratorNode, integrator->getProperties()); currentIntegratorNode.appendChild(childIntegratorNode); currentIntegratorNode = childIntegratorNode; } root.insertAfter(newIntegratorNode, oldIntegratorNode); root.removeChild(oldIntegratorNode); file.setFileName(targetFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { QMessageBox::critical(parent, parent->tr("Unable to save"), parent->tr("Unable to save changes: could not open the destination file %1.").arg(targetFile), QMessageBox::Ok); return; } /* Clean up the XML output generated by Qt so that it is more suitable for human consumption .. Beware: the code below is tailored to Qt's output and won't work on arbitrary XML files */ QString textContent = doc.toString(); QTextStream input(&textContent); QTextStream output(&file); QRegExp nameRegExp("name=\"[^\"]*\""), tagRegExp("^\\s*<([a-zA-Z]+) "), leadingSpaces("^ *"), closeTag("^\\s*"); if (startComment) inComment = true; if (endComment) inComment = false; if (inComment || endComment) { if (startComment) { /* Turn leading spaces into tabs */ if (leadingSpaces.indexIn(line) == 0) line = QString('\t').repeated(leadingSpaces.matchedLength()) + line.mid(leadingSpaces.matchedLength()); } output << line << endl; continue; } /* Make sure that the name attribute is always the first one */ int tagMatch = tagRegExp.indexIn(line), tagLength = tagRegExp.matchedLength(); int nameMatch = nameRegExp.indexIn(line), nameLength = nameRegExp.matchedLength(); if (tagMatch != -1 && nameMatch != -1) { line = line.left(tagLength) + line.mid(nameMatch, nameLength) + " " + line.mid(tagMatch+tagLength, nameMatch-(tagMatch+tagLength)) + line.mid(nameMatch+nameLength); } /* Add an extra newline if this is an object tag, and if there have been siblings before it */ if (tagMatch != -1) { const QString &el = tagRegExp.cap(1); bool isObject = true; isObject &= (el != "string"); isObject &= (el != "integer"); isObject &= (el != "float"); isObject &= (el != "boolean"); isObject &= (el != "vector"); isObject &= (el != "point"); isObject &= (el != "transform"); isObject &= (el != "spectrum"); isObject &= (el != "rgb"); isObject &= (el != "scale"); isObject &= (el != "translate"); isObject &= (el != "rotate"); isObject &= (el != "lookAt"); isObject &= (el != "matrix"); if (isObject && hasContents) { output << endl; hasContents = false; } if (!isObject) hasContents = true; } /* Turn leading spaces into tabs */ if (leadingSpaces.indexIn(line) == 0) line = QString('\t').repeated(leadingSpaces.matchedLength()) + line.mid(leadingSpaces.matchedLength()); /* Remove ugly spaces */ if (line.endsWith(" />")) { line = line.left(line.size()-4) + QString("/>"); hasContents = true; } else if (line.endsWith(" />")) { line = line.left(line.size()-3) + QString("/>"); hasContents = true; } else if (line.endsWith(" >")) { line = line.left(line.size()-2) + QString(">"); } if (closeTag.indexIn(line) == 0) hasContents = true; output << line << endl; } file.close(); }