diff --git a/build/config-macos10.7-gcc-x86_64.py b/build/config-macos10.7-gcc-x86_64.py
index b4112976..5714db52 100644
--- a/build/config-macos10.7-gcc-x86_64.py
+++ b/build/config-macos10.7-gcc-x86_64.py
@@ -6,7 +6,7 @@ CCFLAGS = ['-arch', 'x86_64', '-mmacosx-version-min=10.7', '-march=nocona
LINKFLAGS = ['-framework', 'OpenGL', '-framework', 'Cocoa', '-arch', 'x86_64', '-mmacosx-version-min=10.7', '-Wl,-syslibroot,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk', '-Wl,-headerpad,128']
BASEINCLUDE = ['#include', '#dependencies/include']
BASELIBDIR = ['#dependencies/lib']
-BASELIB = ['m', 'pthread', 'gomp', 'Half']
+BASELIB = ['m', 'pthread', 'Half']
OEXRINCLUDE = ['#dependencies/include/OpenEXR']
OEXRLIB = ['IlmImf', 'Imath', 'Iex', 'z']
PNGLIB = ['png']
diff --git a/data/linux/fedora/mitsuba.spec b/data/linux/fedora/mitsuba.spec
index b1958bc3..aeb0a0f3 100644
--- a/data/linux/fedora/mitsuba.spec
+++ b/data/linux/fedora/mitsuba.spec
@@ -35,13 +35,14 @@ mkdir -p $RPM_BUILD_ROOT/usr/share/mitsuba/plugins
mkdir -p $RPM_BUILD_ROOT/usr/share/pixmaps
mkdir -p $RPM_BUILD_ROOT/usr/share/applications
mkdir -p $RPM_BUILD_ROOT/usr/include
-strip dist/lib* dist/mtsgui dist/mitsuba dist/mtssrv dist/mtsutil
+strip dist/lib* dist/mtsgui dist/mitsuba dist/mtssrv dist/mtsutil dist/mtsimport
strip dist/plugins/* dist/python/*/*
cp dist/libmitsuba-*.so $RPM_BUILD_ROOT%{_libdir}
cp dist/mtsgui $RPM_BUILD_ROOT%{_bindir}
cp dist/mitsuba $RPM_BUILD_ROOT%{_bindir}
cp dist/mtssrv $RPM_BUILD_ROOT%{_bindir}
cp dist/mtsutil $RPM_BUILD_ROOT%{_bindir}
+cp dist/mtsimport $RPM_BUILD_ROOT%{_bindir}
cp dist/python/2.7/mitsuba.so $RPM_BUILD_ROOT%{_libdir}/python2.7/lib-dynload
cp dist/plugins/* $RPM_BUILD_ROOT/usr/share/mitsuba/plugins
cp -Rdp dist/data $RPM_BUILD_ROOT/usr/share/mitsuba/data
diff --git a/data/schema/scene.xsd b/data/schema/scene.xsd
index 1a073b82..912259d1 100644
--- a/data/schema/scene.xsd
+++ b/data/schema/scene.xsd
@@ -26,7 +26,7 @@
-
+
@@ -43,6 +43,7 @@
+
@@ -50,14 +51,14 @@
-
+
-
+
@@ -140,7 +141,7 @@
-
+
@@ -297,7 +298,7 @@
-
+
@@ -314,6 +315,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/format.tex b/doc/format.tex
index cd03be70..0680da42 100644
--- a/doc/format.tex
+++ b/doc/format.tex
@@ -276,7 +276,7 @@ choices are available:
\begin{xml}
\end{xml}
-\item lookat transformations --- this is primarily useful for setting up cameras (and spot lights). The \code{origin} coordinates
+\item \code{lookat} transformations --- this is primarily useful for setting up cameras (and spot lights). The \code{origin} coordinates
specify the camera origin, \code{target} is the point that the camera will look at, and the
(optional) \code{up} parameter determines the ``upward'' direction in the final rendered image.
The \code{up} parameter is not needed for spot lights.
@@ -286,7 +286,33 @@ The \code{up} parameter is not needed for spot lights.
\end{itemize}
Cordinates that are zero (for \code{translate} and \code{rotate}) or one (for \code{scale})
do not explicitly have to be specified.
-\subsection{Instancing}
+\subsection{Animated transformations}
+Most shapes, emitters, and sensors in Mitsuba can accept both normal transformations
+and \emph{animated transformations} as parameters. The latter is useful to
+render scenes involving motion blur. The syntax used to specify these
+is slightly different:
+\begin{xml}
+
+
+ .. chained list of transformations as discussed above ..
+
+
+
+ .. chained list of transformations as discussed above ..
+
+
+ .. additional transformations (optional) ..
+
+\end{xml}
+Mitsuba then decomposes each transformation into a scale, translation, and
+rotation component and interpolates\footnote{Using linear interpolation
+for the scale and translation component and spherical linear quaternion
+interpolation for the rotation component.} these for intermediate
+time values.
+It is important to specify appropriate shutter open/close times
+to the sensor so that the motion is visible.
+
+\subsection{References}
Quite often, you will find yourself using an object (such as a material) in many places. To avoid having
to declare it over and over again, which wastes memory, you can make use of references. Here is an example
of how this works:
diff --git a/doc/macros.sty b/doc/macros.sty
index 6eae9806..854c02ed 100644
--- a/doc/macros.sty
+++ b/doc/macros.sty
@@ -31,7 +31,7 @@
\newcommand{\order}[1]{} % Ignore
\newcommand{\Transform}{\texttt{transform}}
-\newcommand{\Animation}{\texttt{animation}}
+\newcommand{\ATransform}{\texttt{atransform}}
\newcommand{\Spectrum}{\texttt{spectrum}}
\newcommand{\Integer}{\texttt{integer}}
\newcommand{\String}{\texttt{string}}
diff --git a/doc/main.tex b/doc/main.tex
index 170e619d..40d29280 100644
--- a/doc/main.tex
+++ b/doc/main.tex
@@ -116,7 +116,7 @@
medium,film,sampler,integrator,emitter,sensor,
translate,rotate,scale,lookat,point,vector,matrix,
include,fscat,volume,alias,rfilter,boolean,
- subsurface
+ subsurface,atransform
},
}
diff --git a/doc/misc.tex b/doc/misc.tex
index 34cc1329..890915db 100644
--- a/doc/misc.tex
+++ b/doc/misc.tex
@@ -24,7 +24,7 @@ It is important to keep in mind that other applications may not support this
``linearized sRGB'' space---in particular, the Mac OS preview currently
does not display images with this encoding correctly.
-\subsubsection{Spectral mode}
+\subsubsection{Spectral rendering}
Some predictive rendering applications will require a more realistic space for
interreflection computations. In such cases, Mitsuba can be switched to \emph{spectral mode}.
This can be done by compiling it with the \code{SPECTRUM\_SAMPLES=}$n$ parameter
diff --git a/include/mitsuba/core/math.h b/include/mitsuba/core/math.h
new file mode 100644
index 00000000..2d01b4ab
--- /dev/null
+++ b/include/mitsuba/core/math.h
@@ -0,0 +1,142 @@
+/*
+ This file is part of Mitsuba, a physically based rendering system.
+
+ Copyright (c) 2007-2012 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 .
+*/
+
+#pragma once
+
+#if !defined(__MITSUBA_CORE_MATH_H_)
+#define __MITSUBA_CORE_MATH_H_
+
+MTS_NAMESPACE_BEGIN
+
+namespace math {
+#if defined(__LINUX__) && defined(__x86_64__)
+ /*
+ The Linux/x86_64 single precision implementations of 'exp'
+ and 'log' suffer from a serious performance regression.
+ It is about 5x faster to use the double-precision versions
+ with the extra overhead of the involved FP conversion.
+
+ Until this is fixed, the following aliases make sure that
+ the fastest implementation is used in every case.
+ */
+ inline float fastexp(float value) {
+ return (float) ::exp((double) value);
+ }
+
+ inline double fastexp(double value) {
+ return ::exp(value);
+ }
+
+ inline float fastlog(float value) {
+ return (float) ::log((double) value);
+ }
+
+ inline double fastlog(double value) {
+ return ::log(value);
+ }
+#else
+ inline float fastexp(float value) {
+ return ::expf(value);
+ }
+
+ inline double fastexp(double value) {
+ return ::exp(value);
+ }
+
+ inline float fastlog(float value) {
+ return ::logf(value);
+ }
+
+ inline double fastlog(double value) {
+ return ::log(value);
+ }
+#endif
+
+#if defined(_GNU_SOURCE)
+ inline void sincos(float theta, float *sin, float *cos) {
+ ::sincosf(theta, sin, cos);
+ }
+
+ inline void sincos(double theta, double *sin, double *cos) {
+ ::sincos(theta, sin, cos);
+ }
+
+#else
+ inline void sincos(float theta, float *_sin, float *_cos) {
+ *_sin = sinf(theta);
+ *_cos = cosf(theta);
+ }
+
+ inline void sincos(double theta, double *_sin, double *_cos) {
+ *_sin = sin(theta);
+ *_cos = cos(theta);
+ }
+#endif
+
+ /// Arcsine variant that gracefully handles arguments > 1 that are due to roundoff errors
+ inline float safe_asin(float value) {
+ return std::asin(std::min(1.0f, std::max(-1.0f, value)));
+ }
+
+ /// Arcsine variant that gracefully handles arguments > 1 that are due to roundoff errors
+ inline double safe_asin(double value) {
+ return std::asin(std::min(1.0, std::max(-1.0, value)));
+ }
+
+ /// Arccosine variant that gracefully handles arguments > 1 that are due to roundoff errors
+ inline float safe_acos(float value) {
+ return std::acos(std::min(1.0f, std::max(-1.0f, value)));
+ }
+
+ /// Arccosine variant that gracefully handles arguments > 1 that are due to roundoff errors
+ inline double safe_acos(double value) {
+ return std::acos(std::min(1.0, std::max(-1.0, value)));
+ }
+
+ /// Square root variant that gracefully handles arguments < 0 that are due to roundoff errors
+ inline float safe_sqrt(float value) {
+ return std::sqrt(std::max(0.0f, value));
+ }
+
+ /// Square root variant that gracefully handles arguments < 0 that are due to roundoff errors
+ inline double safe_sqrt(double value) {
+ return std::sqrt(std::max(0.0, value));
+ }
+
+ /// Simple signum function -- note that it returns the FP sign of the input (and never zero)
+ inline Float signum(Float value) {
+ #if defined(__WINDOWS__)
+ return (Float) _copysign(1.0, value);
+ #elif defined(SINGLE_PRECISION)
+ return copysignf((float) 1.0, value);
+ #elif defined(DOUBLE_PRECISION)
+ return copysign((double) 1.0, value);
+ #endif
+ }
+}; /* namespace math */
+
+MTS_NAMESPACE_END
+
+#if defined(_MSC_VER)
+extern "C" {
+ extern MTS_EXPORT_CORE float nextafterf(float x, float y);
+ extern MTS_EXPORT_CORE double nextafter(double x, double y);
+};
+#endif
+
+#endif /* __MITSUBA_CORE_MATH_H_ */
diff --git a/include/mitsuba/core/properties.h b/include/mitsuba/core/properties.h
index bc505075..b8728daf 100644
--- a/include/mitsuba/core/properties.h
+++ b/include/mitsuba/core/properties.h
@@ -59,6 +59,8 @@ public:
EVector,
/// 4x4 transform for homogeneous coordinates
ETransform,
+ /// An animated 4x4 transformation
+ EAnimatedTransform,
/// Discretized color spectrum
ESpectrum,
/// Arbitrary-length string
@@ -152,6 +154,15 @@ public:
/// Get a linear transformation (with default)
Transform getTransform(const std::string &name, const Transform &defVal) const;
+ /// Set an animated linear transformation
+ void setAnimatedTransform(const std::string &name, const AnimatedTransform *value, bool warnDuplicates = true);
+ /// Get an animated linear transformation
+ ref getAnimatedTransform(const std::string &name) const;
+ /// Get an animated linear transformation (with default)
+ ref getAnimatedTransform(const std::string &name, const AnimatedTransform *defVal) const;
+ /// Get an animated linear transformation (with default)
+ ref getAnimatedTransform(const std::string &name, const Transform &defVal) const;
+
/// Set a spectral power distribution
void setSpectrum(const std::string &name, const Spectrum &value, bool warnDuplicates = true);
/// Get a spectral power distribution
diff --git a/include/mitsuba/core/quat.h b/include/mitsuba/core/quat.h
index cb8d98ee..66067290 100644
--- a/include/mitsuba/core/quat.h
+++ b/include/mitsuba/core/quat.h
@@ -132,12 +132,17 @@ template struct TQuaternion {
/// Equality test
bool operator==(const TQuaternion &q) const {
- return v == q.v && v.w == q.w;
+ return v == q.v && w == q.w;
}
/// Inequality test
bool operator!=(const TQuaternion &q) const {
- return v != q.v || v.w != q.w;
+ return v != q.v || w != q.w;
+ }
+
+ /// Identity test
+ bool isIdentity() const {
+ return v.isZero() && w == 1;
}
/// Return the rotation axis of this quaternion
@@ -216,39 +221,42 @@ template struct TQuaternion {
}
}
+ inline static TQuaternion fromTransform(const Transform &trafo) {
+ return fromMatrix(trafo.getMatrix());
+ }
+
/**
* \brief Construct an unit quaternion matching the supplied
* rotation matrix.
*/
- static TQuaternion fromTransform(const Transform trafo) {
+ static TQuaternion fromMatrix(const Matrix4x4 &m) {
/// Implementation from PBRT
- const Matrix4x4 &m = trafo.getMatrix();
- T trace = m.m[0][0] + m.m[1][1] + m.m[2][2];
+ T trace = m(0, 0) + m(1, 1) + m(2, 2);
TVector3 v; T w;
if (trace > 0.f) {
// Compute w from matrix trace, then xyz
- // 4w^2 = m[0][0] + m[1][1] + m[2][2] + m[3][3] (but m[3][3] == 1)
+ // 4w^2 = m[0, 0] + m[1, 1] + m[2, 2] + m[3, 3] (but m[3, 3] == 1)
T s = std::sqrt(trace + 1.0f);
w = s / 2.0f;
s = 0.5f / s;
- v.x = (m.m[2][1] - m.m[1][2]) * s;
- v.y = (m.m[0][2] - m.m[2][0]) * s;
- v.z = (m.m[1][0] - m.m[0][1]) * s;
+ v.x = (m(2, 1) - m(1, 2)) * s;
+ v.y = (m(0, 2) - m(2, 0)) * s;
+ v.z = (m(1, 0) - m(0, 1)) * s;
} else {
// Compute largest of $x$, $y$, or $z$, then remaining components
const int nxt[3] = {1, 2, 0};
T q[3];
int i = 0;
- if (m.m[1][1] > m.m[0][0]) i = 1;
- if (m.m[2][2] > m.m[i][i]) i = 2;
+ if (m(1, 1) > m(0, 0)) i = 1;
+ if (m(2, 2) > m(i, i)) i = 2;
int j = nxt[i];
int k = nxt[j];
- T s = std::sqrt((m.m[i][i] - (m.m[j][j] + m.m[k][k])) + 1.0);
+ T s = std::sqrt((m(i, i) - (m(j, j) + m(k, k))) + 1.0f);
q[i] = s * 0.5f;
if (s != 0.f) s = 0.5f / s;
- w = (m.m[k][j] - m.m[j][k]) * s;
- q[j] = (m.m[j][i] + m.m[i][j]) * s;
- q[k] = (m.m[k][i] + m.m[i][k]) * s;
+ w = (m(k, j) - m(j, k)) * s;
+ q[j] = (m(j, i) + m(i, j)) * s;
+ q[k] = (m(k, i) + m(i, k)) * s;
v.x = q[0];
v.y = q[1];
v.z = q[2];
diff --git a/include/mitsuba/core/stl.h b/include/mitsuba/core/stl.h
index f12a2a6c..05d31cef 100644
--- a/include/mitsuba/core/stl.h
+++ b/include/mitsuba/core/stl.h
@@ -20,120 +20,5 @@
#if !defined(__MITSUBA_CORE_STL_H_)
#define __MITSUBA_CORE_STL_H_
-namespace mitsuba {
-namespace math {
-#if defined(__LINUX__) && defined(__x86_64__)
- /*
- The Linux/x86_64 single precision implementations of 'exp'
- and 'log' suffer from a serious performance regression.
- It is about 5x faster to use the double-precision versions
- with the extra overhead of the involved FP conversion.
-
- Until this is fixed, the following aliases make sure that
- the fastest implementation is used in every case.
- */
- inline float fastexp(float value) {
- return (float) ::exp((double) value);
- }
-
- inline double fastexp(double value) {
- return ::exp(value);
- }
-
- inline float fastlog(float value) {
- return (float) ::log((double) value);
- }
-
- inline double fastlog(double value) {
- return ::log(value);
- }
-#else
- inline float fastexp(float value) {
- return ::expf(value);
- }
-
- inline double fastexp(double value) {
- return ::exp(value);
- }
-
- inline float fastlog(float value) {
- return ::logf(value);
- }
-
- inline double fastlog(double value) {
- return ::log(value);
- }
-#endif
-
-#if defined(_GNU_SOURCE)
- inline void sincos(float theta, float *sin, float *cos) {
- ::sincosf(theta, sin, cos);
- }
-
- inline void sincos(double theta, double *sin, double *cos) {
- ::sincos(theta, sin, cos);
- }
-
-#else
- inline void sincos(float theta, float *_sin, float *_cos) {
- *_sin = sinf(theta);
- *_cos = cosf(theta);
- }
-
- inline void sincos(double theta, double *_sin, double *_cos) {
- *_sin = sin(theta);
- *_cos = cos(theta);
- }
-#endif
-
- /// Arcsine variant that gracefully handles arguments > 1 that are due to roundoff errors
- inline float safe_asin(float value) {
- return std::asin(std::min(1.0f, std::max(-1.0f, value)));
- }
-
- /// Arcsine variant that gracefully handles arguments > 1 that are due to roundoff errors
- inline double safe_asin(double value) {
- return std::asin(std::min(1.0, std::max(-1.0, value)));
- }
-
- /// Arccosine variant that gracefully handles arguments > 1 that are due to roundoff errors
- inline float safe_acos(float value) {
- return std::acos(std::min(1.0f, std::max(-1.0f, value)));
- }
-
- /// Arccosine variant that gracefully handles arguments > 1 that are due to roundoff errors
- inline double safe_acos(double value) {
- return std::acos(std::min(1.0, std::max(-1.0, value)));
- }
-
- /// Square root variant that gracefully handles arguments < 0 that are due to roundoff errors
- inline float safe_sqrt(float value) {
- return std::sqrt(std::max(0.0f, value));
- }
-
- /// Square root variant that gracefully handles arguments < 0 that are due to roundoff errors
- inline double safe_sqrt(double value) {
- return std::sqrt(std::max(0.0, value));
- }
-
- /// Simple signum function -- note that it returns the FP sign of the input (and never zero)
- inline Float signum(Float value) {
- #if defined(__WINDOWS__)
- return (Float) _copysign(1.0, value);
- #elif defined(SINGLE_PRECISION)
- return copysignf((float) 1.0, value);
- #elif defined(DOUBLE_PRECISION)
- return copysign((double) 1.0, value);
- #endif
- }
-}; /* namespace math */
-}; /* namespace mitsuba */
-
-#if defined(_MSC_VER)
-extern "C" {
- extern MTS_EXPORT_CORE float nextafterf(float x, float y);
- extern MTS_EXPORT_CORE double nextafter(double x, double y);
-};
-#endif
-/// @endcond
+/// \endcond
#endif /* __MITSUBA_CORE_STL_H_ */
diff --git a/include/mitsuba/core/version.h b/include/mitsuba/core/version.h
index fbc7528e..50aeaf79 100644
--- a/include/mitsuba/core/version.h
+++ b/include/mitsuba/core/version.h
@@ -26,13 +26,13 @@ MTS_NAMESPACE_BEGIN
* \brief Current release of Mitsuba
* \ingroup libcore
*/
-#define MTS_VERSION "0.4.2"
+#define MTS_VERSION "0.4.3"
/**
* \brief Year of the current release
* \ingroup libcore
*/
-#define MTS_YEAR "2012"
+#define MTS_YEAR "2013"
/**
* \brief A simple data structure for representing and
diff --git a/include/mitsuba/hw/vpl.h b/include/mitsuba/hw/vpl.h
index f37307e2..7474bfff 100644
--- a/include/mitsuba/hw/vpl.h
+++ b/include/mitsuba/hw/vpl.h
@@ -326,11 +326,14 @@ protected:
* number of GPU pipeline flushes. Draw transparent objects last.
*/
struct MaterialOrder {
- inline bool operator()(
- const Renderer::TransformedGPUGeometry &g1,
- const Renderer::TransformedGPUGeometry &g2) const {
- const Shader *shader1 = g1.first->getShader();
- const Shader *shader2 = g2.first->getShader();
+ const std::vector &geo;
+
+ MaterialOrder(const std::vector &geo)
+ : geo(geo) { }
+
+ inline bool operator()(size_t idx1, size_t idx2) const {
+ const Shader *shader1 = geo[idx1].first->getShader();
+ const Shader *shader2 = geo[idx2].first->getShader();
if (shader1 && (shader1->getFlags() & Shader::ETransparent))
shader1 = NULL;
@@ -340,6 +343,19 @@ protected:
return shader1 < shader2;
}
};
+
+ /// Helper data structure to keep track of shapes that are undergoing keyframe animations
+ struct AnimatedGeometryRecord {
+ const AnimatedTransform *trafo;
+ ssize_t geometryIndex;
+ ssize_t opaqueGeometryIndex;
+
+ AnimatedGeometryRecord(const AnimatedTransform *trafo,
+ ssize_t geometryIndex, ssize_t opaqueGeometryIndex) :
+ trafo(trafo), geometryIndex(geometryIndex),
+ opaqueGeometryIndex(opaqueGeometryIndex) { }
+ };
+
MTS_DECLARE_CLASS()
private:
ref m_renderer;
@@ -348,6 +364,7 @@ private:
/* On-GPU geometry references */
std::vector m_geometry;
std::vector m_opaqueGeometry;
+ std::vector m_animatedGeometry;
/* Shader & dependency management */
std::map m_configurations;
diff --git a/include/mitsuba/mitsuba.h b/include/mitsuba/mitsuba.h
index 16a1c90c..d097e798 100644
--- a/include/mitsuba/mitsuba.h
+++ b/include/mitsuba/mitsuba.h
@@ -46,6 +46,7 @@ using std::endl;
#include
#include
#include
+#include
#include
#include
#include
diff --git a/include/mitsuba/render/emitter.h b/include/mitsuba/render/emitter.h
index b5836b03..4e30be11 100644
--- a/include/mitsuba/render/emitter.h
+++ b/include/mitsuba/render/emitter.h
@@ -425,7 +425,7 @@ protected:
/// Virtual destructor
virtual ~AbstractEmitter();
protected:
- ref m_worldTransform;
+ ref m_worldTransform;
ref m_medium;
Shape *m_shape;
uint32_t m_type;
diff --git a/include/mitsuba/render/gkdtree.h b/include/mitsuba/render/gkdtree.h
index c0509d97..4f19ec4e 100644
--- a/include/mitsuba/render/gkdtree.h
+++ b/include/mitsuba/render/gkdtree.h
@@ -1321,7 +1321,9 @@ protected:
return a.axis < b.axis;
if (a.pos != b.pos)
return a.pos < b.pos;
- return a.type < b.type;
+ if (a.type != b.type)
+ return a.type < b.type;
+ return a.index < b.index;
}
};
diff --git a/include/mitsuba/render/sahkdtree3.h b/include/mitsuba/render/sahkdtree3.h
index 9db4338d..8053d121 100644
--- a/include/mitsuba/render/sahkdtree3.h
+++ b/include/mitsuba/render/sahkdtree3.h
@@ -63,7 +63,8 @@ public:
* Given a split on axis \a axis that produces children having extents
* \a leftWidth and \a rightWidth along \a axis, compute the probability
* of traversing the left and right child during a typical query
- * operation.
+ * operation. In the case of the surface area heuristic, this is simply
+ * the ratio of surface areas.
*/
inline std::pair operator()(int axis, Float leftWidth, Float rightWidth) const {
return std::pair(
diff --git a/include/mitsuba/render/sahkdtree4.h b/include/mitsuba/render/sahkdtree4.h
new file mode 100644
index 00000000..6900bd80
--- /dev/null
+++ b/include/mitsuba/render/sahkdtree4.h
@@ -0,0 +1,304 @@
+/*
+ This file is part of Mitsuba, a physically based rendering system.
+
+ Copyright (c) 2007-2012 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 .
+*/
+
+#if !defined(__SAH_KDTREE4_H)
+#define __SAH_KDTREE4_H
+
+#include
+
+MTS_NAMESPACE_BEGIN
+
+typedef TAABB AABB4;
+
+/**
+ * \brief Implements the four-dimensional surface area heuristic for use
+ * by the \ref GenericKDTree construction algorithm.
+ */
+class SurfaceAreaHeuristic4 {
+public:
+ /**
+ * \brief Initialize the surface area heuristic with the bounds of
+ * a parent node
+ *
+ * Precomputes some information so that traversal probabilities
+ * of potential split planes can be evaluated efficiently
+ */
+ inline SurfaceAreaHeuristic4(const AABB4 &aabb) : m_aabb(aabb) {
+ const Vector4 extents(aabb.getExtents());
+ const Float temp = 1.0f / (extents.x * extents.y
+ + extents.y*extents.z + extents.x*extents.z);
+
+ m_temp0 = Vector4(
+ extents.y * extents.z * temp,
+ extents.x * extents.z * temp,
+ extents.x * extents.y * temp,
+ 0.0f);
+
+ m_temp1 = Vector4(
+ (extents.y + extents.z) * temp,
+ (extents.x + extents.z) * temp,
+ (extents.x + extents.y) * temp,
+ 1.0f / extents.w);
+ }
+
+ /**
+ * Given a split on axis \a axis that produces children having extents
+ * \a leftWidth and \a rightWidth along \a axis, compute the probability
+ * of traversing the left and right child during a typical query
+ * operation.
+ */
+ inline std::pair operator()(int axis, Float leftWidth, Float rightWidth) const {
+ if (axis == 3 && m_temp1.w == std::numeric_limits::infinity()) {
+ return std::pair(
+ std::numeric_limits::infinity(),
+ std::numeric_limits::infinity()
+ );
+ }
+
+ return std::pair(
+ m_temp0[axis] + m_temp1[axis] * leftWidth,
+ m_temp0[axis] + m_temp1[axis] * rightWidth);
+ }
+
+ /**
+ * Compute the underlying quantity used by the tree construction
+ * heuristic. This is used to compute the final cost of a kd-tree.
+ */
+ inline static Float getQuantity(const AABB4 &aabb) {
+ const Vector4 extents(aabb.getExtents());
+ Float result = 2 * (extents[0] * extents[1] + extents[1] * extents[2]
+ + extents[2] * extents[0]);
+ if (extents[3] != 0)
+ result *= extents[3];
+ return result;
+ }
+private:
+ Vector4 m_temp0, m_temp1;
+ AABB4 m_aabb;
+};
+
+/**
+ * This class specializes \ref GenericKDTree to a four-dimensional
+ * tree to be used for spacetime ray tracing. One additional function call
+ * must be implemented by subclasses:
+ *
+ * /// Check whether a primitive is intersected by the given ray.
+ * /// Some temporary space is supplied, which can be used to cache
+ * /// information about the intersection
+ * bool intersect(const Ray &ray, IndexType idx,
+ * Float mint, Float maxt, Float &t, void *tmp);
+ *
+ * This class implements an epsilon-free version of the optimized ray
+ * traversal algorithm (TA^B_{rec}), which is explained in Vlastimil
+ * Havran's PhD thesis "Heuristic Ray Shooting Algorithms".
+ *
+ * \author Wenzel Jakob
+ */
+template
+ class SAHKDTree4D : public GenericKDTree {
+public:
+ typedef typename KDTreeBase::SizeType SizeType;
+ typedef typename KDTreeBase::IndexType IndexType;
+ typedef typename KDTreeBase::KDNode KDNode;
+
+protected:
+ void buildInternal() {
+ SizeType primCount = this->cast()->getPrimitiveCount();
+ KDLog(EInfo, "Constructing a 4D SAH kd-tree (%i primitives) ..", primCount);
+ GenericKDTree::buildInternal();
+ }
+
+ /**
+ * \brief Hashed mailbox implementation
+ */
+ struct HashedMailbox {
+ inline HashedMailbox() {
+ memset(entries, 0xFF, sizeof(IndexType)*MTS_KD_MAILBOX_SIZE);
+ }
+
+ inline void put(IndexType primIndex) {
+ entries[primIndex & MTS_KD_MAILBOX_MASK] = primIndex;
+ }
+
+ inline bool contains(IndexType primIndex) const {
+ return entries[primIndex & MTS_KD_MAILBOX_MASK] == primIndex;
+ }
+
+ IndexType entries[MTS_KD_MAILBOX_SIZE];
+ };
+
+ /// Ray traversal stack entry for Havran-style incoherent ray tracing
+ struct KDStackEntryHavran {
+ /* Pointer to the far child */
+ const KDNode * __restrict node;
+ /* Distance traveled along the ray (entry or exit) */
+ Float t;
+ /* Previous stack item */
+ uint32_t prev;
+ /* Associated point */
+ Point p;
+ };
+
+ /**
+ * \brief Ray tracing kd-tree traversal loop (Havran variant)
+ *
+ * This is generally the most robust and fastest traversal routine
+ * of the methods implemented in this class.
+ */
+ template FINLINE
+ bool rayIntersectHavran(const Ray &ray, Float mint, Float maxt,
+ Float &t, void *temp) const {
+ KDStackEntryHavran stack[MTS_KD_MAXDEPTH];
+ #if 0
+ static const int prevAxisTable[] = { 2, 0, 1 };
+ static const int nextAxisTable[] = { 1, 2, 0 };
+ #endif
+
+ #if defined(MTS_KD_MAILBOX_ENABLED)
+ HashedMailbox mailbox;
+ #endif
+
+ /* Set up the entry point */
+ uint32_t enPt = 0;
+ stack[enPt].t = mint;
+ stack[enPt].p = ray(mint);
+
+ /* Set up the exit point */
+ uint32_t exPt = 1;
+ stack[exPt].t = maxt;
+ stack[exPt].p = ray(maxt);
+ stack[exPt].node = NULL;
+
+ bool foundIntersection = false;
+ const KDNode * __restrict currNode = this->m_nodes;
+ while (currNode != NULL) {
+ while (EXPECT_TAKEN(!currNode->isLeaf())) {
+ const Float splitVal = (Float) currNode->getSplit();
+ const int axis = currNode->getAxis();
+ const KDNode * __restrict farChild;
+
+ if (axis == 3) {
+ if (ray.time <= splitVal)
+ currNode = currNode->getLeft();
+ else
+ currNode = currNode->getRight();
+ continue;
+ } else if (stack[enPt].p[axis] <= splitVal) {
+ if (stack[exPt].p[axis] <= splitVal) {
+ /* Cases N1, N2, N3, P5, Z2 and Z3 (see thesis) */
+ currNode = currNode->getLeft();
+ continue;
+ }
+
+ /* Typo in Havran's thesis:
+ (it specifies "stack[exPt].p == splitVal", which
+ is clearly incorrect) */
+ if (stack[enPt].p[axis] == splitVal) {
+ /* Case Z1 */
+ currNode = currNode->getRight();
+ continue;
+ }
+
+ /* Case N4 */
+ currNode = currNode->getLeft();
+ farChild = currNode + 1; // getRight()
+ } else { /* stack[enPt].p[axis] > splitVal */
+ if (splitVal < stack[exPt].p[axis]) {
+ /* Cases P1, P2, P3 and N5 */
+ currNode = currNode->getRight();
+ continue;
+ }
+ /* Case P4 */
+ farChild = currNode->getLeft();
+ currNode = farChild + 1; // getRight()
+ }
+
+ /* Cases P4 and N4 -- calculate the distance to the split plane */
+ Float distToSplit = (splitVal - ray.o[axis]) * ray.dRcp[axis];
+
+ /* Set up a new exit point */
+ const uint32_t tmp = exPt++;
+ if (exPt == enPt) /* Do not overwrite the entry point */
+ ++exPt;
+
+ KDAssert(exPt < MTS_KD_MAXDEPTH);
+ stack[exPt].prev = tmp;
+ stack[exPt].t = distToSplit;
+ stack[exPt].node = farChild;
+
+ #if 1
+ /* Intrestingly, this appears to be faster than the
+ original code with the prevAxis & nextAxis table */
+ stack[exPt].p = ray(distToSplit);
+ stack[exPt].p[axis] = splitVal;
+ #else
+ const int nextAxis = nextAxisTable[axis];
+ const int prevAxis = prevAxisTable[axis];
+ stack[exPt].p[axis] = splitVal;
+ stack[exPt].p[nextAxis] = ray.o[nextAxis]
+ + distToSplit*ray.d[nextAxis];
+ stack[exPt].p[prevAxis] = ray.o[prevAxis]
+ + distToSplit*ray.d[prevAxis];
+ #endif
+
+ }
+
+ /* Reached a leaf node */
+ for (IndexType entry=currNode->getPrimStart(),
+ last = currNode->getPrimEnd(); entry != last; entry++) {
+ const IndexType primIdx = this->m_indices[entry];
+
+ #if defined(MTS_KD_MAILBOX_ENABLED)
+ if (mailbox.contains(primIdx))
+ continue;
+ #endif
+
+ bool result;
+ if (!shadowRay)
+ result = this->cast()->intersect(ray, primIdx, mint, maxt, t, temp);
+ else
+ result = this->cast()->intersect(ray, primIdx, mint, maxt);
+
+ if (result) {
+ if (shadowRay)
+ return true;
+ maxt = t;
+ foundIntersection = true;
+ }
+
+ #if defined(MTS_KD_MAILBOX_ENABLED)
+ mailbox.put(primIdx);
+ #endif
+ }
+
+ if (stack[exPt].t > maxt)
+ break;
+
+ /* Pop from the stack and advance to the next node on the interval */
+ enPt = exPt;
+ currNode = stack[exPt].node;
+ exPt = stack[enPt].prev;
+ }
+
+ return foundIntersection;
+ }
+};
+
+MTS_NAMESPACE_END
+
+#endif /* __SAH_KDTREE4_H */
diff --git a/include/mitsuba/render/scenehandler.h b/include/mitsuba/render/scenehandler.h
index 38ebae2e..2959a6b3 100644
--- a/include/mitsuba/render/scenehandler.h
+++ b/include/mitsuba/render/scenehandler.h
@@ -158,7 +158,8 @@ private:
EBoolean, EString, ETranslate, ERotate,
ELookAt, EScale, EMatrix, EPoint,
EVector, ERGB, ESRGB, EBlackBody,
- ESpectrum, ETransform, EInclude, EAlias
+ ESpectrum, ETransform, EATransform,
+ EInclude, EAlias
};
typedef std::pair TagEntry;
@@ -173,6 +174,7 @@ private:
std::stack m_context;
TagMap m_tags;
Transform m_transform;
+ ref m_animatedTransform;
bool m_isIncludedFile;
};
diff --git a/include/mitsuba/render/shape.h b/include/mitsuba/render/shape.h
index 8ad7e0a3..723c8807 100644
--- a/include/mitsuba/render/shape.h
+++ b/include/mitsuba/render/shape.h
@@ -193,6 +193,9 @@ public:
/**
* \brief Return the shape's surface area
*
+ * Assumes that the object is not undergoing some kind of
+ * time-dependent scaling.
+ *
* The default implementation throws an exception
*/
virtual Float getSurfaceArea() const;
diff --git a/include/mitsuba/render/skdtree.h b/include/mitsuba/render/skdtree.h
index efc302dd..e6f25f9d 100644
--- a/include/mitsuba/render/skdtree.h
+++ b/include/mitsuba/render/skdtree.h
@@ -32,9 +32,9 @@
#if defined(SINGLE_PRECISION)
/// 32 byte temporary storage for intersection computations
-#define MTS_KD_INTERSECTION_TEMP 32
-#else
#define MTS_KD_INTERSECTION_TEMP 64
+#else
+#define MTS_KD_INTERSECTION_TEMP 128
#endif
MTS_NAMESPACE_BEGIN
diff --git a/include/mitsuba/render/track.h b/include/mitsuba/render/track.h
index 25049a3d..4c418c58 100644
--- a/include/mitsuba/render/track.h
+++ b/include/mitsuba/render/track.h
@@ -22,6 +22,7 @@
#include
#include
+#include
MTS_NAMESPACE_BEGIN
@@ -65,6 +66,9 @@ public:
/// Serialize to a binary data stream
virtual void serialize(Stream *stream) const = 0;
+ /// Clone this track
+ virtual AbstractAnimationTrack *clone() const = 0;
+
MTS_DECLARE_CLASS()
protected:
AbstractAnimationTrack(EType type, size_t nKeyframes)
@@ -82,9 +86,9 @@ protected:
*/
template class AnimationTrack : public AbstractAnimationTrack {
public:
- typedef T value_type;
+ typedef T ValueType;
- AnimationTrack(EType type, size_t nKeyframes)
+ AnimationTrack(EType type, size_t nKeyframes = 0)
: AbstractAnimationTrack(type, nKeyframes), m_values(nKeyframes) { }
AnimationTrack(EType type, Stream *stream)
@@ -95,11 +99,38 @@ public:
unserialize(stream, m_values[i]);
}
+ /// Copy constructor
+ AnimationTrack(const AnimationTrack *track)
+ : AbstractAnimationTrack(track->getType(), track->getSize()) {
+ m_times = track->m_times;
+ m_values = track->m_values;
+ }
+
/// Set the value of a certain keyframe
- inline void setValue(size_t idx, const value_type &value) { m_values[idx] = value; }
+ inline void setValue(size_t idx, const ValueType &value) { m_values[idx] = value; }
/// Return the value of a certain keyframe
- inline const value_type &getValue(size_t idx) const { return m_values[idx]; }
+ inline const ValueType &getValue(size_t idx) const { return m_values[idx]; }
+
+ /// Reserve space for a certain number of entries
+ inline void reserve(size_t count) { m_times.reserve(count); m_values.reserve(count); }
+
+ /// Append a value
+ inline void append(Float time, const ValueType &value) {
+ m_times.push_back(time);
+ m_values.push_back(value);
+ }
+
+ /// Clone this instance
+ AbstractAnimationTrack *clone() const {
+ return new AnimationTrack(this);
+ }
+
+ /// Prepend a transformation to every entry of this track
+ void prependTransformation(const ValueType &value) {
+ for (size_t i=0; i 0);
std::vector::const_iterator entry =
std::lower_bound(m_times.begin(), m_times.end(), time);
@@ -126,19 +157,84 @@ public:
}
return lerp(idx0, idx1, t);
}
+
+private:
+ struct SortPredicate {
+ inline bool operator()(const std::pair &p1,
+ const std::pair &p2) const {
+ return p1.first < p2.first;
+ }
+ };
+
+ struct UniqueTimePredicate {
+ inline bool operator()(const std::pair &p1,
+ const std::pair &p2) const {
+ return p1.first == p2.first;
+ }
+ };
+
+public:
+ /**
+ * \brief Sort all animation tracks and remove
+ * unnecessary data (for user-provided input)
+ *
+ * \return \c false if this animation track was deemed to be "trivial"
+ * after the cleanup (for instance, it only contains (0,0,0) translation operations)
+ */
+ bool sortAndSimplify() {
+ SAssert(m_values.size() == m_times.size());
+ if (m_values.size() == 0)
+ return false;
+
+ std::vector< std::pair > temp(m_values.size());
+ for (size_t i=0; ireadElement();
+ /// Is this a "no-op" transformation?
+ inline bool isNoOp(const ValueType &value) const;
+
+ /// Concatenate two transformations
+ inline ValueType concatenateTransformations(
+ const ValueType &value1, const ValueType &value2) const;
+
+ inline void unserialize(Stream *stream, ValueType &value) {
+ value = stream->readElement();
}
- inline void serialize(Stream *stream, const value_type &value) const {
- stream->writeElement(value);
+ inline void serialize(Stream *stream, const ValueType &value) const {
+ stream->writeElement(value);
}
private:
- std::vector m_values;
+ std::vector m_values;
};
template inline T AnimationTrack::lerp(size_t idx0, size_t idx1, Float t) const {
@@ -150,6 +246,53 @@ template<> inline Quaternion AnimationTrack::lerp(size_t idx0, size_
return slerp(m_values[idx0], m_values[idx1], t);
}
+template inline T AnimationTrack::concatenateTransformations(
+ const T &value1, const T &value2) const {
+ return value1 * value2;
+}
+
+template<> inline Vector AnimationTrack::concatenateTransformations(
+ const Vector &value1, const Vector &value2) const {
+ if (m_type == ETranslationXYZ)
+ return value1 + value2;
+ else
+ return Vector(value1.x * value2.x, value1.y * value2.y, value1.z * value2.z);
+}
+
+template<> inline Float AnimationTrack::concatenateTransformations(
+ const Float &value1, const Float &value2) const {
+ if (m_type == ETranslationX || m_type == ETranslationY || m_type == ETranslationZ)
+ return value1 + value2;
+ else
+ return value1 * value2;
+}
+
+template inline bool AnimationTrack::isNoOp(const ValueType &value) const {
+ return false;
+}
+
+template<> inline bool AnimationTrack::isNoOp(const Float &value) const {
+ if ((m_type == ETranslationX || m_type == ETranslationY || m_type == ETranslationZ) && value == 0)
+ return true;
+ else if ((m_type == ERotationX || m_type == ERotationY || m_type == ERotationZ) && value == 0)
+ return true;
+ else if ((m_type == EScaleX || m_type == EScaleY || m_type == EScaleZ) && value == 1)
+ return true;
+ return false;
+}
+
+template<> inline bool AnimationTrack::isNoOp(const Vector &value) const {
+ if (m_type == ETranslationXYZ && value.isZero())
+ return true;
+ else if (m_type == EScaleXYZ && (value.x == 1 && value.y == 1 && value.z == 1))
+ return true;
+ return false;
+}
+
+template<> inline bool AnimationTrack::isNoOp(const Quaternion &value) const {
+ return value.isIdentity();
+}
+
template<> inline void AnimationTrack::unserialize(Stream *stream, Point &value) {
value = Point(stream);
}
@@ -179,7 +322,7 @@ template<> inline void AnimationTrack::serialize(Stream *stream, con
* \ingroup librender
*/
class MTS_EXPORT_RENDER AnimatedTransform : public Object {
-protected:
+private:
/// Internal functor used by \ref eval() and \ref SimpleCache
struct MTS_EXPORT_RENDER TransformFunctor {
public:
@@ -204,12 +347,27 @@ public:
/// Unserialized an animated transformation from a binary data stream
AnimatedTransform(Stream *stream);
+ /// Copy constructor
+ AnimatedTransform(const AnimatedTransform *trafo);
+
/// Return the number of associated animation tracks
inline size_t getTrackCount() const { return m_tracks.size(); }
+ /// Find a track of the given type
+ AbstractAnimationTrack *findTrack(AbstractAnimationTrack::EType type);
+
+ /// Find a track of the given type
+ const AbstractAnimationTrack *findTrack(AbstractAnimationTrack::EType type) const;
+
/// Look up one of the tracks by index
+ inline AbstractAnimationTrack *getTrack(size_t idx) { return m_tracks[idx]; }
+
+ /// Look up one of the tracks by index (const version)
inline const AbstractAnimationTrack *getTrack(size_t idx) const { return m_tracks[idx]; }
+ /// Return the used keyframes as a set
+ void collectKeyframes(std::set &result) const;
+
/// Append an animation track
void addTrack(AbstractAnimationTrack *track);
@@ -221,7 +379,7 @@ public:
* to this function.
*/
inline const Transform &eval(Float t) const {
- if (m_tracks.size() == 0)
+ if (EXPECT_TAKEN(m_tracks.size() == 0))
return m_transform;
else
return m_cache.get(TransformFunctor(m_tracks), t);
@@ -230,6 +388,12 @@ public:
/// Is the animation static?
inline bool isStatic() const { return m_tracks.size() == 0; }
+ /**
+ * \brief Sort all animation tracks and remove unnecessary
+ * data (for user-provided input)
+ */
+ void sortAndSimplify();
+
/// Transform a point by an affine / non-projective matrix
inline Point transformAffine(Float t, const Point &p) const {
return eval(t).transformAffine(p);
@@ -290,6 +454,9 @@ public:
eval(t).operator()(r, dest);
}
+ /// Prepend a scale transformation to the transform (this is often useful)
+ void prependScale(const Vector &scale);
+
/// Serialize to a binary data stream
void serialize(Stream *stream) const;
diff --git a/src/emitters/collimated.cpp b/src/emitters/collimated.cpp
index bc55ad33..1ba51fdb 100644
--- a/src/emitters/collimated.cpp
+++ b/src/emitters/collimated.cpp
@@ -26,7 +26,7 @@ MTS_NAMESPACE_BEGIN
* \icon{emitter_collimated}
* \order{5}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional emitter-to-world transformation.
* \default{none (i.e. emitter space $=$ world space)}
* }
diff --git a/src/emitters/directional.cpp b/src/emitters/directional.cpp
index 25ea9bbb..834857e0 100644
--- a/src/emitters/directional.cpp
+++ b/src/emitters/directional.cpp
@@ -25,7 +25,7 @@ MTS_NAMESPACE_BEGIN
* \icon{emitter_directional}
* \order{4}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional emitter-to-world transformation.
* \default{none (i.e. emitter space $=$ world space)}
* }
diff --git a/src/emitters/point.cpp b/src/emitters/point.cpp
index 5741dbf7..7edc7d73 100644
--- a/src/emitters/point.cpp
+++ b/src/emitters/point.cpp
@@ -28,7 +28,7 @@ MTS_NAMESPACE_BEGIN
* \icon{emitter_point}
* \order{1}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional sensor-to-world transformation.
* \default{none (i.e. sensor space $=$ world space)}
* }
diff --git a/src/emitters/sky.cpp b/src/emitters/sky.cpp
index fe3a559b..2daf4284 100644
--- a/src/emitters/sky.cpp
+++ b/src/emitters/sky.cpp
@@ -71,6 +71,10 @@ MTS_NAMESPACE_BEGIN
* Specifies the relative amount of samples
* allocated to this emitter. \default{1}
* }
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
+ * Specifies an optional sensor-to-world transformation.
+ * \default{none (i.e. sensor space $=$ world space)}
+ * }
* }
*
* \renderings{
@@ -376,8 +380,7 @@ public:
bitmapData.ptr = (uint8_t *) bitmap.get();
bitmapData.size = sizeof(Bitmap);
props.setData("bitmap", bitmapData);
- const Transform &trafo = m_worldTransform->eval(0);
- props.setTransform("toWorld", trafo);
+ props.setAnimatedTransform("toWorld", m_worldTransform);
props.setFloat("samplingWeight", m_samplingWeight);
Emitter *emitter = static_cast(
PluginManager::getInstance()->createObject(
diff --git a/src/emitters/spot.cpp b/src/emitters/spot.cpp
index 4acb24c6..acbcac86 100644
--- a/src/emitters/spot.cpp
+++ b/src/emitters/spot.cpp
@@ -25,7 +25,7 @@ MTS_NAMESPACE_BEGIN
* \icon{emitter_spot}
* \order{3}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional sensor-to-world transformation.
* \default{none (i.e. sensor space $=$ world space)}
* }
diff --git a/src/emitters/sun.cpp b/src/emitters/sun.cpp
index f09ff716..89222785 100644
--- a/src/emitters/sun.cpp
+++ b/src/emitters/sun.cpp
@@ -221,8 +221,7 @@ public:
bitmapData.ptr = (uint8_t *) bitmap.get();
bitmapData.size = sizeof(Bitmap);
props.setData("bitmap", bitmapData);
- const Transform &trafo = m_worldTransform->eval(0);
- props.setTransform("toWorld", trafo);
+ props.setAnimatedTransform("toWorld", m_worldTransform);
props.setFloat("samplingWeight", m_samplingWeight);
Emitter *emitter = static_cast(
PluginManager::getInstance()->createObject(
diff --git a/src/emitters/sunsky.cpp b/src/emitters/sunsky.cpp
index 26766552..37a3c35b 100644
--- a/src/emitters/sunsky.cpp
+++ b/src/emitters/sunsky.cpp
@@ -223,7 +223,7 @@ public:
bitmapData.ptr = (uint8_t *) bitmap.get();
bitmapData.size = sizeof(Bitmap);
envProps.setData("bitmap", bitmapData);
- envProps.setTransform("toWorld", trafo);
+ envProps.setAnimatedTransform("toWorld", m_worldTransform);
envProps.setFloat("samplingWeight", m_samplingWeight);
m_envEmitter = static_cast(
PluginManager::getInstance()->createObject(
diff --git a/src/libbidir/SConscript b/src/libbidir/SConscript
index 5a78630c..69161888 100644
--- a/src/libbidir/SConscript
+++ b/src/libbidir/SConscript
@@ -9,7 +9,7 @@ bidirEnv.Append(CPPDEFINES = [['MTS_BUILD_MODULE', 'MTS_MODULE_BIDIR']])
libbidir = bidirEnv.SharedLibrary('mitsuba-bidir', [
'common.cpp', 'rsampler.cpp', 'vertex.cpp', 'edge.cpp',
'path.cpp', 'verification.cpp', 'util.cpp', 'pathsampler.cpp',
- 'mut_bidir.cpp', 'mut_lens.cpp', 'mut_caustic.cpp',
+ 'mut_bidir.cpp', 'mut_lens.cpp', 'mut_caustic.cpp',
'mut_mchain.cpp', 'manifold.cpp', 'mut_manifold.cpp'
])
diff --git a/src/libcore/properties.cpp b/src/libcore/properties.cpp
index ba4db6ec..ef6e5da6 100644
--- a/src/libcore/properties.cpp
+++ b/src/libcore/properties.cpp
@@ -18,6 +18,7 @@
#include
#include
+#include
/* Keep the boost::variant includes outside of properties.h,
since they noticeably add to the overall compile times */
@@ -26,7 +27,7 @@
MTS_NAMESPACE_BEGIN
typedef boost::variant<
- bool, int64_t, Float, Point, Vector, Transform,
+ bool, int64_t, Float, Point, Vector, Transform, AnimatedTransform *,
Spectrum, std::string, Properties::Data> ElementData;
struct PropertyElement {
@@ -78,33 +79,104 @@ DEFINE_PROPERTY_ACCESSOR(Spectrum, Spectrum, Spectrum, spectrum)
DEFINE_PROPERTY_ACCESSOR(std::string, std::string, String, string)
DEFINE_PROPERTY_ACCESSOR(Properties::Data, Properties::Data, Data, data)
+void Properties::setAnimatedTransform(const std::string &name, const AnimatedTransform *value, bool warnDuplicates) {
+ if (hasProperty(name)) {
+ AnimatedTransform **old = boost::get(&((*m_elements)[name].data));
+ if (old)
+ (*old)->decRef();
+ if (warnDuplicates)
+ SLog(EWarn, "Property \"%s\" was specified multiple times!", name.c_str());
+ }
+ (*m_elements)[name].data = (AnimatedTransform *) value;
+ (*m_elements)[name].queried = false;
+ value->incRef();
+}
+
+ref Properties::getAnimatedTransform(const std::string &name) const {
+ std::map::const_iterator it = m_elements->find(name);
+ if (it == m_elements->end())
+ SLog(EError, "Property \"%s\" missing", name.c_str());
+ const AnimatedTransform * const * result1 = boost::get(&it->second.data);
+ const Transform *result2 = boost::get(&it->second.data);
+
+ if (!result1 && !result2)
+ SLog(EError, "The property \"%s\" has the wrong type (expected or ). The "
+ "complete property record is :\n%s", name.c_str(), toString().c_str());
+ it->second.queried = true;
+
+ if (result1)
+ return *result1;
+ else
+ return new AnimatedTransform(*result2);
+}
+
+ref Properties::getAnimatedTransform(const std::string &name, const AnimatedTransform *defVal) const {
+ std::map::const_iterator it = m_elements->find(name);
+ if (it == m_elements->end())
+ return defVal;
+ AnimatedTransform * const * result1 = boost::get(&it->second.data);
+ const Transform *result2 = boost::get(&it->second.data);
+
+ if (!result1 && !result2)
+ SLog(EError, "The property \"%s\" has the wrong type (expected or ). The "
+ "complete property record is :\n%s", name.c_str(), toString().c_str());
+
+ it->second.queried = true;
+
+ if (result1)
+ return *result1;
+ else
+ return new AnimatedTransform(*result2);
+}
+
+ref Properties::getAnimatedTransform(const std::string &name, const Transform &defVal) const {
+ std::map::const_iterator it = m_elements->find(name);
+ if (it == m_elements->end())
+ return new AnimatedTransform(defVal);
+
+ AnimatedTransform * const * result1 = boost::get(&it->second.data);
+ const Transform *result2 = boost::get(&it->second.data);
+
+ if (!result1 && !result2)
+ SLog(EError, "The property \"%s\" has the wrong type (expected or ). The "
+ "complete property record is :\n%s", name.c_str(), toString().c_str());
+ it->second.queried = true;
+
+ if (result1)
+ return *result1;
+ else
+ return new AnimatedTransform(*result2);
+}
+
namespace {
class TypeVisitor : public boost::static_visitor {
public:
- Properties::EPropertyType operator()(const bool &) const { return Properties::EBoolean; }
- Properties::EPropertyType operator()(const int64_t &) const { return Properties::EInteger; }
- Properties::EPropertyType operator()(const Float &) const { return Properties::EFloat; }
- Properties::EPropertyType operator()(const Point &) const { return Properties::EPoint; }
- Properties::EPropertyType operator()(const Vector &) const { return Properties::EVector; }
- Properties::EPropertyType operator()(const Transform &) const { return Properties::ETransform; }
- Properties::EPropertyType operator()(const Spectrum &) const { return Properties::ESpectrum; }
- Properties::EPropertyType operator()(const std::string &) const { return Properties::EString; }
- Properties::EPropertyType operator()(const Properties::Data &) const { return Properties::EData; }
+ Properties::EPropertyType operator()(const bool &) const { return Properties::EBoolean; }
+ Properties::EPropertyType operator()(const int64_t &) const { return Properties::EInteger; }
+ Properties::EPropertyType operator()(const Float &) const { return Properties::EFloat; }
+ Properties::EPropertyType operator()(const Point &) const { return Properties::EPoint; }
+ Properties::EPropertyType operator()(const Vector &) const { return Properties::EVector; }
+ Properties::EPropertyType operator()(const Transform &) const { return Properties::ETransform; }
+ Properties::EPropertyType operator()(const AnimatedTransform *) const { return Properties::EAnimatedTransform; }
+ Properties::EPropertyType operator()(const Spectrum &) const { return Properties::ESpectrum; }
+ Properties::EPropertyType operator()(const std::string &) const { return Properties::EString; }
+ Properties::EPropertyType operator()(const Properties::Data &) const { return Properties::EData; }
};
class EqualityVisitor : public boost::static_visitor {
public:
EqualityVisitor(const ElementData *ref) : ref(ref) { }
- bool operator()(const bool &v) const { const bool *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const int64_t &v) const { const int64_t *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Float &v) const { const Float *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Point &v) const { const Point *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Vector &v) const { const Vector *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Transform &v) const { const Transform *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Spectrum &v) const { const Spectrum *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const std::string &v) const { const std::string *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
- bool operator()(const Properties::Data &v) const { const Properties::Data *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const bool &v) const { const bool *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const int64_t &v) const { const int64_t *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Float &v) const { const Float *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Point &v) const { const Point *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Vector &v) const { const Vector *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Transform &v) const { const Transform *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const AnimatedTransform *v) const { AnimatedTransform * const *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Spectrum &v) const { const Spectrum *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const std::string &v) const { const std::string *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
+ bool operator()(const Properties::Data &v) const { const Properties::Data *v2 = boost::get(ref); return v2 ? (v == *v2) : false; }
private:
const ElementData *ref;
};
@@ -113,15 +185,16 @@ namespace {
public:
StringVisitor(std::ostringstream &oss, bool quote) : oss(oss), quote(quote) { }
- void operator()(const bool &v) const { oss << (v ? "true" : "false"); }
- void operator()(const int64_t &v) const { oss << v; }
- void operator()(const Float &v) const { oss << v; }
- void operator()(const Point &v) const { oss << v.toString(); }
- void operator()(const Vector &v) const { oss << v.toString(); }
- void operator()(const Transform &v) const { oss << v.toString(); }
- void operator()(const Spectrum &v) const { oss << v.toString(); }
- void operator()(const std::string &v) const { oss << (quote ? "\"" : "") << v << (quote ? "\"" : ""); }
- void operator()(const Properties::Data &v) const { oss << v.ptr << " (size=" << v.size << ")"; }
+ void operator()(const bool &v) const { oss << (v ? "true" : "false"); }
+ void operator()(const int64_t &v) const { oss << v; }
+ void operator()(const Float &v) const { oss << v; }
+ void operator()(const Point &v) const { oss << v.toString(); }
+ void operator()(const Vector &v) const { oss << v.toString(); }
+ void operator()(const Transform &v) const { oss << v.toString(); }
+ void operator()(const AnimatedTransform *v) const { oss << ((Object *) v)->toString(); }
+ void operator()(const Spectrum &v) const { oss << v.toString(); }
+ void operator()(const std::string &v) const { oss << (quote ? "\"" : "") << v << (quote ? "\"" : ""); }
+ void operator()(const Properties::Data &v) const { oss << v.ptr << " (size=" << v.size << ")"; }
private:
std::ostringstream &oss;
bool quote;
@@ -141,16 +214,44 @@ Properties::Properties(const std::string &pluginName)
Properties::Properties(const Properties &props)
: m_pluginName(props.m_pluginName), m_id(props.m_id) {
m_elements = new std::map(*props.m_elements);
+
+ for (std::map::iterator it = m_elements->begin();
+ it != m_elements->end(); ++it) {
+ AnimatedTransform **trafo = boost::get(&(*it).second.data);
+ if (trafo)
+ (*trafo)->incRef();
+ }
}
Properties::~Properties() {
+ for (std::map::iterator it = m_elements->begin();
+ it != m_elements->end(); ++it) {
+ AnimatedTransform **trafo = boost::get(&(*it).second.data);
+ if (trafo)
+ (*trafo)->decRef();
+ }
+
delete m_elements;
}
void Properties::operator=(const Properties &props) {
+ for (std::map::iterator it = m_elements->begin();
+ it != m_elements->end(); ++it) {
+ AnimatedTransform **trafo = boost::get(&(*it).second.data);
+ if (trafo)
+ (*trafo)->decRef();
+ }
+
m_pluginName = props.m_pluginName;
m_id = props.m_id;
*m_elements = *props.m_elements;
+
+ for (std::map::iterator it = m_elements->begin();
+ it != m_elements->end(); ++it) {
+ AnimatedTransform **trafo = boost::get(&(*it).second.data);
+ if (trafo)
+ (*trafo)->incRef();
+ }
}
bool Properties::hasProperty(const std::string &name) const {
@@ -161,6 +262,9 @@ bool Properties::removeProperty(const std::string &name) {
std::map::iterator it = m_elements->find(name);
if (it == m_elements->end())
return false;
+ AnimatedTransform **trafo = boost::get(&(*it).second.data);
+ if (trafo)
+ (*trafo)->decRef();
m_elements->erase(it);
return true;
}
diff --git a/src/libhw/SConscript b/src/libhw/SConscript
index c0ca0c82..34ef87af 100644
--- a/src/libhw/SConscript
+++ b/src/libhw/SConscript
@@ -1,8 +1,8 @@
Import('env', 'sys', 'os')
libhw_objects = [
- 'session.cpp', 'device.cpp', 'gputexture.cpp', 'gpugeometry.cpp',
- 'gpuprogram.cpp', 'renderer.cpp', 'glrenderer.cpp', 'glprogram.cpp',
+ 'session.cpp', 'device.cpp', 'gputexture.cpp', 'gpugeometry.cpp',
+ 'gpuprogram.cpp', 'renderer.cpp', 'glrenderer.cpp', 'glprogram.cpp',
'glgeometry.cpp', 'gltexture.cpp', 'gpusync.cpp', 'glsync.cpp',
'vpl.cpp', 'font.cpp', 'viewer.cpp', 'basicshader.cpp', 'shadow.cpp']
@@ -14,7 +14,7 @@ if sys.platform == 'win32':
elif sys.platform == 'linux2':
libhw_objects += ['x11session.cpp',
'x11device.cpp',
- 'glxdevice.cpp',
+ 'glxdevice.cpp',
'glxrenderer.cpp']
glEnv = env.Clone()
diff --git a/src/libhw/vpl.cpp b/src/libhw/vpl.cpp
index 79cd24e4..877d7715 100644
--- a/src/libhw/vpl.cpp
+++ b/src/libhw/vpl.cpp
@@ -146,6 +146,7 @@ void VPLShaderManager::setScene(const Scene *scene) {
m_geometry.reserve(shapes.size());
m_opaqueGeometry.clear();
m_opaqueGeometry.reserve(shapes.size());
+ m_animatedGeometry.clear();
Matrix4x4 identityTrafo;
identityTrafo.setIdentity();
@@ -158,7 +159,8 @@ void VPLShaderManager::setScene(const Scene *scene) {
const Instance *instance = static_cast(shape);
const std::vector &instantiatedShapes =
instance->getShapeGroup()->getKDTree()->getShapes();
- const Matrix4x4 &trafo = instance->getWorldTransform().getMatrix();
+ const AnimatedTransform *atrafo = instance->getWorldTransform();
+ const Matrix4x4 &trafo = atrafo->eval(0).getMatrix();
for (size_t j=0; jsetShader(shader);
+ ssize_t geometryIndex = (ssize_t) m_geometry.size(), opaqueGeometryIndex = -1;
m_geometry.push_back(std::make_pair(gpuGeo, trafo));
- if (shader && !(shader->getFlags() & Shader::ETransparent))
+ if (shader && !(shader->getFlags() & Shader::ETransparent)) {
+ opaqueGeometryIndex = (ssize_t) m_opaqueGeometry.size();
m_opaqueGeometry.push_back(std::make_pair(gpuGeo, trafo));
+ }
+
+ if (!atrafo->isStatic()) {
+ m_animatedGeometry.push_back(AnimatedGeometryRecord(atrafo,
+ geometryIndex, opaqueGeometryIndex));
+ }
}
} else {
GPUGeometry *gpuGeo = m_renderer->registerGeometry(shape);
@@ -233,8 +243,40 @@ void VPLShaderManager::setScene(const Scene *scene) {
m_backgroundParam_emitterScale = prog->getParameterID("emitterScale", false);
}
+ std::vector geometryPermutation(m_geometry.size()),
+ opaqueGeometryPermutation(m_opaqueGeometry.size());
+
+ for (size_t i=0; i geometryPermutationInv(m_geometry.size()),
+ opaqueGeometryPermutationInv(m_opaqueGeometry.size());
+
+ for (size_t i=0; i= 0)
+ agRec.geometryIndex = geometryPermutationInv[agRec.geometryIndex];
+ if (agRec.opaqueGeometryIndex >= 0)
+ agRec.opaqueGeometryIndex = opaqueGeometryPermutationInv[agRec.opaqueGeometryIndex];
+ }
+ }
+
+ permute_inplace(&m_geometry[0], geometryPermutation);
+ permute_inplace(&m_opaqueGeometry[0], opaqueGeometryPermutation);
}
void VPLShaderManager::setVPL(const VPL &vpl) {
@@ -244,6 +286,18 @@ void VPLShaderManager::setVPL(const VPL &vpl) {
m_nearClip = std::numeric_limits::infinity();
m_farClip = -std::numeric_limits::infinity();
+ /* Update animations */
+ for (size_t i=0; ieval(vpl.its.time).getMatrix();
+
+ if (agRec.geometryIndex >= 0)
+ m_geometry[agRec.geometryIndex].second = matrix;
+
+ if (agRec.opaqueGeometryIndex >= 0)
+ m_opaqueGeometry[agRec.opaqueGeometryIndex].second = matrix;
+ }
+
if (vpl.type != EDirectionalEmitterVPL) {
/* Trace a few rays from the VPL to estimate a suitable depth range */
for (size_t i=0; i
#include
#include
+#include
#include
MTS_NAMESPACE_BEGIN
@@ -102,6 +103,7 @@ SceneHandler::SceneHandler(const SAXParser *parser,
m_tags["blackbody"] = TagEntry(EBlackBody, (Class *) NULL);
m_tags["spectrum"] = TagEntry(ESpectrum, (Class *) NULL);
m_tags["transform"] = TagEntry(ETransform, (Class *) NULL);
+ m_tags["atransform"] = TagEntry(EATransform, (Class *) NULL);
m_tags["include"] = TagEntry(EInclude, (Class *) NULL);
m_tags["alias"] = TagEntry(EAlias, (Class *) NULL);
@@ -179,11 +181,12 @@ void SceneHandler::characters(const XMLCh* const name,
Float SceneHandler::parseFloat(const std::string &name,
const std::string &str, Float defVal) const {
char *end_ptr = NULL;
- if (str == "") {
+ if (str.empty()) {
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());
@@ -251,6 +254,19 @@ void SceneHandler::startElement(const XMLCh* const xmlName,
case ETransform:
m_transform = Transform();
break;
+ case EATransform: {
+ m_animatedTransform = new AnimatedTransform();
+ ref translation = new VectorTrack(VectorTrack::ETranslationXYZ);
+ ref rotation = new QuatTrack(VectorTrack::ERotationQuat);
+ ref scaling = new VectorTrack(VectorTrack::EScaleXYZ);
+ translation->reserve(2);
+ rotation->reserve(2);
+ scaling->reserve(2);
+ m_animatedTransform->addTrack(translation);
+ m_animatedTransform->addTrack(rotation);
+ m_animatedTransform->addTrack(scaling);
+ }
+ break;
default:
break;
}
@@ -270,7 +286,7 @@ void SceneHandler::endElement(const XMLCh* const xmlName) {
if (context.attributes.find("id") != context.attributes.end())
context.properties.setID(context.attributes["id"]);
- ref object = NULL;
+ ref object;
TagMap::const_iterator it = m_tags.find(name);
if (it == m_tags.end())
@@ -590,9 +606,56 @@ void SceneHandler::endElement(const XMLCh* const xmlName) {
}
break;
+ case EATransform: {
+ m_animatedTransform->sortAndSimplify();
+ context.parent->properties.setAnimatedTransform(
+ context.attributes["name"], m_animatedTransform);
+ m_animatedTransform = NULL;
+ }
+ break;
+
case ETransform: {
- context.parent->properties.setTransform(
- context.attributes["name"], m_transform);
+ if (!m_animatedTransform.get()) {
+ context.parent->properties.setTransform(
+ context.attributes["name"], m_transform);
+ } else {
+ /* Compute the polar decomposition and insert into the animated transform
+ uh oh.. we have to get rid of 2 matrix libraries at some point :) */
+ typedef Eigen::Matrix EMatrix;
+ const Matrix4x4 m = m_transform.getMatrix();
+
+ EMatrix A;
+ A << m(0, 0), m(0, 1), m(0, 2),
+ m(1, 0), m(1, 1), m(1, 2),
+ m(2, 0), m(2, 1), m(2, 2);
+
+ Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV);
+ EMatrix U = svd.matrixU(), V = svd.matrixV(), S = svd.singularValues().asDiagonal();
+
+ if (svd.singularValues().prod() < 0) {
+ S = -S; U = -U;
+ }
+
+ EMatrix Q = U*V.transpose();
+ EMatrix P = V*S*V.transpose();
+
+ VectorTrack *translation = (VectorTrack *) m_animatedTransform->getTrack(0);
+ QuatTrack *rotation = (QuatTrack *) m_animatedTransform->getTrack(1);
+ VectorTrack *scaling = (VectorTrack *) m_animatedTransform->getTrack(2);
+
+ Float time = parseFloat("time", context.attributes["time"]);
+ rotation->append(time, Quaternion::fromMatrix(
+ Matrix4x4(
+ Q(0, 0), Q(0, 1), Q(0, 2), 0.0f,
+ Q(1, 0), Q(1, 1), Q(1, 2), 0.0f,
+ Q(2, 0), Q(2, 1), Q(2, 2), 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ )
+ ));
+
+ scaling->append(time, Vector(P(0, 0), P(1, 1), P(2, 2)));
+ translation->append(time, Vector(m(0, 3), m(1, 3), m(2, 3)));
+ }
}
break;
@@ -638,11 +701,61 @@ void SceneHandler::endElement(const XMLCh* const xmlName) {
}
break;
- default:
- if (tag.second == NULL)
- XMLLog(EError, "Internal error: could not instantiate an object "
- "corresponding to the tag '%s'", name.c_str());
- object = m_pluginManager->createObject(tag.second, context.properties);
+ default: {
+ if (tag.second == NULL)
+ XMLLog(EError, "Internal error: could not instantiate an object "
+ "corresponding to the tag '%s'", name.c_str());
+
+ Properties &props = context.properties;
+
+ /* Convenience hack: allow passing animated transforms to arbitrary shapes
+ and then internally rewrite this into a shape group + animated instance */
+ if (tag.second == MTS_CLASS(Shape)
+ && props.hasProperty("toWorld")
+ && props.getType("toWorld") == Properties::EAnimatedTransform
+ && (props.getPluginName() != "instance" && props.getPluginName() != "disk")) {
+ /* (The 'disk' plugin also directly supports animated transformations, so
+ the instancing trick isn't required for it) */
+
+ ref trafo = props.getAnimatedTransform("toWorld");
+ props.removeProperty("toWorld");
+
+ if (trafo->isStatic())
+ props.setTransform("toWorld", trafo->eval(0));
+
+ object = m_pluginManager->createObject(tag.second, props);
+
+ if (!trafo->isStatic()) {
+ object = m_pluginManager->createObject(tag.second, props);
+ /* 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();
+ }
+ }
+ context.children.clear();
+
+ object->configure();
+
+ ref shapeGroup = static_cast (
+ m_pluginManager->createObject(MTS_CLASS(Shape), Properties("shapegroup")));
+ shapeGroup->addChild(object);
+ shapeGroup->configure();
+
+ Properties instanceProps("instance");
+ instanceProps.setAnimatedTransform("toWorld", trafo);
+ object = m_pluginManager->createObject(instanceProps);
+ object->addChild(shapeGroup);
+
+ }
+ } else {
+ object = m_pluginManager->createObject(tag.second, props);
+ }
+ }
break;
}
diff --git a/src/librender/track.cpp b/src/librender/track.cpp
index e438d0f4..47f1b3c1 100644
--- a/src/librender/track.cpp
+++ b/src/librender/track.cpp
@@ -3,6 +3,16 @@
MTS_NAMESPACE_BEGIN
+AnimatedTransform::AnimatedTransform(const AnimatedTransform *trafo)
+ : m_transform(trafo->m_transform) {
+ m_tracks.reserve(trafo->getTrackCount());
+ for (size_t i=0; igetTrackCount(); ++i) {
+ AbstractAnimationTrack *track = trafo->getTrack(i)->clone();
+ m_tracks.push_back(track);
+ track->incRef();
+ }
+}
+
AnimatedTransform::AnimatedTransform(Stream *stream) {
size_t nTracks = stream->readSize();
if (nTracks == 0) {
@@ -48,18 +58,9 @@ void AnimatedTransform::addTrack(AbstractAnimationTrack *track) {
AABB1 AnimatedTransform::getTimeBounds() const {
if (m_tracks.size() == 0)
-#if !defined(__clang__)
return AABB1(0.0f, 0.0f);
-#else
- // HACK Workaround for clang
- {
- AABB1 b;
- b.min = b.max = 0.0f;
- return b;
- }
-#endif
- Float min = std::numeric_limits::infinity();
+ Float min = std::numeric_limits::infinity();
Float max = -std::numeric_limits::infinity();
for (size_t i=0; igetTime(size-1));
}
-#if !defined(__clang__)
return AABB1(min, max);
-#else
- // HACK Workaround for clang
- AABB1 b;
- b.min = min;
- b.max = max;
- return b;
-#endif
}
AABB AnimatedTransform::getTranslationBounds() const {
@@ -152,6 +145,122 @@ AnimatedTransform::~AnimatedTransform() {
m_tracks[i]->decRef();
}
+void AnimatedTransform::sortAndSimplify() {
+ bool isStatic = true;
+
+ for (size_t i=0; igetType()) {
+ case AbstractAnimationTrack::ETranslationX:
+ case AbstractAnimationTrack::ETranslationY:
+ case AbstractAnimationTrack::ETranslationZ:
+ case AbstractAnimationTrack::ERotationX:
+ case AbstractAnimationTrack::ERotationY:
+ case AbstractAnimationTrack::ERotationZ:
+ case AbstractAnimationTrack::EScaleX:
+ case AbstractAnimationTrack::EScaleY:
+ case AbstractAnimationTrack::EScaleZ:
+ isNeeded = static_cast(track)->sortAndSimplify();
+ break;
+ case AbstractAnimationTrack::ETranslationXYZ:
+ case AbstractAnimationTrack::EScaleXYZ:
+ isNeeded = static_cast(track)->sortAndSimplify();
+ break;
+ case AbstractAnimationTrack::ERotationQuat:
+ isNeeded = static_cast(track)->sortAndSimplify();
+ break;
+ default:
+ Log(EError, "Encountered an unsupported "
+ "animation track type: %i!", track->getType());
+ }
+ if (isNeeded) {
+ isStatic &= track->getSize() == 1;
+ } else {
+ m_tracks.erase(m_tracks.begin() + i);
+ track->decRef();
+ --i;
+ }
+ }
+
+ if (isStatic) {
+ Transform temp;
+ temp = eval(0);
+ m_transform = temp;
+ for (size_t i=0; idecRef();
+ m_tracks.clear();
+ }
+}
+
+
+const AbstractAnimationTrack *AnimatedTransform::findTrack(AbstractAnimationTrack::EType type) const {
+ for (size_t i=0; igetType() == type)
+ return track;
+ }
+ return NULL;
+}
+AbstractAnimationTrack *AnimatedTransform::findTrack(AbstractAnimationTrack::EType type) {
+ for (size_t i=0; igetType() == type)
+ return track;
+ }
+ return NULL;
+}
+
+void AnimatedTransform::prependScale(const Vector &scale) {
+ FloatTrack *trackX = (FloatTrack *) findTrack(AbstractAnimationTrack::EScaleX);
+ FloatTrack *trackY = (FloatTrack *) findTrack(AbstractAnimationTrack::EScaleY);
+ FloatTrack *trackZ = (FloatTrack *) findTrack(AbstractAnimationTrack::EScaleZ);
+ VectorTrack *trackXYZ = (VectorTrack *) findTrack(AbstractAnimationTrack::EScaleXYZ);
+
+ if (m_tracks.empty()) {
+ m_transform = m_transform * Transform::scale(scale);
+ } else if (trackXYZ) {
+ trackXYZ->prependTransformation(scale);
+ } else if (trackX && trackY && trackZ) {
+ if (trackX) {
+ trackX->prependTransformation(scale.x);
+ } else {
+ trackX = new FloatTrack(AbstractAnimationTrack::EScaleX);
+ trackX->append(0.0f, scale.x); addTrack(trackX);
+ }
+
+ if (trackY) {
+ trackY->prependTransformation(scale.y);
+ } else {
+ trackY = new FloatTrack(AbstractAnimationTrack::EScaleY);
+ trackY->append(0.0f, scale.y); addTrack(trackY);
+ }
+
+ if (trackZ) {
+ trackZ->prependTransformation(scale.z);
+ } else {
+ trackZ = new FloatTrack(AbstractAnimationTrack::EScaleZ);
+ trackZ->append(0.0f, scale.z); addTrack(trackZ);
+ }
+ } else {
+ trackXYZ = new VectorTrack(AbstractAnimationTrack::EScaleXYZ);
+ trackXYZ->append(0.0f, scale);
+ addTrack(trackXYZ);
+ }
+}
+
+void AnimatedTransform::collectKeyframes(std::set &result) const {
+ for (size_t i=0; igetSize(); ++j)
+ result.insert(track->getTime(j));
+ }
+
+ if (result.size() == 0)
+ result.insert(0);
+}
+
void AnimatedTransform::serialize(Stream *stream) const {
stream->writeSize(m_tracks.size());
if (m_tracks.size() == 0) {
@@ -203,9 +312,13 @@ void AnimatedTransform::TransformFunctor::operator()(const Float &t, Transform &
}
}
- trafo = Transform::translate(translation) *
- rotation.toTransform() *
- Transform::scale(scale);
+ trafo = Transform::translate(translation);
+
+ if (!rotation.isIdentity())
+ trafo = trafo * rotation.toTransform();
+
+ if (scale != Vector(0.0f))
+ trafo = trafo * Transform::scale(scale);
}
std::string AnimatedTransform::toString() const {
diff --git a/src/librender/vpl.cpp b/src/librender/vpl.cpp
index def62936..e23dadd6 100644
--- a/src/librender/vpl.cpp
+++ b/src/librender/vpl.cpp
@@ -30,7 +30,7 @@ static void appendVPL(const Scene *scene, Random *random,
const Sensor *sensor = scene->getSensor();
Float time = sensor->getShutterOpen()
- + 0.5f * sensor->getShutterOpenTime();
+ + sensor->getShutterOpenTime() * random->nextFloat();
if (prune) {
/* Possibly reject VPLs if they are unlikely to be
@@ -86,11 +86,12 @@ size_t generateVPLs(const Scene *scene, Random *random,
sampler = static_cast (PluginManager::getInstance()->
createObject(MTS_CLASS(Sampler), props));
sampler->configure();
+ sampler->generate(Point2i(0));
}
const Sensor *sensor = scene->getSensor();
Float time = sensor->getShutterOpen()
- + 0.5f * sensor->getShutterOpenTime();
+ + sensor->getShutterOpenTime() * sampler->next1D();
const Frame stdFrame(Vector(1,0,0), Vector(0,1,0), Vector(0,0,1));
@@ -110,6 +111,7 @@ size_t generateVPLs(const Scene *scene, Random *random,
if (!emitter->isEnvironmentEmitter() && emitter->needsDirectionSample()) {
VPL lumVPL(EPointEmitterVPL, weight);
lumVPL.its.p = pRec.p;
+ lumVPL.its.time = time;
lumVPL.its.shFrame = pRec.n.isZero() ? stdFrame : Frame(pRec.n);
lumVPL.emitter = emitter;
appendVPL(scene, random, lumVPL, prune, vpls);
@@ -128,6 +130,7 @@ size_t generateVPLs(const Scene *scene, Random *random,
VPL lumVPL(EDirectionalEmitterVPL, weight2);
lumVPL.its.p = Point(0.0);
+ lumVPL.its.time = time;
lumVPL.its.shFrame = Frame(-diRec.d);
lumVPL.emitter = emitter;
appendVPL(scene, random, lumVPL, false, vpls);
diff --git a/src/mtsgui/SConscript b/src/mtsgui/SConscript
index a0c70beb..85ec98da 100644
--- a/src/mtsgui/SConscript
+++ b/src/mtsgui/SConscript
@@ -1,4 +1,4 @@
-Import('env', 'os', 'glob', 'sys', 'hasQt', 'hasCollada', 'hasBreakpad', 'mainEnv',
+Import('env', 'os', 'glob', 'sys', 'hasQt', 'hasCollada', 'hasBreakpad', 'mainEnv',
'resources', 'converter_objects')
# For running Uic & Moc (below)
@@ -58,7 +58,7 @@ if hasQt:
qtEnv.Prepend(LIBPATH=env['COLLADALIBDIR'])
if env.has_key('COLLADALIB'):
qtEnv.Prepend(LIBS=env['COLLADALIB'])
-
+
if sys.platform == 'darwin':
mainEnv_osx = mainEnv.Clone()
qtEnv_osx = qtEnv.Clone()
diff --git a/src/mtsgui/save.cpp b/src/mtsgui/save.cpp
index ae8a35a7..fe51b38d 100644
--- a/src/mtsgui/save.cpp
+++ b/src/mtsgui/save.cpp
@@ -78,6 +78,31 @@ static void setProperties(QDomDocument &doc, QDomElement &element,
property = doc.createElement("string");
property.setAttribute("value", props.getString(*it).c_str());
break;
+ case Properties::EAnimatedTransform: {
+ const AnimatedTransform *trafo = props.getAnimatedTransform(*it);
+
+ std::set times;
+ trafo->collectKeyframes(times);
+
+ property = doc.createElement("atransform");
+
+ for (std::set::iterator it2 = times.begin(); it2 != times.end(); ++it2) {
+ const Matrix4x4 &matrix = trafo->eval(*it2).getMatrix();
+ QDomElement trafoTag = doc.createElement("transform");
+ QDomElement matrixTag = doc.createElement("matrix");
+
+ QString value;
+ for (int i=0; i<4; ++i)
+ for (int j=0; j<4; ++j)
+ value += QString("%1 ").arg(matrix(i, j));
+
+ matrixTag.setAttribute("value", value);
+ trafoTag.setAttribute("time", *it2);
+ trafoTag.appendChild(matrixTag);
+ property.appendChild(trafoTag);
+ }
+ }
+ break;
case Properties::ETransform: {
/* Captures the subset of transformations that are used by
Mitsuba's perspective and orthographic camera classes */
diff --git a/src/sensors/fluencemeter.cpp b/src/sensors/fluencemeter.cpp
index 53c87e18..cf0dca9e 100644
--- a/src/sensors/fluencemeter.cpp
+++ b/src/sensors/fluencemeter.cpp
@@ -26,7 +26,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{fluencemeter}{Fluence meter}
* \order{7}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional sensor-to-world transformation.
* \default{none (i.e. sensor space $=$ world space)}
* }
diff --git a/src/sensors/orthographic.cpp b/src/sensors/orthographic.cpp
index 37ea76d4..8e885bc7 100644
--- a/src/sensors/orthographic.cpp
+++ b/src/sensors/orthographic.cpp
@@ -27,7 +27,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{orthographic}{Orthographic camera}
* \order{3}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional camera-to-world transformation.
* \default{none (i.e. camera space $=$ world space)}
* }
diff --git a/src/sensors/perspective.cpp b/src/sensors/perspective.cpp
index 6a521467..9e09290b 100644
--- a/src/sensors/perspective.cpp
+++ b/src/sensors/perspective.cpp
@@ -26,7 +26,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{perspective}{Perspective pinhole camera}
* \order{1}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional camera-to-world transformation.
* \default{none (i.e. camera space $=$ world space)}
* }
@@ -113,7 +113,7 @@ public:
foreshortening terms caused by the aperture, hence the flag "EOnSurface" */
m_type |= EDeltaPosition | EPerspectiveCamera | EOnSurface | EDirectionSampleMapsToPixels;
- if (props.getTransform("toWorld", Transform()).hasScale())
+ if (props.getAnimatedTransform("toWorld", Transform())->eval(0).hasScale())
Log(EError, "Scale factors in the camera-to-world "
"transformation are not allowed!");
}
diff --git a/src/sensors/radiancemeter.cpp b/src/sensors/radiancemeter.cpp
index 758b9d7c..73c94644 100644
--- a/src/sensors/radiancemeter.cpp
+++ b/src/sensors/radiancemeter.cpp
@@ -25,7 +25,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{radiancemeter}{Radiance meter}
* \order{6}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional sensor-to-world transformation.
* \default{none (i.e. sensor space $=$ world space)}
* }
diff --git a/src/sensors/spherical.cpp b/src/sensors/spherical.cpp
index 4ef108a4..65c19717 100644
--- a/src/sensors/spherical.cpp
+++ b/src/sensors/spherical.cpp
@@ -25,7 +25,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{spherical}{Spherical camera}
* \order{5}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional camera-to-world transformation.
* \default{none (i.e. camera space $=$ world space)}
* }
diff --git a/src/sensors/telecentric.cpp b/src/sensors/telecentric.cpp
index 96f06fe3..77441d78 100644
--- a/src/sensors/telecentric.cpp
+++ b/src/sensors/telecentric.cpp
@@ -27,7 +27,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{telecentric}{Telecentric lens camera}
* \order{4}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional sensor-to-world transformation.
* \default{none (i.e. camera space $=$ world space)}
* }
diff --git a/src/sensors/thinlens.cpp b/src/sensors/thinlens.cpp
index 0107915f..fce964b8 100644
--- a/src/sensors/thinlens.cpp
+++ b/src/sensors/thinlens.cpp
@@ -29,7 +29,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{thinlens}{Perspective camera with a thin lens}
* \order{2}
* \parameters{
- * \parameter{toWorld}{\Transform\Or\Animation}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional camera-to-world transformation.
* \default{none (i.e. camera space $=$ world space)}
* }
@@ -137,7 +137,7 @@ public:
m_apertureRadius = Epsilon;
}
- if (props.getTransform("toWorld", Transform()).hasScale())
+ if (props.getAnimatedTransform("toWorld", Transform())->eval(0).hasScale())
Log(EError, "Scale factors in the camera-to-world "
"transformation are not allowed!");
}
@@ -520,14 +520,11 @@ public:
}
ref createShape(const Scene *scene) {
- if (!m_worldTransform->isStatic())
- Log(EError, "Bidirectional renderings involving moving "
- "perspective cameras with depth of field are currently not supported!");
- Transform trafo = m_worldTransform->eval(0) *
- Transform::scale(Vector(m_apertureRadius));
+ ref trafo = new AnimatedTransform(m_worldTransform);
+ trafo->prependScale(Vector(m_apertureRadius));
Properties props("disk");
- props.setTransform("toWorld", trafo);
+ props.setAnimatedTransform("toWorld", trafo);
Shape *shape = static_cast (PluginManager::getInstance()->
createObject(MTS_CLASS(Shape), props));
shape->addChild(this);
diff --git a/src/shapes/SConscript b/src/shapes/SConscript
index 6abc53ce..0d8a266d 100644
--- a/src/shapes/SConscript
+++ b/src/shapes/SConscript
@@ -11,6 +11,6 @@ plugins += env.SharedLibrary('cylinder', ['cylinder.cpp'])
plugins += env.SharedLibrary('hair', ['hair.cpp'])
plugins += env.SharedLibrary('shapegroup', ['shapegroup.cpp'])
plugins += env.SharedLibrary('instance', ['instance.cpp'])
-plugins += env.SharedLibrary('animatedinstance', ['animatedinstance.cpp'])
+#plugins += env.SharedLibrary('deformable', ['deformable.cpp'])
Export('plugins')
diff --git a/src/shapes/animatedinstance.cpp b/src/shapes/animatedinstance.cpp
deleted file mode 100644
index aa288645..00000000
--- a/src/shapes/animatedinstance.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- This file is part of Mitsuba, a physically based rendering system.
-
- Copyright (c) 2007-2012 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 "shapegroup.h"
-
-MTS_NAMESPACE_BEGIN
-
-/*!\plugin{animatedinstance}{Animated geometry instance}
- * \order{10}
- * \parameters{
- * \parameter{filename}{\String}{Filename of an animated
- * transformation}
- * \parameter{\Unnamed}{\ShapeGroup}{A reference to a
- * shape group that should be instantiated}
- * }
- *
- * This plugin implements an \emph{animated} geometry instance,
- * i.e. one or more shapes that are undergoing \emph{ridgid}
- * transformations over time.
- *
- * The input file should contain a binary / serialized
- * \code{AnimatedTransform} data structure -- for details,
- * please refer to the C++ implementation of this class.
- */
-class AnimatedInstance : public Shape {
-public:
- AnimatedInstance(const Properties &props) : Shape(props) {
- FileResolver *fResolver = Thread::getThread()->getFileResolver();
- fs::path path = fResolver->resolve(props.getString("filename"));
- m_name = path.filename().string();
-
- Log(EInfo, "Loading animation track from \"%s\"", m_name.c_str());
- ref fs = new FileStream(path, FileStream::EReadOnly);
- m_transform = new AnimatedTransform(fs);
- }
-
- AnimatedInstance(Stream *stream, InstanceManager *manager)
- : Shape(stream, manager) {
- m_shapeGroup = static_cast(manager->getInstance(stream));
- m_transform = new AnimatedTransform(stream);
- configure();
- }
-
- void serialize(Stream *stream, InstanceManager *manager) const {
- Shape::serialize(stream, manager);
- manager->serialize(stream, m_shapeGroup.get());
- m_transform->serialize(stream);
- }
-
- void configure() {
- if (!m_shapeGroup)
- Log(EError, "A reference to a 'shapegroup' must be specified!");
- const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
- m_aabb = m_transform->getSpatialBounds(kdtree->getAABB());
- }
-
- AABB getAABB() const {
- return m_aabb;
- }
-
- std::string getName() const {
- return m_name;
- }
-
- Float getSurfaceArea() const {
- Log(EError, "AnimatedInstance::getSurfaceArea(): not supported!");
- return 0.0f;
- }
-
- void addChild(const std::string &name, ConfigurableObject *child) {
- const Class *cClass = child->getClass();
- if (cClass->getName() == "ShapeGroup") {
- m_shapeGroup = static_cast(child);
- } else {
- Shape::addChild(name, child);
- }
- }
-
- bool rayIntersect(const Ray &_ray, Float mint,
- Float maxt, Float &t, void *temp) const {
- const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
- Ray ray;
- const Transform &objectToWorld = m_transform->eval(_ray.time);
- Transform worldToObject = objectToWorld.inverse();
- worldToObject.transformAffine(_ray, ray);
- return kdtree->rayIntersect(ray, mint, maxt, t, temp);
- }
-
- bool rayIntersect(const Ray &_ray, Float mint, Float maxt) const {
- const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
- Ray ray;
- const Transform &objectToWorld = m_transform->eval(_ray.time);
- Transform worldToObject = objectToWorld.inverse();
- worldToObject.transformAffine(_ray, ray);
- return kdtree->rayIntersect(ray, mint, maxt);
- }
-
- void fillIntersectionRecord(const Ray &_ray,
- const void *temp, Intersection &its) const {
- const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
- const Transform &objectToWorld = m_transform->eval(_ray.time);
-
- Ray ray;
- objectToWorld.inverse()(_ray, ray);
- kdtree->fillIntersectionRecord(ray, temp, its);
-
- its.shFrame.n = normalize(objectToWorld(its.shFrame.n));
- its.shFrame.s = normalize(objectToWorld(its.shFrame.s));
- its.shFrame.t = normalize(objectToWorld(its.shFrame.t));
- its.geoFrame = Frame(normalize(objectToWorld(its.geoFrame.n)));
- its.dpdu = objectToWorld(its.dpdu);
- its.dpdv = objectToWorld(its.dpdv);
- its.wi = normalize(its.shFrame.toLocal(-_ray.d));
- its.instance = this;
- }
-
- void getNormalDerivative(const Intersection &its,
- Vector &dndu, Vector &dndv, bool shadingFrame) const {
- const Transform &objectToWorld = m_transform->eval(its.time);
- its.shape->getNormalDerivative(its, dndu, dndv, shadingFrame);
-
- /* The following will probably be incorrect for
- non-rigid transformations */
- dndu = objectToWorld(dndu);
- dndv = objectToWorld(dndv);
- }
-
- size_t getPrimitiveCount() const {
- return 0;
- }
-
- size_t getEffectivePrimitiveCount() const {
- return m_shapeGroup->getPrimitiveCount();
- }
-
- MTS_DECLARE_CLASS()
-private:
- ref m_shapeGroup;
- ref m_transform;
- AABB m_aabb;
- std::string m_name;
-};
-
-MTS_IMPLEMENT_CLASS_S(AnimatedInstance, false, Shape)
-MTS_EXPORT_PLUGIN(AnimatedInstance, "Animated instanced geometry");
-MTS_NAMESPACE_END
diff --git a/src/shapes/cylinder.cpp b/src/shapes/cylinder.cpp
index 27139b0a..1d4fa95a 100644
--- a/src/shapes/cylinder.cpp
+++ b/src/shapes/cylinder.cpp
@@ -43,7 +43,7 @@ MTS_NAMESPACE_BEGIN
* Is the cylinder inverted, i.e. should the normal vectors
* be flipped? \default{\code{false}, i.e. the normals point outside}
* }
- * \parameter{toWorld}{\Transform}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional linear object-to-world transformation.
* Note that non-uniform scales are not permitted!
* \default{none (i.e. object space $=$ world space)}
diff --git a/src/shapes/deformable.cpp b/src/shapes/deformable.cpp
new file mode 100644
index 00000000..4f82ff41
--- /dev/null
+++ b/src/shapes/deformable.cpp
@@ -0,0 +1,467 @@
+/*
+ This file is part of Mitsuba, a physically based rendering system.
+
+ Copyright (c) 2007-2012 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
+#include
+
+#define SHAPE_PER_SEGMENT 1
+#define NO_CLIPPING_SUPPORT 1
+
+MTS_NAMESPACE_BEGIN
+
+class SpaceTimeKDTree : public SAHKDTree4D {
+ friend class GenericKDTree;
+ friend class SAHKDTree4D;
+public:
+ /// Temporarily holds some intersection information
+ struct IntersectionCache {
+ Point p[3];
+ Float u, v;
+ };
+
+ SpaceTimeKDTree(const std::vector &frameTimes, std::vector &positions,
+ Triangle *triangles, size_t vertexCount, size_t triangleCount)
+ : m_frameTimes(frameTimes), m_positions(positions), m_triangles(triangles),
+ m_vertexCount(vertexCount), m_triangleCount(triangleCount) {
+
+ Log(EInfo, "Total amount of vertex data: %s",
+ memString(vertexCount*frameTimes.size()*sizeof(float)*3).c_str());
+
+ //setClip(false);
+ //setExactPrimitiveThreshold(10);
+ buildInternal();
+
+ /* Collect some statistics */
+ std::stack stack;
+
+ stack.push(m_nodes);
+ size_t spatialSplits = 0, timeSplits = 0;
+ while (!stack.empty()) {
+ const KDNode *node = stack.top();
+ stack.pop();
+ if (!node->isLeaf()) {
+ if (node->getAxis() == 3) {
+ timeSplits++;
+ } else {
+ spatialSplits++;
+ }
+ stack.push((const KDNode *) node->getLeft());
+ stack.push((const KDNode *) node->getRight());
+ }
+ }
+
+ KDLog(EInfo, "Spacetime kd-tree statistics");
+ KDLog(EInfo, " Time interval = [%f, %f]" , m_tightAABB.min.w, m_tightAABB.max.w);
+ KDLog(EInfo, " Spatial splits = " SIZE_T_FMT, spatialSplits);
+ KDLog(EInfo, " Time splits = " SIZE_T_FMT, timeSplits);
+ KDLog(EInfo, "");
+
+ m_spatialAABB = AABB(
+ Point(m_aabb.min.x, m_aabb.min.y, m_aabb.min.z),
+ Point(m_aabb.max.x, m_aabb.max.y, m_aabb.max.z)
+ );
+ }
+
+ /// Return one of the points stored in the point cache
+ inline Point getPoint(uint32_t frame, uint32_t index) const {
+ float *ptr = m_positions[frame] + index*3;
+#if defined(__LITTLE_ENDIAN__)
+ return Point(
+ (Float) endianness_swap(ptr[0]),
+ (Float) endianness_swap(ptr[1]),
+ (Float) endianness_swap(ptr[2]));
+#else
+ return Point((Float) ptr[0], (Float) ptr[1], (Float) ptr[2]);
+#endif
+ }
+
+ // ========================================================================
+ // Implementation of functions required by the parent class
+ // ========================================================================
+
+ /// Return the total number of primitives that are organized in the tree
+ inline SizeType getPrimitiveCount() const {
+#ifdef SHAPE_PER_SEGMENT
+ return m_triangleCount * (m_frameTimes.size() - 1);
+#else
+ return m_triangleCount;
+#endif
+ }
+
+ /// Return the 4D extents for one of the primitives contained in the tree
+ AABB4 getAABB(IndexType index) const {
+#ifdef SHAPE_PER_SEGMENT
+ int frameIdx = index / m_triangleCount;
+ int triangleIdx = index % m_triangleCount;
+ const Triangle &tri = m_triangles[triangleIdx];
+
+ AABB aabb;
+ for (int i=0; i<3; ++i) {
+ aabb.expandBy(getPoint(frameIdx, tri.idx[i]));
+ aabb.expandBy(getPoint(frameIdx+1, tri.idx[i]));
+ }
+
+ return AABB4(
+ Point4(aabb.min.x, aabb.min.y, aabb.min.z, m_frameTimes[frameIdx]),
+ Point4(aabb.max.x, aabb.max.y, aabb.max.z, m_frameTimes[frameIdx+1])
+ );
+#else
+ AABB aabb;
+ const Triangle &tri = m_triangles[index];
+ for (size_t i=0; i 1)
+ return false;
+
+ /* Compute interpolated positions */
+ Point p[3];
+ for (int i=0; i<3; ++i)
+ p[i] = (1 - alpha) * getPoint(frameIdx, tri.idx[i])
+ + alpha * getPoint(frameIdx+1, tri.idx[i]);
+
+ Float tempU, tempV, tempT;
+ if (!Triangle::rayIntersect(p[0], p[1], p[2], ray, tempU, tempV, tempT))
+ return false;
+ if (tempT < mint || tempT > maxt)
+ return false;
+
+ if (tmp != NULL) {
+ IntersectionCache *cache =
+ static_cast(tmp);
+ t = tempT;
+ memcpy(cache->p, p, sizeof(Point)*3);
+ cache->u = tempU;
+ cache->v = tempV;
+ }
+ return true;
+ }
+
+ /// Cast a shadow ray against a specific triangle
+ inline bool intersect(const Ray &ray, IndexType idx,
+ Float mint, Float maxt) const {
+ Float tempT;
+ /* No optimized version for shadow rays yet */
+ return intersect(ray, idx, mint, maxt, tempT, NULL);
+ }
+
+ // ========================================================================
+ // Miscellaneous
+ // ========================================================================
+
+ /// Intersect a ray with all primitives stored in the kd-tree
+ inline bool rayIntersect(const Ray &ray, Float _mint, Float _maxt,
+ Float &t, void *temp) const {
+ Float tempT = std::numeric_limits::infinity();
+ Float mint, maxt;
+
+ if (m_spatialAABB.rayIntersect(ray, mint, maxt)) {
+ if (_mint > mint) mint = _mint;
+ if (_maxt < maxt) maxt = _maxt;
+
+ if (EXPECT_TAKEN(maxt > mint && ray.time >= m_aabb.min.w && ray.time <= m_aabb.max.w)) {
+ if (rayIntersectHavran(ray, mint, maxt, tempT, temp)) {
+ t = tempT;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * \brief Intersect a ray with all primitives stored in the kd-tree
+ * (Visiblity query version)
+ */
+ inline bool rayIntersect(const Ray &ray, Float _mint, Float _maxt) const {
+ Float tempT = std::numeric_limits::infinity();
+ Float mint, maxt;
+
+ if (m_spatialAABB.rayIntersect(ray, mint, maxt)) {
+ if (_mint > mint) mint = _mint;
+ if (_maxt < maxt) maxt = _maxt;
+
+ if (EXPECT_TAKEN(maxt > mint && ray.time >= m_aabb.min.w && ray.time <= m_aabb.max.w))
+ if (rayIntersectHavran(ray, mint, maxt, tempT, NULL))
+ return true;
+ }
+ return false;
+ }
+
+ inline const Triangle *getTriangles() const {
+ return m_triangles;
+ }
+
+ /// Return an AABB with the spatial extents
+ inline const AABB &getSpatialAABB() const {
+ return m_spatialAABB;
+ }
+
+ MTS_DECLARE_CLASS()
+protected:
+ std::vector m_frameTimes;
+ std::vector m_positions;
+ Triangle *m_triangles;
+ size_t m_vertexCount;
+ size_t m_triangleCount;
+ AABB m_spatialAABB;
+};
+
+class Deformable : public Shape {
+public:
+ Deformable(const Properties &props) : Shape(props) {
+ FileResolver *fResolver = Thread::getThread()->getFileResolver();
+ fs::path path = fResolver->resolve(props.getString("filename"));
+ if (path.extension() != ".mdd")
+ Log(EError, "Point cache files must have the extension \".mdd\"");
+
+ m_mmap = new MemoryMappedFile(path);
+
+ ref mStream = new MemoryStream((uint8_t *) m_mmap->getData(),
+ m_mmap->getSize());
+ mStream->setByteOrder(Stream::EBigEndian);
+
+ uint32_t frameCount = mStream->readUInt();
+ m_vertexCount = mStream->readUInt();
+
+ Log(EInfo, "Point cache has %i frames and %i vertices", frameCount, m_vertexCount);
+
+ Float clipStart = props.getFloat("clipStart", 0),
+ clipEnd = props.getFloat("clipEnd", 0);
+
+ std::vector frameTimes;
+ std::vector positions;
+
+ for (uint32_t i=0; ireadSingle());
+
+ for (uint32_t i=0; i(mStream->getCurrentData()));
+ mStream->skip(m_vertexCount * 3 * sizeof(float));
+ }
+
+ if (clipStart != clipEnd) {
+ m_positions.reserve(positions.size());
+ m_frameTimes.reserve(frameTimes.size());
+ for (uint32_t i=0; i= clipStart && frameTimes[i] <= clipEnd) {
+ m_frameTimes.push_back(frameTimes[i]);
+ m_positions.push_back(positions[i]);
+ }
+ }
+ if (m_frameTimes.empty())
+ Log(EError, "After clipping to the time range [%f, %f] no frames were left!",
+ clipStart, clipEnd);
+ Log(EInfo, "Clipped away %u/%u frames", frameCount - (uint32_t) m_frameTimes.size(), frameCount);
+ } else {
+ m_positions = positions;
+ m_frameTimes = frameTimes;
+ }
+ }
+
+ Deformable(Stream *stream, InstanceManager *manager)
+ : Shape(stream, manager) {
+ /// TBD
+ }
+
+ void serialize(Stream *stream, InstanceManager *manager) const {
+ Shape::serialize(stream, manager);
+ /// TBD
+ }
+
+ void configure() {
+ Shape::configure();
+
+ if (m_mesh == NULL)
+ Log(EError, "A nested triangle mesh is required so that "
+ "connectivity information can be extracted!");
+ if (m_mesh->getVertexCount() != m_vertexCount)
+ Log(EError, "Point cache and nested geometry have mismatched "
+ "numbers of vertices!");
+
+ m_kdtree = new SpaceTimeKDTree(m_frameTimes, m_positions, m_mesh->getTriangles(),
+ m_vertexCount, m_mesh->getTriangleCount());
+ m_aabb = m_kdtree->getSpatialAABB();
+ }
+
+ bool rayIntersect(const Ray &ray, Float mint,
+ Float maxt, Float &t, void *temp) const {
+ return m_kdtree->rayIntersect(ray, mint, maxt, t, temp);
+ }
+
+ bool rayIntersect(const Ray &ray, Float mint, Float maxt) const {
+ return m_kdtree->rayIntersect(ray, mint, maxt);
+ }
+
+ void fillIntersectionRecord(const Ray &ray,
+ const void *temp, Intersection &its) const {
+ const SpaceTimeKDTree::IntersectionCache *cache
+ = reinterpret_cast(temp);
+
+ const Vector b(1 - cache->u - cache->v, cache->u, cache->v);
+ const Point p0 = cache->p[0];
+ const Point p1 = cache->p[1];
+ const Point p2 = cache->p[2];
+
+ Normal faceNormal(cross(p1-p0, p2-p0));
+ Float length = faceNormal.length();
+ if (!faceNormal.isZero())
+ faceNormal /= length;
+
+ /* Just the basic attributes for now and geometric normals */
+ its.p = ray(its.t);
+ its.geoFrame = Frame(faceNormal);
+ its.shFrame = its.geoFrame;
+ its.wi = its.toLocal(-ray.d);
+ its.shape = this;
+ its.instance = this;
+ its.hasUVPartials = false;
+ its.time = ray.time;
+ }
+
+ AABB getAABB() const {
+ return m_kdtree->getSpatialAABB();
+ }
+
+ size_t getPrimitiveCount() const {
+ return m_mesh->getTriangleCount();
+ }
+
+ size_t getEffectivePrimitiveCount() const {
+ return m_mesh->getTriangleCount();
+ }
+
+ void addChild(const std::string &name, ConfigurableObject *child) {
+ const Class *cClass = child->getClass();
+ if (cClass->derivesFrom(TriMesh::m_theClass)) {
+ Assert(m_mesh == NULL);
+ m_mesh = static_cast(child);
+ if (m_mesh->getVertexCount() != m_vertexCount)
+ Log(EError, "Geometry mismatch! MDD file contains %u vertices. "
+ "The attached shape uses %u!", m_vertexCount, m_mesh->getVertexCount());
+ } else if (cClass->derivesFrom(Shape::m_theClass) && static_cast(child)->isCompound()) {
+ size_t index = 0;
+ Shape *shape = static_cast(child);
+ do {
+ ref element = shape->getElement(index++);
+ if (element == NULL)
+ break;
+ addChild(name, element);
+ } while (true);
+ } else {
+ Shape::addChild(name, child);
+ }
+ }
+
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << "Deformable[" << endl
+ << " mesh = " << indent(m_mesh.toString()) << endl
+ << "]";
+ return oss.str();
+ }
+
+ MTS_DECLARE_CLASS()
+private:
+ ref m_mmap;
+ ref m_kdtree;
+ std::vector m_frameTimes;
+ std::vector m_positions;
+ ref m_mesh;
+ uint32_t m_vertexCount;
+ AABB m_aabb;
+};
+
+MTS_IMPLEMENT_CLASS(SpaceTimeKDTree, false, KDTreeBase)
+MTS_IMPLEMENT_CLASS_S(Deformable, false, Shape)
+MTS_EXPORT_PLUGIN(Deformable, "Deformable shape");
+MTS_NAMESPACE_END
diff --git a/src/shapes/disk.cpp b/src/shapes/disk.cpp
index fac35568..a7445f95 100644
--- a/src/shapes/disk.cpp
+++ b/src/shapes/disk.cpp
@@ -31,7 +31,7 @@ MTS_NAMESPACE_BEGIN
/*!\plugin{disk}{Disk intersection primitive}
* \order{4}
* \parameters{
- * \parameter{toWorld}{\Transform}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies a linear object-to-world transformation.
* Note that non-uniform scales are not permitted!
* \default{none (i.e. object space $=$ world space)}
@@ -81,31 +81,29 @@ MTS_NAMESPACE_BEGIN
class Disk : public Shape {
public:
Disk(const Properties &props) : Shape(props) {
- m_objectToWorld = props.getTransform("toWorld", Transform());
+ m_objectToWorld = new AnimatedTransform(props.getAnimatedTransform("toWorld", Transform()));
+
if (props.getBoolean("flipNormals", false))
- m_objectToWorld = m_objectToWorld * Transform::scale(Vector(1, 1, -1));
- m_worldToObject = m_objectToWorld.inverse();
+ m_objectToWorld->prependScale(Vector(1, 1, -1));
}
Disk(Stream *stream, InstanceManager *manager)
: Shape(stream, manager) {
- m_objectToWorld = Transform(stream);
- m_worldToObject = m_objectToWorld.inverse();
+ m_objectToWorld = new AnimatedTransform(stream);
configure();
}
void serialize(Stream *stream, InstanceManager *manager) const {
Shape::serialize(stream, manager);
- m_objectToWorld.serialize(stream);
+ m_objectToWorld->serialize(stream);
}
void configure() {
Shape::configure();
- m_normal = normalize(m_objectToWorld(Normal(0, 0, 1)));
-
- Vector dpdu = m_objectToWorld(Vector(1, 0, 0));
- Vector dpdv = m_objectToWorld(Vector(0, 1, 0));
+ const Transform &trafo = m_objectToWorld->eval(0);
+ Vector dpdu = trafo(Vector(1, 0, 0));
+ Vector dpdv = trafo(Vector(0, 1, 0));
if (std::abs(dot(dpdu, dpdv)) > 1e-3f)
Log(EError, "Error: 'toWorld' transformation contains shear!");
@@ -117,23 +115,30 @@ public:
}
AABB getAABB() const {
+ std::set times;
+ m_objectToWorld->collectKeyframes(times);
+
AABB aabb;
- aabb.expandBy(m_objectToWorld(Point( 1, 0, 0)));
- aabb.expandBy(m_objectToWorld(Point(-1, 0, 0)));
- aabb.expandBy(m_objectToWorld(Point( 0, 1, 0)));
- aabb.expandBy(m_objectToWorld(Point( 0, -1, 0)));
+ for (std::set::iterator it = times.begin(); it != times.end(); ++it) {
+ const Transform &trafo = m_objectToWorld->eval(*it);
+ aabb.expandBy(trafo(Point( 1, 0, 0)));
+ aabb.expandBy(trafo(Point(-1, 0, 0)));
+ aabb.expandBy(trafo(Point( 0, 1, 0)));
+ aabb.expandBy(trafo(Point( 0, -1, 0)));
+ }
return aabb;
}
Float getSurfaceArea() const {
- Vector dpdu = m_objectToWorld(Vector(1, 0, 0));
- Vector dpdv = m_objectToWorld(Vector(0, 1, 0));
+ const Transform &trafo = m_objectToWorld->eval(0);
+ Vector dpdu = trafo(Vector(1, 0, 0));
+ Vector dpdv = trafo(Vector(0, 1, 0));
return M_PI * dpdu.length() * dpdv.length();
}
inline bool rayIntersect(const Ray &_ray, Float mint, Float maxt, Float &t, void *temp) const {
Ray ray;
- m_worldToObject.transformAffine(_ray, ray);
+ m_objectToWorld->eval(ray.time).inverse().transformAffine(_ray, ray);
Float hit = -ray.o.z / ray.d.z;
if (!(hit >= mint && hit <= maxt))
@@ -173,18 +178,20 @@ public:
phi += 2*M_PI;
Float cosPhi = data[0] * invR, sinPhi = data[1] * invR;
+ const Transform &trafo = m_objectToWorld->eval(ray.time);
its.shape = this;
if (r != 0) {
- its.dpdu = m_objectToWorld(Vector(cosPhi, sinPhi, 0));
- its.dpdv = m_objectToWorld(Vector(-sinPhi, cosPhi, 0));
+ its.dpdu = trafo(Vector(cosPhi, sinPhi, 0));
+ its.dpdv = trafo(Vector(-sinPhi, cosPhi, 0));
} else {
- its.dpdu = m_objectToWorld(Vector(1, 0, 0));
- its.dpdv = m_objectToWorld(Vector(0, 1, 0));
+ its.dpdu = trafo(Vector(1, 0, 0));
+ its.dpdv = trafo(Vector(0, 1, 0));
}
its.shFrame = its.geoFrame = Frame(
- normalize(its.dpdu), normalize(its.dpdv), m_normal);
+ normalize(its.dpdu), normalize(its.dpdv),
+ normalize(trafo(Normal(0, 0, 1))));
its.uv = Point2(r, phi * INV_TWOPI);
its.p = ray(its.t);
its.wi = its.toLocal(-ray.d);
@@ -206,16 +213,19 @@ public:
Float dphi = (2 * M_PI) / (Float) (phiSteps-1);
- Point center = m_objectToWorld(Point(0.0f));
+ const Transform &trafo = m_objectToWorld->eval(0.0f);
+ Point center = trafo(Point(0.0f));
+ Normal normal = normalize(trafo(Normal(0, 0, 1)));
+
for (uint32_t i=0; ieval(pRec.time);
Point2 p = Warp::squareToUniformDiskConcentric(sample);
- pRec.p = m_objectToWorld(Point3(p.x, p.y, 0));
- pRec.n = m_normal;
+ pRec.p = trafo(Point3(p.x, p.y, 0));
+ pRec.n = trafo(normalize(Normal(0,0,1)));
pRec.pdf = m_invSurfaceArea;
pRec.measure = EArea;
}
@@ -261,7 +272,7 @@ public:
std::string toString() const {
std::ostringstream oss;
oss << "Disk[" << endl
- << " objectToWorld = " << indent(m_objectToWorld.toString()) << "," << endl
+ << " objectToWorld = " << indent(m_objectToWorld->toString()) << "," << endl
<< " bsdf = " << indent(m_bsdf.toString()) << "," << endl;
if (isMediumTransition()) {
oss << " interiorMedium = " << indent(m_interiorMedium.toString()) << "," << endl
@@ -276,10 +287,7 @@ public:
MTS_DECLARE_CLASS()
private:
- Transform m_objectToWorld;
- Transform m_worldToObject;
- Normal m_normal;
- Float m_surfaceArea;
+ ref m_objectToWorld;
Float m_invSurfaceArea;
};
diff --git a/src/shapes/instance.cpp b/src/shapes/instance.cpp
index 1b1d56cf..32f81525 100644
--- a/src/shapes/instance.cpp
+++ b/src/shapes/instance.cpp
@@ -25,7 +25,7 @@ MTS_NAMESPACE_BEGIN
* \parameters{
* \parameter{\Unnamed}{\ShapeGroup}{A reference to a
* shape group that should be instantiated}
- * \parameter{toWorld}{\Transform}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional linear instance-to-world transformation.
* \default{none (i.e. instance space $=$ world space)}
* }
@@ -37,21 +37,19 @@ MTS_NAMESPACE_BEGIN
*/
Instance::Instance(const Properties &props) : Shape(props) {
- m_objectToWorld = props.getTransform("toWorld", Transform());
- m_worldToObject = m_objectToWorld.inverse();
+ m_transform = props.getAnimatedTransform("toWorld", Transform());
}
Instance::Instance(Stream *stream, InstanceManager *manager)
: Shape(stream, manager) {
m_shapeGroup = static_cast(manager->getInstance(stream));
- m_objectToWorld = Transform(stream);
- m_worldToObject = m_objectToWorld.inverse();
+ m_transform = new AnimatedTransform(stream);
}
void Instance::serialize(Stream *stream, InstanceManager *manager) const {
Shape::serialize(stream, manager);
manager->serialize(stream, m_shapeGroup.get());
- m_objectToWorld.serialize(stream);
+ m_transform->serialize(stream);
}
void Instance::configure() {
@@ -64,15 +62,19 @@ AABB Instance::getAABB() const {
const AABB &aabb = kdtree->getAABB();
if (!aabb.isValid()) // the geometry group is empty
return aabb;
- AABB result;
- for (int i=0; i<8; ++i)
- result.expandBy(m_objectToWorld(aabb.getCorner(i)));
- return result;
-}
-Float Instance::getSurfaceArea() const {
- Log(EError, "Instance::getSurfaceArea(): not supported!");
- return 0.0f;
+ std::set times;
+ m_transform->collectKeyframes(times);
+
+ AABB result;
+ for (std::set::iterator it = times.begin(); it != times.end(); ++it) {
+ const Transform &trafo = m_transform->eval(*it);
+
+ for (int i=0; i<8; ++i)
+ result.expandBy(trafo(aabb.getCorner(i)));
+ }
+
+ return result;
}
void Instance::addChild(const std::string &name, ConfigurableObject *child) {
@@ -95,55 +97,61 @@ size_t Instance::getEffectivePrimitiveCount() const {
bool Instance::rayIntersect(const Ray &_ray, Float mint,
Float maxt, Float &t, void *temp) const {
const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
+ const Transform &trafo = m_transform->eval(_ray.time);
Ray ray;
- m_worldToObject(_ray, ray);
+ trafo.inverse()(_ray, ray);
return kdtree->rayIntersect(ray, mint, maxt, t, temp);
}
bool Instance::rayIntersect(const Ray &_ray, Float mint, Float maxt) const {
const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
Ray ray;
- m_worldToObject(_ray, ray);
+ const Transform &trafo = m_transform->eval(_ray.time);
+ trafo.inverse()(_ray, ray);
return kdtree->rayIntersect(ray, mint, maxt);
}
void Instance::fillIntersectionRecord(const Ray &_ray,
const void *temp, Intersection &its) const {
const ShapeKDTree *kdtree = m_shapeGroup->getKDTree();
+ const Transform &trafo = m_transform->eval(_ray.time);
Ray ray;
- m_worldToObject(_ray, ray);
+ trafo.inverse()(_ray, ray);
kdtree->fillIntersectionRecord(ray, temp, its);
- its.shFrame.n = normalize(m_objectToWorld(its.shFrame.n));
- its.shFrame.s = normalize(m_objectToWorld(its.shFrame.s));
- its.shFrame.t = normalize(m_objectToWorld(its.shFrame.t));
- its.geoFrame = Frame(normalize(m_objectToWorld(its.geoFrame.n)));
- its.dpdu = m_objectToWorld(its.dpdu);
- its.dpdv = m_objectToWorld(its.dpdv);
- its.p = m_objectToWorld(its.p);
+ its.shFrame.n = normalize(trafo(its.shFrame.n));
+ its.shFrame.s = normalize(trafo(its.shFrame.s));
+ its.shFrame.t = normalize(trafo(its.shFrame.t));
+ its.geoFrame = Frame(normalize(trafo(its.geoFrame.n)));
+ its.dpdu = trafo(its.dpdu);
+ its.dpdv = trafo(its.dpdv);
+ its.p = trafo(its.p);
its.wi = normalize(its.shFrame.toLocal(-_ray.d));
its.instance = this;
}
void Instance::getNormalDerivative(const Intersection &its,
Vector &dndu, Vector &dndv, bool shadingFrame) const {
+ const Transform &trafo = m_transform->eval(its.time);
+ const Transform invTrafo = trafo.inverse();
+
/* The following is really super-inefficient, but it's
needed to be able to deal with general transformations */
Intersection temp(its);
- temp.p = m_worldToObject(its.p);
- temp.dpdu = m_worldToObject(its.dpdu);
- temp.dpdv = m_worldToObject(its.dpdv);
+ temp.p = invTrafo(its.p);
+ temp.dpdu = invTrafo(its.dpdu);
+ temp.dpdv = invTrafo(its.dpdv);
/* Determine the length of the transformed normal
*before* it was re-normalized */
- Normal tn = m_objectToWorld(normalize(m_worldToObject(its.shFrame.n)));
- Float invLen = 1/tn.length();
+ Normal tn = trafo(normalize(invTrafo(its.shFrame.n)));
+ Float invLen = 1 / tn.length();
tn *= invLen;
its.shape->getNormalDerivative(temp, dndu, dndv, shadingFrame);
- dndu = m_objectToWorld(Normal(dndu)) * invLen;
- dndv = m_objectToWorld(Normal(dndv)) * invLen;
+ dndu = trafo(Normal(dndu)) * invLen;
+ dndv = trafo(Normal(dndv)) * invLen;
dndu -= tn * dot(tn, dndu);
dndv -= tn * dot(tn, dndv);
diff --git a/src/shapes/instance.h b/src/shapes/instance.h
index 3b013811..24f096ed 100644
--- a/src/shapes/instance.h
+++ b/src/shapes/instance.h
@@ -40,7 +40,7 @@ public:
void configure();
/// Return the object-to-world transformation used by this instance
- inline Transform getWorldTransform() const { return m_objectToWorld; }
+ inline const AnimatedTransform *getWorldTransform() const { return m_transform.get(); }
/// Add a child ConfigurableObject
void addChild(const std::string &name, ConfigurableObject *child);
@@ -57,8 +57,6 @@ public:
AABB getAABB() const;
- Float getSurfaceArea() const;
-
bool rayIntersect(const Ray &_ray, Float mint,
Float maxt, Float &t, void *temp) const;
@@ -80,7 +78,7 @@ public:
MTS_DECLARE_CLASS()
private:
ref m_shapeGroup;
- Transform m_objectToWorld, m_worldToObject;
+ ref m_transform;
};
MTS_NAMESPACE_END
diff --git a/src/shapes/obj.cpp b/src/shapes/obj.cpp
index 14834ed0..015384e4 100644
--- a/src/shapes/obj.cpp
+++ b/src/shapes/obj.cpp
@@ -55,7 +55,7 @@ MTS_NAMESPACE_BEGIN
* this convention. \default{\code{true}, i.e. flip them to get the
* correct coordinates}.
* }
- * \parameter{toWorld}{\Transform}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional linear object-to-world transformation.
* Note that non-uniform scales are not permitted!
* \default{none (i.e. object space $=$ world space)}
diff --git a/src/shapes/ply.cpp b/src/shapes/ply.cpp
index 4faa7f25..75fca394 100644
--- a/src/shapes/ply.cpp
+++ b/src/shapes/ply.cpp
@@ -62,7 +62,7 @@ MTS_NAMESPACE_BEGIN
* Optional flag to flip all normals. \default{\code{false}, i.e.
* the normals are left unchanged}.
* }
- * \parameter{toWorld}{\Transform}{
+ * \parameter{toWorld}{\Transform\Or\ATransform}{
* Specifies an optional linear object-to-world transformation.
* Note that non-uniform scales are not permitted!
* \default{none (i.e. object space $=$ world space)}
@@ -108,13 +108,14 @@ public:
Log(EError, "PLY file \"%s\" could not be found!", filePath.string().c_str());
m_triangleCount = m_vertexCount = 0;
- m_vertexCtr = m_triangleCtr = m_triangleIdxCtr = 0;
+ m_vertexCtr = m_faceCount = m_faceCtr = m_indexCtr = 0;
m_normal = Normal(0.0f);
m_uv = Point2(0.0f);
m_hasNormals = false;
m_hasTexCoords = false;
- memset(&m_triangle, 0, sizeof(Triangle));
+ memset(&m_face, 0, sizeof(uint32_t)*4);
loadPLY(filePath);
+
if (m_triangleCount == 0 || m_vertexCount == 0)
Log(EError, "Unable to load \"%s\" (no triangles or vertices found)!");
@@ -125,7 +126,15 @@ public:
rebuildTopology(props.getFloat("maxSmoothAngle"));
}
- Assert(m_triangleCtr == m_triangleCount);
+ if (m_triangleCount < m_faceCount * 2) {
+ /* Needed less memory than the earlier conservative estimate -- free it! */
+ Triangle *temp = new Triangle[m_triangleCount];
+ memcpy(temp, m_triangles, sizeof(Triangle) * m_triangleCount);
+ delete[] m_triangles;
+ m_triangles = temp;
+ }
+
+ Assert(m_faceCtr == m_faceCount);
Assert(m_vertexCtr == m_vertexCount);
}
@@ -171,8 +180,8 @@ public:
std::tr1::bind(&PLYLoader::vertex_end_callback, this)
);
} else if (element_name == "face") {
- m_triangleCount = count;
- m_triangles = new Triangle[m_triangleCount];
+ m_faceCount = count;
+ m_triangles = new Triangle[m_faceCount*2];
return std::tr1::tuple,
std::tr1::function >(
std::tr1::bind(&PLYLoader::face_begin_callback, this),
@@ -263,32 +272,42 @@ public:
void face_end_callback() { }
void face_vertex_indices_begin_uint8(ply::uint8 size) {
- if (size != 3)
- Log(EError, "Only triangle PLY meshes are supported for now.");
- m_triangleIdxCtr = 0;
+ if (size != 3 && size != 4)
+ Log(EError, "Only triangle and quad-based PLY meshes are supported for now.");
+ m_indexCtr = 0;
}
void face_vertex_indices_begin_uint32(ply::uint32 size) {
- if (size != 3)
- Log(EError, "Only triangle PLY meshes are supported for now.");
- m_triangleIdxCtr = 0;
+ if (size != 3 && size != 4)
+ Log(EError, "Only triangle and quad-based PLY meshes are supported for now.");
+ m_indexCtr = 0;
}
void face_vertex_indices_element_int32(ply::int32 element) {
- Assert(m_triangleIdxCtr < 3);
+ Assert(m_indexCtr < 4);
Assert((size_t) element < m_vertexCount);
- m_triangle.idx[m_triangleIdxCtr++] = element;
+ m_face[m_indexCtr++] = element;
}
void face_vertex_indices_element_uint32(ply::uint32 element) {
- Assert(m_triangleIdxCtr < 3);
+ Assert(m_indexCtr < 4);
Assert((size_t) element < m_vertexCount);
- m_triangle.idx[m_triangleIdxCtr++] = element;
+ m_face[m_indexCtr++] = element;
}
void face_vertex_indices_end() {
- Assert(m_triangleIdxCtr == 3);
- m_triangles[m_triangleCtr++] = m_triangle;
+ Assert(m_indexCtr == 3 || m_indexCtr == 4);
+
+ Triangle t;
+ t.idx[0] = m_face[0]; t.idx[1] = m_face[1]; t.idx[2] = m_face[2];
+ m_triangles[m_triangleCount++] = t;
+
+ if (m_indexCtr == 4) {
+ t.idx[0] = m_face[3]; t.idx[1] = m_face[0]; t.idx[2] = m_face[2];
+ m_triangles[m_triangleCount++] = t;
+ }
+
+ m_faceCtr++;
}
MTS_DECLARE_CLASS()
@@ -297,8 +316,9 @@ private:
Normal m_normal;
Float m_red, m_green, m_blue;
Transform m_objectToWorld;
- size_t m_vertexCtr, m_triangleCtr, m_triangleIdxCtr;
- Triangle m_triangle;
+ size_t m_faceCount, m_vertexCtr;
+ size_t m_faceCtr, m_indexCtr;
+ uint32_t m_face[4];
bool m_hasNormals, m_hasTexCoords;
Point2 m_uv;
bool m_sRGB;
@@ -380,7 +400,7 @@ template<> std::tr1::tuple,
std::tr1::function, std::tr1::function >
PLYLoader::list_property_definition_callback(const std::string& element_name,
const std::string& property_name) {
- if ((element_name == "face") && (property_name == "vertex_indices")) {
+ if ((element_name == "face") && (property_name == "vertex_indices" || property_name == "vertex_index")) {
return std::tr1::tuple,
std::tr1::function, std::tr1::function >(
std::tr1::bind(&PLYLoader::face_vertex_indices_begin_uint8, this, _1),
@@ -404,7 +424,7 @@ template<> std::tr1::tuple,
std::tr1::function, std::tr1::function >
PLYLoader::list_property_definition_callback(const std::string& element_name,
const std::string& property_name) {
- if ((element_name == "face") && (property_name == "vertex_indices")) {
+ if ((element_name == "face") && (property_name == "vertex_indices" || property_name == "vertex_index")) {
return std::tr1::tuple,
std::tr1::function, std::tr1::function >(
std::tr1::bind(&PLYLoader::face_vertex_indices_begin_uint32, this, _1),
@@ -428,7 +448,7 @@ template<> std::tr1::tuple,
std::tr1::function, std::tr1::function >
PLYLoader::list_property_definition_callback(const std::string& element_name,
const std::string& property_name) {
- if ((element_name == "face") && (property_name == "vertex_indices")) {
+ if ((element_name == "face") && (property_name == "vertex_indices" || property_name == "vertex_index")) {
return std::tr1::tuple