diff --git a/include/mitsuba/core/vmf.h b/include/mitsuba/core/vmf.h index 96576895..b4c46263 100644 --- a/include/mitsuba/core/vmf.h +++ b/include/mitsuba/core/vmf.h @@ -28,7 +28,9 @@ MTS_NAMESPACE_BEGIN * \brief Von Mises-Fisher distribution on the 2-sphere * * This is a basic implementation, which assumes that the - * distribution is centered around the Z-axis. + * distribution is centered around the Z-axis. All provided + * functions are implemented in such a way that they avoid + * issues with numerical overflow. * * \author Wenzel Jakob * \ingroup libcore @@ -39,13 +41,21 @@ public: * \brief Create a new von Mises-Fisher distribution * with the given concentration parameter */ - VonMisesFisherDistr(Float kappa = 0); + explicit inline VonMisesFisherDistr(Float kappa = 0) : m_kappa(kappa) { } + + /// Return the concentration parameter kappa + inline void setKappa(Float kappa) { + m_kappa = kappa; + } /// Return the concentration parameter kappa inline Float getKappa() const { return m_kappa; } + /// Return the mean cosine of the distribution + Float getMeanCosine() const; + /// Evaluate the distribution for a given value of cos(theta) Float eval(Float cosTheta) const; @@ -57,12 +67,21 @@ public: */ Vector sample(const Point2 &sample) const; + /// Return a string representation + std::string toString() const; + /** * \brief Compute an appropriate concentration parameter so that * the associated vMF distribution takes on the value \c x at its peak */ static Float forPeakValue(Float x); + /** + * \brief Compute an appropriate concentration parameter so that + * the associated vMF distribution has the mean cosine \c g. + */ + static Float forMeanCosine(Float g); + /** * \brief Compute an concentration parameter that approximately * corresponds to the spherical convolution of two vMF distributions. diff --git a/src/libcore/vmf.cpp b/src/libcore/vmf.cpp index 7380b1d4..59f2dcf7 100644 --- a/src/libcore/vmf.cpp +++ b/src/libcore/vmf.cpp @@ -18,11 +18,11 @@ #include #include +#include +#include MTS_NAMESPACE_BEGIN -VonMisesFisherDistr::VonMisesFisherDistr(Float kappa): m_kappa(kappa) { } - Float VonMisesFisherDistr::eval(Float cosTheta) const { if (m_kappa == 0.0f) return INV_FOURPI; @@ -63,22 +63,23 @@ Vector VonMisesFisherDistr::sample(const Point2 &sample) const { sinPhi * sinTheta, cosTheta); } -Float VonMisesFisherDistr::forPeakValue(Float x) { - if (x < INV_FOURPI) { - return 0.0f; - } else if (x > 0.795) { - return 2 * M_PI * x; - } else { - return std::max((Float) 0.0f, - (168.479f * x * x + 16.4585f * x - 2.39942f) / - (-1.12718f * x * x + 29.1433f * x + 1.0f)); - } +Float VonMisesFisherDistr::getMeanCosine() const { + if (m_kappa == 0) + return 0; + Float coth = m_kappa > 6 ? 1 : ((std::exp(2*m_kappa)+1)/(std::exp(2*m_kappa)-1)); + return coth-1/m_kappa; } static Float A3(Float kappa) { return 1/ std::tanh(kappa) - 1 / kappa; } +std::string VonMisesFisherDistr::toString() const { + std::ostringstream oss; + oss << "VonMisesFisherDistr[kappa=" << m_kappa << "]"; + return oss.str(); +} + static Float dA3(Float kappa) { Float csch = 2.0f / (math::fastexp(kappa)-math::fastexp(-kappa)); @@ -110,4 +111,33 @@ Float VonMisesFisherDistr::convolve(Float kappa1, Float kappa2) { return A3inv(A3(kappa1) * A3(kappa2), std::min(kappa1, kappa2)); } +Float VonMisesFisherDistr::forPeakValue(Float x) { + if (x < INV_FOURPI) { + return 0.0f; + } else if (x > 0.795) { + return 2 * M_PI * x; + } else { + return std::max((Float) 0.0f, + (168.479f * x * x + 16.4585f * x - 2.39942f) / + (-1.12718f * x * x + 29.1433f * x + 1.0f)); + } +} + +static Float meanCosineFunctor(Float kappa, Float g) { + return VonMisesFisherDistr(kappa).getMeanCosine()-g; +} + +Float VonMisesFisherDistr::forMeanCosine(Float g) { + if (g == 0) + return 0; + else if (g < 0) + SLog(EError, "Error: vMF distribution cannot be created for g<0."); + + BrentSolver brentSolver(100, 1e-6f); + BrentSolver::Result result = brentSolver.solve( + boost::bind(&meanCosineFunctor, _1, g), 0, 1000); + SAssert(result.success); + return result.x; +} + MTS_NAMESPACE_END diff --git a/src/libpython/core.cpp b/src/libpython/core.cpp index 12ae33fc..eadb96bf 100644 --- a/src/libpython/core.cpp +++ b/src/libpython/core.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -2251,6 +2252,20 @@ void export_core() { .def("getMillisecondsSinceStart", &Timer::getMillisecondsSinceStart) .def("getSecondsSinceStart", &Timer::getSecondsSinceStart); + BP_STRUCT(VonMisesFisherDistr, bp::init()) + .def("getKappa", &VonMisesFisherDistr::getKappa) + .def("setKappa", &VonMisesFisherDistr::setKappa) + .def("eval", &VonMisesFisherDistr::eval) + .def("getMeanCosine", &VonMisesFisherDistr::getMeanCosine) + .def("sample", &VonMisesFisherDistr::sample, BP_RETURN_VALUE) + .def("forMeanCosine", &VonMisesFisherDistr::forMeanCosine) + .def("forPeakValue", &VonMisesFisherDistr::forPeakValue) + .def("convolve", &VonMisesFisherDistr::convolve) + .def("__repr__", &VonMisesFisherDistr::toString) + .staticmethod("forMeanCosine") + .staticmethod("forPeakValue") + .staticmethod("convolve"); + bp::detail::current_scope = oldScope; }