diff --git a/doc/format.tex b/doc/format.tex
index 6a990ec9..acb3dd51 100644
--- a/doc/format.tex
+++ b/doc/format.tex
@@ -2,7 +2,7 @@
Mitsuba uses a very simple and general XML-based format to represent scenes.
Since the framework's philosophy is to represent discrete blocks of functionality as plugins,
a scene file can essentially be interpreted as description that determines which
-plugins should be instantiated and how they should be interface with each other.
+plugins should be instantiated and how they should interface with each other.
In the following, we'll look at a few examples to get a feeling for the scope of the
format.
@@ -21,15 +21,15 @@ create the scene. This information allows Mitsuba to always correctly process th
file irregardless of any potential future changes in the scene description language.
This example already contains the most important things to know about format: you can have
-\emph{objects} (such as the objects instantiated by the \code{scene} or \code{shape} tags), which are allowed to be nested within
-each other. Each object optionally accepts \emph{properties} (such as the \code{string} tag),
-which further characterize its behavior. All objects except for the root object (the \code{scene})
-cause the renderer to load and instantiate a plugin, hence you must provide the plugin name using
-\code{type=".."} parameter.
+\emph{objects} (such as the objects instantiated by the \code{scene} or \code{shape} tags),
+which are allowed to be nested within each other. Each object optionally accepts \emph{properties}
+(such as the \code{string} tag), which further characterize its behavior. All objects except
+for the root object (the \code{scene}) cause the renderer to search and load a plugin from disk,
+hence you must provide the plugin name using \code{type=".."} parameter.
The object tags also let the renderer know \emph{what kind} of object is to be instantiated: for instance,
-any plugin loaded using the \code{shape} tag must conform to the \emph{Shape} interface, which
-the certainly case for the plugin named \code{obj} (it contains a WaveFront OBJ loader).
+any plugin loaded using the \code{shape} tag must conform to the \emph{Shape} interface, which is
+certainly the case for the plugin named \code{obj} (it contains a WaveFront OBJ loader).
Similarly, you could write
\begin{xml}
@@ -41,16 +41,20 @@ Similarly, you could write
\end{xml}
This loads a different plugin (\code{sphere}) which is still a \emph{Shape}, but instead represents
a sphere configured with a radius of 10 world-space units. Mitsuba ships with
-a large number of plugins; please refer to the next chapter for a reference.
+a large number of plugins; please refer to the next chapter for a detailed
+overview of them.
The most common scene setup is to declare an integrator, some geometry, a camera, a film, a sampler
and one or more luminaires. Here is a more complex example:
\begin{xml}
+
-
+
+
+
@@ -72,17 +76,18 @@ and one or more luminaires. Here is a more complex example:
-
+
-
+
-
+
-
+
@@ -134,18 +139,41 @@ these values are directly used. The renderer can also be configured to sample th
number of samples, in which case the RGB values are first converted into spectra using a simple heuristic.
You can also directly supply the spectral color samples that Mitsuba internally uses if spectral rendering is
-active. This unfortunately closely couples the interpretation of a scene to how Mitsuba is compiled, which can be a disadvantage.
-For instance, the below example assumes that 6 spectral samples are being used:
+active. This unfortunately closely couples the interpretation of a scene to how Mitsuba is compiled, which
+can be a disadvantage.
+For instance, the below example assumes that 6 spectral samples in the 400-700nm range are being used:
\begin{xml}
\end{xml}
A safer way is to specify a linearly interpolated spectral power distribution, which is converted into
-the internal spectral representation as the scene is loaded.
+the internal spectral representation when the scene is loaded.
\begin{xml}
\end{xml}
-This is essentially a mapping from wavelength in nanometers (before the colon) to a reflectance or intensity value (after the colon). Missing values are linearly interpolated from the two closest neighbors.
-To specify a constant spectrum, simply provide just one value
+This is essentially a mapping from wavelength in nanometers (before the colon) to a reflectance or
+intensity value (after the colon). Missing values are linearly interpolated from the two closest neighbors.
+
+When spectral power distributions are obtained from measurements (e.g. at 10nm intervals), they are
+usually quite unwiedy and can clutter the scene description. For this reason,
+there is yet another way to pass a spectrum by loading it from an external
+file:
+\begin{xml}
+
+\end{xml}
+The file should contain a single measurement per line, with the corresponding
+wavelength in nanometers and the measured value separated by a space. Here is
+an example:
+\begin{xml}
+406.13 0.703313
+413.88 0.744563
+422.03 0.791625
+430.62 0.822125
+435.09 0.834000
+...
+\end{xml}
+
+It is also possible to specify a completely flat spectral power distribution by specifying
+just one value:
\begin{xml}
\end{xml}
@@ -163,8 +191,8 @@ Points and vectors can be specified as follows:
It is important that whatever you choose as world-space units (meters, inches, etc.) is
used consistently in all places.
\subsubsection{Transformations}
-Transformations are the only kind of property, which require more than a single tag. The idea is that, starting
-with the identity, you build up a transformation using nested commands. For instance, a transformation that
+Transformations are the only kind of property that require more than a single tag. The idea is that, starting
+with the identity, one can build up a transformation using a sequence of commands. For instance, a transformation that
does a translation followed by a rotation might be written like this:
\begin{xml}
@@ -211,9 +239,9 @@ of how this works:
-
+
+ to the BRDF as the reflectance parameter -->
@@ -225,14 +253,28 @@ of how this works:
\end{xml}
-Note that this feature cannot yet be used to do geometry instancing.
+By providing a unique \texttt{id} attribute in the
+object declaration, the object is bound to that identifier
+upon instantiation.
+Referencing this identifier at a later point (using the \texttt{} tag)
+will add the instance to the parent object, with no further memory
+allocation taking place. Note that some plugins expect their child objects
+to be named\footnote{For instance, material plugins such as \pluginref{diffuse} require that
+nested texture instances explicitly specify the parameter to which they want to bind (e.g. ``\texttt{reflectance}'').}.
+For this reason, a name can also be associated with the reference.
+
+Note that while this feature is meant to efficiently handle materials,
+textures, and participating media that are referenced from multiple places,
+it cannot be used to instantiate geometry---if this functionality is needed,
+take a look at the \pluginref{instance} plugin.
+
\subsection{Including external files}
A scene can be split into multiple pieces for better readability.
to include an external file, please use the following command:
\begin{xml}
\end{xml}
-In this case, the file \code{nested-scene.xml} must still be a proper scene file with a \code{} tag at the root.
+In this case, the file \code{nested-scene.xml} must be a proper scene file with a \code{} tag at the root.
This feature is sometimes very convenient in conjunction with the \code{-D key=value} flag of the \code{mitsuba} command line renderer (see the previous section for details).
This lets you include different parts of a scene configuration by changing the command line parameters (and without having to touch the XML file):
\begin{xml}
diff --git a/doc/introduction.tex b/doc/introduction.tex
index d051cdd3..c0413c46 100644
--- a/doc/introduction.tex
+++ b/doc/introduction.tex
@@ -1,6 +1,6 @@
\section{Introduction}
-\textbf{Disclaimer:} This is manual is currently a work in progress --
-please bear with me as things are coming together.
+\textbf{Disclaimer:} This is manual is currently a work in progress---please
+bear with me while things are coming together.
\subsection{About Mitsuba}
Mitsuba is an extensible rendering framework written in portable C++. It implements unbiased
@@ -11,5 +11,40 @@ In comparison to other open source renderers, Mitsuba places a strong emphasis o
rendering techniques, such as path-based formulations of Metropolis Light Transport and volumetric
modeling approaches.
+\paragraph{Performance:}
+One important goal of Mitsuba is to provide optimized implementations of the most commonly
+used rendering algorithms. By virtue of running on a shared foundation, comparisons between them can
+better highlight the merits and limitations of different approaches. This is in contrast to, say,
+comparing two completely different rendering products, where technical information on the underlying
+implementation is often intentionally not provided.
+
+\paragraph{Robustness:}
+In many cases, physically-based rendering packages force the user to model scenes with the underlying
+algorithm (specifically: its convergence behavior) in mind. For instance, glass windows are routinely
+replaced with light portals, photons must be manually guided to the relevant parts of a scene, and
+interactions with complex materials are taboo, since they cannot be importance sampled exactly.
+One focus of Mitsuba will be to develop path-space light transport algorithms, which handle such
+cases more gracefully.
+
+\paragraph{Scalability:} Mitsuba instances can be merged into large clusters, which transparently distribute and
+jointly execute tasks assigned to them using only node-to-node communcation. It has successfully
+scaled to large-scale renderings that involved 1024 cores working on a single image.
+Most algorithms in Mitsuba are written using a generic parallelization layer, which can tap
+into this cluster-wide parallelism. The principle is that if any component of the renderer produces
+work that takes longer than a second or so, it at least ought to use all of the processing power
+it can get.
+
+The renderer also tries to be very conservative in its use of memory, which lets it handle
+large scenes (>30 million triangles) and multi-gigabyte heterogeneous volumes on consumer hardware.
+
+\paragraph{Usability:}
+Mitsuba comes with a graphical user interface to interactively explore scenes. Once a suitable
+viewpoint has been found, it is straightforward to perform renderings using any of the
+implemented rendering techniques, while tweaking their parameters to find the most suitable
+settings. Experimental integration into Blender 2.5 is also available.
+
\subsection{License}
-Mitsuba is free software and can be redistributed and modified under the terms of the GNU General Public License (Version 3) as provided by the Free Software Foundation.
+Mitsuba is free software and can be redistributed and modified under the terms of the GNU General
+Public License (Version 3) as provided by the Free Software Foundation.
+
+
diff --git a/doc/main.tex b/doc/main.tex
index bf5da4e2..f03e0f85 100644
--- a/doc/main.tex
+++ b/doc/main.tex
@@ -101,7 +101,9 @@
keywords= [1] {
shape,bsdf,scene,texture,phase,integer,float,
string,transform,ref,rgb,srgb,spectrum,blackbody,
- medium,camera,film,sampler,integrator
+ medium,camera,film,sampler,integrator,luminaire,
+ translate,rotate,scale,lookAt,point,vector,matrix,
+ include
},
}
@@ -140,7 +142,7 @@
\include{introduction}
%\include{compiling}
%\include{basics}
-%\include{format}
+\include{format}
\IfFileExists{plugins_generated.tex}{\include{plugins_generated}}{}
%\include{import}
%\include{development}
diff --git a/include/mitsuba/core/chisquare.h b/include/mitsuba/core/chisquare.h
index 4e2e75c5..f2e0cdd5 100644
--- a/include/mitsuba/core/chisquare.h
+++ b/include/mitsuba/core/chisquare.h
@@ -20,6 +20,8 @@
#define __CHI_SQUARE_TEST_H
#include
+#include
+#include
#include
MTS_NAMESPACE_BEGIN
@@ -33,7 +35,8 @@ MTS_NAMESPACE_BEGIN
* This class performs a chi-square goodness-of-fit test of the null hypothesis
* that a specified sampling procedure produces samples that are distributed
* according to a supplied density function. This is very useful to verify BRDF
- * and phase function sampling codes for their correctness.
+ * and phase function sampling codes for their correctness. Currently, it
+ * supports both 2D and discrete sampling methods and mixtures thereof.
*
* This implementation works by generating a large batch of samples, which are
* then accumulated into rectangular bins in spherical coordinates. To obtain
@@ -48,10 +51,10 @@ MTS_NAMESPACE_BEGIN
* // Sample a (optionally weighted) direction. A non-unity weight
* // in the return value is needed when the sampling distribution
* // doesn't exactly match the implementation in pdf()
- * std::pair generateSample() const;
+ * boost::tuple generateSample() const;
*
- * /// Compute the probability density for the specified direction
- * Float pdf(const Vector &direction) const;
+ * /// Compute the probability density for the specified direction and measure
+ * Float pdf(const Vector &direction, EMeasure) const;
* };
*
*
@@ -64,14 +67,13 @@ MTS_NAMESPACE_BEGIN
* // Initialize the tables used by the chi-square test
* chiSqr.fill(
* boost::bind(&MyDistribution::generateSample, myDistrInstance),
- * boost::bind(&MyDistribution::pdf, myDistrInstance, _1)
+ * boost::bind(&MyDistribution::pdf, myDistrInstance, _1, _2)
* );
*
* // Optional: dump the tables to a MATLAB file for external analysis
* chiSqr.dumpTables("debug.m");
*
- * // (the following assumes that the distribution has 1 parameter, e.g. exponent value)
- * if (!chiSqr.runTest(1))
+ * if (!chiSqr.runTest())
* Log(EError, "Uh oh -- test failed, the implementation is probably incorrect!");
*
*/
@@ -139,8 +141,8 @@ public:
* on how to invoke this function
*/
void fill(
- const boost::function()> &sampleFn,
- const boost::function &pdfFn);
+ const boost::function()> &sampleFn,
+ const boost::function &pdfFn);
/**
* \brief Dump the bin counts to a file using MATLAB format
@@ -150,11 +152,6 @@ public:
/**
* \brief Perform the actual chi-square test
*
- * \param distParams
- * Number of parameters of the distribution in question.
- * Anything such as lobe width, indices of refraction, etc.
- * should be counted.
- *
* \param pvalThresh
* The implementation will reject the null hypothesis
* when the computed p-value lies below this parameter
@@ -162,7 +159,7 @@ public:
*
* \return A status value of type \ref ETestResult
*/
- ETestResult runTest(int distParams, Float pvalThresh = 0.01f);
+ ETestResult runTest(Float pvalThresh = 0.01f);
MTS_DECLARE_CLASS()
protected:
@@ -171,11 +168,12 @@ protected:
/// Functor to evaluate the pdf values in parallel using OpenMP
static void integrand(
- const boost::function &pdfFn,
+ const boost::function &pdfFn,
size_t nPts, const Float *in, Float *out) {
#pragma omp parallel for
for (int i=0; i<(int) nPts; ++i)
- out[i] = pdfFn(sphericalDirection(in[2*i], in[2*i+1])) * std::sin(in[2*i]);
+ out[i] = pdfFn(sphericalDirection(in[2*i], in[2*i+1]), ESolidAngle)
+ * std::sin(in[2*i]);
}
private:
ELogLevel m_logLevel;
diff --git a/include/mitsuba/core/spectrum.h b/include/mitsuba/core/spectrum.h
index 25475bbf..b05ce400 100644
--- a/include/mitsuba/core/spectrum.h
+++ b/include/mitsuba/core/spectrum.h
@@ -20,6 +20,9 @@
#define __SPECTRUM_H
#include
+#include
+
+namespace fs = boost::filesystem;
#define SPECTRUM_MIN_WAVELENGTH 400
#define SPECTRUM_MAX_WAVELENGTH 700
@@ -76,6 +79,10 @@ private:
/**
* \brief Linearly interpolated spectral power distribution
*
+ * This class implements a linearly interpolated spectral
+ * power distribution that is defined over a discrete set of
+ * measurements at different wavelengths.
+ *
* \ingroup libcore
*/
class MTS_EXPORT_CORE InterpolatedSpectrum : public SmoothSpectrum {
@@ -89,6 +96,12 @@ public:
m_value.reserve(size);
}
+ /**
+ * Read an interpolated spectrum from a simple
+ * ASCII format
+ */
+ InterpolatedSpectrum(const fs::path &path);
+
/**
* \brief Append an entry to the spectral power distribution.
*
diff --git a/schema/scene.xsd b/schema/scene.xsd
index 162a9125..c5725746 100644
--- a/schema/scene.xsd
+++ b/schema/scene.xsd
@@ -21,7 +21,7 @@
-
+
@@ -41,7 +41,7 @@
-
+
@@ -276,6 +276,12 @@
+
+
+
+
+
+
diff --git a/src/libcore/chisquare.cpp b/src/libcore/chisquare.cpp
index 442d88f8..870f37f5 100644
--- a/src/libcore/chisquare.cpp
+++ b/src/libcore/chisquare.cpp
@@ -21,8 +21,26 @@
#include
#include
#include
+#include
MTS_NAMESPACE_BEGIN
+
+/* Simple ordering for storing vectors in a set */
+struct VectorOrder {
+ inline int compare(const Vector &v1, const Vector &v2) const {
+ if (v1.x < v2.x) return -1;
+ else if (v1.x > v2.x) return 1;
+ if (v1.y < v2.y) return -1;
+ else if (v1.y > v2.y) return 1;
+ if (v1.z < v2.z) return -1;
+ else if (v1.z > v2.z) return 1;
+ return 0;
+ }
+
+ bool operator()(const Vector &v1, const Vector &v2) const {
+ return compare(v1, v2) < 0;
+ }
+};
ChiSquare::ChiSquare(int thetaBins, int phiBins, int numTests,
size_t sampleCount) : m_logLevel(EInfo), m_thetaBins(thetaBins),
@@ -69,29 +87,53 @@ void ChiSquare::dumpTables(const fs::path &filename) {
}
void ChiSquare::fill(
- const boost::function()> &sampleFn,
- const boost::function &pdfFn) {
+ const boost::function()> &sampleFn,
+ const boost::function &pdfFn) {
memset(m_table, 0, m_thetaBins*m_phiBins*sizeof(Float));
+ memset(m_refTable, 0, m_thetaBins*m_phiBins*sizeof(Float));
Log(m_logLevel, "Accumulating " SIZE_T_FMT " samples into a %ix%i"
" contingency table", m_sampleCount, m_thetaBins, m_phiBins);
Point2 factor(m_thetaBins / M_PI, m_phiBins / (2*M_PI));
+ std::set discreteDirections;
+
ref timer = new Timer();
for (size_t i=0; i sample = sampleFn();
- Point2 sphCoords = toSphericalCoordinates(sample.first);
+ boost::tuple sample = sampleFn();
+ Point2 sphCoords = toSphericalCoordinates(boost::get<0>(sample));
int thetaBin = std::min(std::max(0,
floorToInt(sphCoords.x * factor.x)), m_thetaBins-1);
int phiBin = std::min(std::max(0,
floorToInt(sphCoords.y * factor.y)), m_phiBins-1);
-
- m_table[thetaBin * m_phiBins + phiBin] += sample.second;
+m_table[thetaBin * m_phiBins + phiBin] += boost::get<1>(sample);
+ if (boost::get<1>(sample) > 0 && boost::get<2>(sample) == EDiscrete)
+ discreteDirections.insert(boost::get<0>(sample));
}
+
+ if (discreteDirections.size() > 0) {
+ Log(EDebug, "Incorporating the disrete density over "
+ SIZE_T_FMT " direction(s) into the contingency table", discreteDirections.size());
+ for (std::set::const_iterator it = discreteDirections.begin();
+ it != discreteDirections.end(); ++it) {
+ const Vector &direction = *it;
+ Point2 sphCoords = toSphericalCoordinates(direction);
+ Float pdf = pdfFn(direction, EDiscrete);
+
+ int thetaBin = std::min(std::max(0,
+ floorToInt(sphCoords.x * factor.x)), m_thetaBins-1);
+ int phiBin = std::min(std::max(0,
+ floorToInt(sphCoords.y * factor.y)), m_phiBins-1);
+
+ m_refTable[thetaBin * m_phiBins + phiBin] += pdf * m_sampleCount;
+ }
+ }
+
factor = Point2(M_PI / m_thetaBins, (2*M_PI) / m_phiBins);
- Log(m_logLevel, "Done, took %i ms. Integrating reference contingency table ..", timer->getMilliseconds());
+ Log(m_logLevel, "Done, took %i ms. Integrating reference "
+ "contingency table ..", timer->getMilliseconds());
timer->reset();
Float min[2], max[2];
size_t idx = 0;
@@ -113,10 +155,11 @@ void ChiSquare::fill(
);
integral += result;
- m_refTable[idx++] = result * m_sampleCount;
+ m_refTable[idx++] += result * m_sampleCount;
maxError = std::max(maxError, error);
}
}
+
Log(m_logLevel, "Done, took %i ms (max error = %f, integral=%f).",
timer->getMilliseconds(), maxError, integral);
}
@@ -132,7 +175,7 @@ struct SortedCellFunctor {
}
};
-ChiSquare::ETestResult ChiSquare::runTest(int distParams, Float pvalThresh) {
+ChiSquare::ETestResult ChiSquare::runTest(Float pvalThresh) {
/* Compute the chi-square statistic */
Float pooledCounts = 0, pooledRef = 0, chsq = 0.0f;
int pooledCells = 0, df = 0;
@@ -188,12 +231,14 @@ ChiSquare::ETestResult ChiSquare::runTest(int distParams, Float pvalThresh) {
++df;
}
- df -= distParams + 1;
+ /* All parameters are assumed to be known, so there is no
+ DF reduction due to model parameters */
+ df -= 1;
Log(m_logLevel, "Chi-square statistic = %e (df=%i)", chsq, df);
if (df <= 0) {
- Log(EWarn, "The number of degrees of freedom (%i) is too low!", df);
+ Log(m_logLevel, "The number of degrees of freedom (%i) is too low!", df);
return ELowDoF;
}
diff --git a/src/libcore/spectrum.cpp b/src/libcore/spectrum.cpp
index b06951db..92d4110b 100644
--- a/src/libcore/spectrum.cpp
+++ b/src/libcore/spectrum.cpp
@@ -17,6 +17,7 @@
*/
#include
+#include
MTS_NAMESPACE_BEGIN
@@ -47,7 +48,7 @@ void Spectrum::staticInitialization() {
}
void Spectrum::staticShutdown() {
- /* Do nothing */
+ /* Nothing to do */
}
void Spectrum::fromSmoothSpectrum(const SmoothSpectrum *smooth) {
@@ -260,9 +261,24 @@ Float BlackBodySpectrum::eval(Float l) const {
/ (1e9*1e4*(std::exp((h/k)*c/(lambda*m_temperature)) - 1.0));
return (Float) I;
}
+
+InterpolatedSpectrum::InterpolatedSpectrum(const fs::path &path) {
+ fs::ifstream is(path);
+ if (is.bad() || is.fail())
+ SLog(EError, "InterpolatedSpectrum: could not open \"%s\"",
+ path.file_string().c_str());
+
+ while (is.good() && !is.eof()) {
+ Float lambda, value;
+ is >> lambda >> value;
+ appendSample(lambda, value);
+ }
+}
void InterpolatedSpectrum::appendSample(Float lambda, Float value) {
- SAssert(m_wavelength.size() == 0 || m_wavelength[m_wavelength.size()-1] < lambda);
+ if (m_wavelength.size() != 0 && m_wavelength[m_wavelength.size()-1] >= lambda)
+ SLog(EError, "InterpolatedSpectrum: spectral power distribution values must "
+ "be provided in order of increasing wavelength!");
m_wavelength.push_back(lambda);
m_value.push_back(value);
}
diff --git a/src/librender/scenehandler.cpp b/src/librender/scenehandler.cpp
index 6b4156e2..c07d2b10 100644
--- a/src/librender/scenehandler.cpp
+++ b/src/librender/scenehandler.cpp
@@ -344,36 +344,50 @@ void SceneHandler::endElement(const XMLCh* const xmlName) {
discrete.fromSmoothSpectrum(&bb);
context.parent->properties.setSpectrum(context.attributes["name"], discrete);
} else if (name == "spectrum") {
- 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.appendSample(wavelength, value);
- }
- Spectrum discrete;
- discrete.fromSmoothSpectrum(&interp);
+ 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);
+ Spectrum discrete;
+ discrete.fromSmoothSpectrum(&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"],
- discrete);
+ Spectrum(value[0]));
} 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));
+ 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.appendSample(wavelength, value);
+ }
+ Spectrum discrete;
+ discrete.fromSmoothSpectrum(&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") {
diff --git a/src/tests/test_chisquare.cpp b/src/tests/test_chisquare.cpp
index 88c51b4f..958a5702 100644
--- a/src/tests/test_chisquare.cpp
+++ b/src/tests/test_chisquare.cpp
@@ -101,7 +101,7 @@ public:
m_fakeSampler = new FakeSampler(m_sampler);
}
- std::pair generateSample() {
+ boost::tuple generateSample() {
Point2 sample(m_sampler->next2D());
Intersection its;
BSDFQueryRecord bRec(its);
@@ -136,7 +136,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
- return std::make_pair(bRec.wo, 0.0f);
+ return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
} else if (sampled.isZero()) {
if (!f.isZero() && pdfVal != 0)
Log(EWarn, "Inconsistency (2): f=%s, pdf=%f, sampled f/pdf=%s, bRec=%s",
@@ -144,14 +144,14 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
- return std::make_pair(bRec.wo, 0.0f);
+ return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
}
Spectrum sampled2 = f/pdfVal;
if (!sampled.isValid() || !sampled2.isValid()) {
Log(EWarn, "Ooops: f=%s, pdf=%f, sampled f/pdf=%s, bRec=%s",
f.toString().c_str(), pdfVal, sampled.toString().c_str(), bRec.toString().c_str());
- return std::make_pair(bRec.wo, 0.0f);
+ return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
}
bool mismatch = false;
@@ -175,10 +175,11 @@ public:
disableFPExceptions();
#endif
- return std::make_pair(bRec.wo, 1.0f);
+ return boost::make_tuple(bRec.wo, 1.0f,
+ BSDF::getMeasure(bRec.sampledType));
}
- Float pdf(const Vector &wo) {
+ Float pdf(const Vector &wo, EMeasure measure) {
Intersection its;
BSDFQueryRecord bRec(its);
bRec.component = m_component;
@@ -191,9 +192,10 @@ public:
enableFPExceptions();
#endif
- if (m_bsdf->eval(bRec).isZero())
+ if (m_bsdf->eval(bRec, measure).isZero())
return 0.0f;
- Float result = m_bsdf->pdf(bRec);
+
+ Float result = m_bsdf->pdf(bRec, measure);
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
@@ -220,7 +222,7 @@ public:
: m_mRec(mRec), m_phase(phase), m_sampler(sampler), m_wi(wi),
m_largestWeight(0) { }
- std::pair generateSample() {
+ boost::tuple generateSample() {
Point2 sample(m_sampler->next2D());
PhaseFunctionQueryRecord pRec(m_mRec, m_wi);
@@ -240,7 +242,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
- return std::make_pair(pRec.wo, 0.0f);
+ return boost::make_tuple(pRec.wo, 0.0f, ESolidAngle);
} else if (sampled == 0) {
if (f != 0 && pdfVal != 0)
Log(EWarn, "Inconsistency: f=%f, pdf=%f, sampled f/pdf=%f",
@@ -248,7 +250,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
- return std::make_pair(pRec.wo, 0.0f);
+ return boost::make_tuple(pRec.wo, 0.0f, ESolidAngle);
}
Float sampled2 = f/pdfVal;
@@ -271,10 +273,13 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
- return std::make_pair(pRec.wo, 1.0f);
+ return boost::make_tuple(pRec.wo, 1.0f, ESolidAngle);
}
- Float pdf(const Vector &wo) const {
+ Float pdf(const Vector &wo, EMeasure measure) const {
+ if (measure != ESolidAngle)
+ return 0.0f;
+
PhaseFunctionQueryRecord pRec(m_mRec, m_wi, wo);
#if defined(MTS_DEBUG_FP)
enableFPExceptions();
@@ -351,11 +356,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&BSDFAdapter::generateSample, &adapter),
- boost::bind(&BSDFAdapter::pdf, &adapter, _1)
+ boost::bind(&BSDFAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
- ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
+ ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);
@@ -394,11 +399,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&BSDFAdapter::generateSample, &adapter),
- boost::bind(&BSDFAdapter::pdf, &adapter, _1)
+ boost::bind(&BSDFAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
- ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
+ ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);
@@ -460,11 +465,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&PhaseFunctionAdapter::generateSample, &adapter),
- boost::bind(&PhaseFunctionAdapter::pdf, &adapter, _1)
+ boost::bind(&PhaseFunctionAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
- ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
+ ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);