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, std::tr1::function, std::tr1::function >( std::tr1::bind(&PLYLoader::face_vertex_indices_begin_uint8, this, _1), @@ -452,7 +472,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), diff --git a/src/shapes/rectangle.cpp b/src/shapes/rectangle.cpp index 553bbf17..e95a5e4f 100644 --- a/src/shapes/rectangle.cpp +++ b/src/shapes/rectangle.cpp @@ -30,7 +30,7 @@ MTS_NAMESPACE_BEGIN /*!\plugin{rectangle}{Rectangle intersection primitive} * \order{3} * \parameters{ - * \parameter{toWorld}{\Transform}{ + * \parameter{toWorld}{\Transform\Or\ATransform}{ * Specifies a linear object-to-world transformation. * It is allowed to use non-uniform scaling, but no shear. * \default{none (i.e. object space $=$ world space)} diff --git a/src/shapes/serialized.cpp b/src/shapes/serialized.cpp index 4c44a254..bdfced9e 100644 --- a/src/shapes/serialized.cpp +++ b/src/shapes/serialized.cpp @@ -55,7 +55,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)} diff --git a/src/shapes/shapegroup.cpp b/src/shapes/shapegroup.cpp index db42100e..c9e60c73 100644 --- a/src/shapes/shapegroup.cpp +++ b/src/shapes/shapegroup.cpp @@ -120,8 +120,8 @@ Float ShapeGroup::getSurfaceArea() const { void ShapeGroup::addChild(const std::string &name, ConfigurableObject *child) { const Class *cClass = child->getClass(); - if (cClass->derivesFrom(MTS_CLASS(ShapeGroup)) || cClass->getName() == "ShapeInstance") { - Log(EError, "Nested instancing is not supported!"); + if (cClass->derivesFrom(MTS_CLASS(ShapeGroup)) || cClass->getName() == "Instance") { + Log(EError, "Nested instancing is not permitted"); } else if (cClass->derivesFrom(MTS_CLASS(Shape))) { Shape *shape = static_cast(child); if (shape->isEmitter()) diff --git a/src/shapes/sphere.cpp b/src/shapes/sphere.cpp index 522743e2..6c8c0ad4 100644 --- a/src/shapes/sphere.cpp +++ b/src/shapes/sphere.cpp @@ -37,7 +37,7 @@ MTS_NAMESPACE_BEGIN * \parameter{radius}{\Float}{ * Radius of the sphere in object-space units \default{1} * } - * \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/tests/SConscript b/src/tests/SConscript index fc42deae..0dc60491 100644 --- a/src/tests/SConscript +++ b/src/tests/SConscript @@ -9,7 +9,7 @@ bidirEnv.Append(LIBPATH=['#src/libbidir']) for plugin in glob.glob(GetBuildPath('test_*.cpp')): name = os.path.basename(plugin) - if "bidir" in name: + if "bidir" in name: lib = bidirEnv.SharedLibrary(name[0:len(name)-4], name) else: lib = testEnv.SharedLibrary(name[0:len(name)-4], name)