437 lines
14 KiB
C++
437 lines
14 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "save.h"
|
|
#include <QtXml>
|
|
|
|
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<std::string> propertyNames;
|
|
props.putPropertyNames(propertyNames);
|
|
for (std::vector<std::string>::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 <b>%1</b>. "
|
|
"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 <b>%1</b>. "
|
|
"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 PerspectiveCamera *sceneCamera = static_cast<const PerspectiveCamera *>(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 focusDepthProperty = findProperty(camera, "focusDepth");
|
|
if (focusDepthProperty.isNull()) {
|
|
focusDepthProperty = doc.createElement("float");
|
|
focusDepthProperty.setAttribute("name", "focusDepth");
|
|
camera.insertBefore(focusDepthProperty, QDomNode());
|
|
}
|
|
focusDepthProperty.setAttribute("value", QString::number(sceneCamera->getFocusDepth()));
|
|
|
|
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 <b>%1</b>.").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
|
|
filenameRegExp("filename=\"[^\"]*\""),
|
|
nameRegExp("name=\"[^\"]*\""),
|
|
tagRegExp("^\\s*<([a-zA-Z]+) "),
|
|
leadingSpaces("^ *"),
|
|
closeTag("^\\s*</");
|
|
bool inComment = false, hasContents = false;
|
|
|
|
while (!input.atEnd()) {
|
|
QString line = input.readLine();
|
|
bool startComment = line.contains("<!--");
|
|
bool endComment = line.contains("-->");
|
|
|
|
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),
|
|
filenameMatch = filenameRegExp.indexIn(line),
|
|
nameLength = nameRegExp.matchedLength();
|
|
if (tagMatch != -1 && nameMatch != -1 && filenameMatch == -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();
|
|
}
|
|
|