From e997344b8c03986e383c819b67444b624dcd5362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 31 Aug 2018 20:45:39 +0200 Subject: [PATCH] Math: new CubicHermite class. For spline interpolation. --- doc/changelog.dox | 1 + doc/types.dox | 4 + src/Magnum/Magnum.h | 30 + src/Magnum/Math/CMakeLists.txt | 1 + src/Magnum/Math/Complex.h | 5 +- src/Magnum/Math/CubicHermite.h | 422 ++++++++++ src/Magnum/Math/Functions.h | 8 +- src/Magnum/Math/Math.h | 23 +- src/Magnum/Math/Quaternion.h | 5 +- src/Magnum/Math/Test/CMakeLists.txt | 2 + src/Magnum/Math/Test/CubicHermiteTest.cpp | 915 ++++++++++++++++++++++ src/Magnum/Math/instantiation.cpp | 12 + 12 files changed, 1416 insertions(+), 12 deletions(-) create mode 100644 src/Magnum/Math/CubicHermite.h create mode 100644 src/Magnum/Math/Test/CubicHermiteTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index f2ffda900..1c22b3fce 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -47,6 +47,7 @@ See also: @subsubsection changelog-latest-new-math Math library +- New @ref Math::CubicHermite class for cubic Hermite spline interpolation - Added @ref Math::Intersection::rangeFrustum(), @ref Math::Intersection::aabbFrustum(), @ref Math::Intersection::sphereFrustum(), diff --git a/doc/types.dox b/doc/types.dox index 317d5aad5..164ba457a 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -170,10 +170,14 @@ Other types, which don't have their GLSL equivalent, are: or @ref QuadraticBezier3Dd - @ref CubicBezier2D or @ref CubicBezier2Dd, @ref CubicBezier3D or @ref CubicBezier3Dd +- @ref CubicHermite1D or @ref CubicHermite1Dd, @ref CubicHermite2D or + @ref CubicHermite2Dd, @ref CubicHermite3D or @ref CubicHermite3Dd - @ref Complex or @ref Complexd, @ref DualComplex or @ref DualComplexd - @ref Frustum or @ref Frustumd - @ref Quaternion or @ref Quaterniond, @ref DualQuaternion or @ref DualQuaterniond +- @ref CubicHermiteComplex or @ref CubicHermiteComplexd +- @ref CubicHermiteQuaternion or @ref CubicHermiteQuaterniond - @ref Range1D / @ref Range2D / @ref Range3D, @ref Range1Di / @ref Range2Di / @ref Range3Di or @ref Range1Dd / @ref Range2Dd / @ref Range3Dd diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 159fa87ae..ce2cd1e4b 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -456,6 +456,21 @@ typedef Math::CubicBezier2D CubicBezier2D; /** @brief Float three-dimensional cubic Bézier curve */ typedef Math::CubicBezier3D CubicBezier3D; +/** @brief Float scalar cubic Hermite spline point */ +typedef Math::CubicHermite1D CubicHermite1D; + +/** @brief Float two-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite2D CubicHermite2D; + +/** @brief Float three-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite3D CubicHermite3D; + +/** @brief Float cubic Hermite spline complex number */ +typedef Math::CubicHermiteComplex CubicHermiteComplex; + +/** @brief Float cubic Hermite spline quaternion */ +typedef Math::CubicHermiteQuaternion CubicHermiteQuaternion; + /** @brief Float complex number */ typedef Math::Complex Complex; @@ -641,6 +656,21 @@ typedef Math::CubicBezier2D CubicBezier2Dd; /** @brief Double three-dimensional cubic Bézier curve */ typedef Math::CubicBezier3D CubicBezier3Dd; +/** @brief Double scalar cubic Hermite spline point */ +typedef Math::CubicHermite1D CubicHermite1Dd; + +/** @brief Double two-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite2D CubicHermite2Dd; + +/** @brief Double three-dimensional cubic Hermite spline point */ +typedef Math::CubicHermite3D CubicHermite3Dd; + +/** @brief Double cubic Hermite spline complex number */ +typedef Math::CubicHermiteComplex CubicHermiteComplexd; + +/** @brief Double cubic Hermite spline quaternion */ +typedef Math::CubicHermiteQuaternion CubicHermiteQuaterniond; + /** @brief Double complex number */ typedef Math::Complex Complexd; diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index 744599e03..b5510e744 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -30,6 +30,7 @@ set(MagnumMath_HEADERS Color.h Complex.h Constants.h + CubicHermite.h Distance.h Dual.h DualComplex.h diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 9299826df..be0dc5d81 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -469,7 +469,10 @@ Expects that both complex numbers are normalized. @f[ @f] @see @ref Complex::isNormalized(), @ref slerp(const Complex&, const Complex&, T), @ref lerp(const Quaternion&, const Quaternion&, T), - @ref lerp(const T&, const T&, U) + @ref lerp(const T&, const T&, U), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template inline Complex lerp(const Complex& normalizedA, const Complex& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), diff --git a/src/Magnum/Math/CubicHermite.h b/src/Magnum/Math/CubicHermite.h new file mode 100644 index 000000000..e14d909e1 --- /dev/null +++ b/src/Magnum/Math/CubicHermite.h @@ -0,0 +1,422 @@ +#ifndef Magnum_Math_CubicHermiteSpline_h +#define Magnum_Math_CubicHermiteSpline_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Math::CubicHermite, alias @ref Magnum::Math::CubicHermite2D, @ref Magnum::Math::CubicHermite3D, function @ref Magnum::Math::select(), @ref Magnum::Math::lerp(), @ref Magnum::Math::splerp() + */ + +#include "Magnum/Math/Complex.h" +#include "Magnum/Math/Quaternion.h" + +namespace Magnum { namespace Math { + +/** +@brief Cubic Hermite spline point + +Represents a point on a [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline). + +Unlike @ref Bezier, which describes a curve segment, this structure describes +a spline point @f$ \boldsymbol{p} @f$, with in-tangent @f$ \boldsymbol{m} @f$ +and out-tangent @f$ \boldsymbol{n} @f$. This form is more suitable for +animation keyframe representation. The structure assumes the in/out tangents +to be in their final form, i.e. already normalized by length of their adjacent +segments. + +@see @ref CubicHermite2D, @ref CubicHermite3D, + @ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd, + @ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd, + @ref CubicBezier +@experimental +*/ +template class CubicHermite { + public: + typedef T Type; /**< @brief Underlying data type */ + + /** + * @brief Default constructor + * + * Equivalent to @ref CubicHermite(ZeroInitT) for vector types and to + * @ref CubicHermite(IdentityInitT) for complex and quaternion types. + */ + constexpr /*implicit*/ CubicHermite() noexcept: CubicHermite{typename std::conditional::value, IdentityInitT, ZeroInitT>::type{typename std::conditional::value, IdentityInitT, ZeroInitT>::type::Init{}}} {} + + /** + * @brief Default constructor + * + * Construct cubic Hermite spline point with all control points being + * zero. + */ + constexpr explicit CubicHermite(ZeroInitT) noexcept: CubicHermite{ZeroInit, typename std::conditional::value, ZeroInitT*, void*>::type{}} {} + + /** + * @brief Identity constructor + * + * The @ref point() is constructed as identity in order to have + * interpolation working correctly; @ref inTangent() and + * @ref outTangent() is constructed as zero. Enabled only for complex + * and quaternion types. + */ + template::value>::type> constexpr explicit CubicHermite(IdentityInitT) noexcept: _inTangent{ZeroInit}, _point{IdentityInit}, _outTangent{ZeroInit} {} + + /** @brief Construct cubic Hermite spline point without initializing its contents */ + explicit CubicHermite(NoInitT) noexcept: CubicHermite{NoInit, typename std::conditional::value, NoInitT*, void*>::type{}} {} + + /** + * @brief Construct cubic Hermite spline point with given control points + * @param inTangent In-tangent @f$ \boldsymbol{m} @f$ + * @param point Point @f$ \boldsymbol{p} @f$ + * @param outTangent Out-tangent @f$ \boldsymbol{n} @f$ + */ + constexpr /*implicit*/ CubicHermite(const T& inTangent, const T& point, const T& outTangent) noexcept: _inTangent{inTangent}, _point{point}, _outTangent{outTangent} {} + + /** + * @brief Construct subic Hermite spline point from another of different type + * + * Performs only default casting on the values, no rounding or + * anything else. + */ + template constexpr explicit CubicHermite(const CubicHermite& other) noexcept: _inTangent{T(other._inTangent)}, _point{T(other._point)}, _outTangent{T(other._outTangent)} {} + + /** @brief Equality comparison */ + bool operator==(const CubicHermite& other) const; + + /** @brief Non-equality comparison */ + bool operator!=(const CubicHermite& other) const { + return !operator==(other); + } + + /** @brief In-tangent @f$ \boldsymbol{m} @f$ */ + T& inTangent() { return _inTangent; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& inTangent() const { return _inTangent; } /**< @overload */ + + /** @brief Point @f$ \boldsymbol{p} @f$ */ + T& point() { return _point; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& point() const { return _point; } /**< @overload */ + + /** @brief Out-tangent @f$ \boldsymbol{n} @f$ */ + T& outTangent() { return _outTangent; } + /* returns const& so [] operations are also constexpr */ + constexpr const T& outTangent() const { return _outTangent; } /**< @overload */ + + private: + template friend class CubicHermite; + + /* Called from CubicHermite(ZeroInit), either using the ZeroInit + constructor (if available) or passing zero directly (for scalar + types) */ + constexpr explicit CubicHermite(ZeroInitT, ZeroInitT*) noexcept: _inTangent{ZeroInit}, _point{ZeroInit}, _outTangent{ZeroInit} {} + constexpr explicit CubicHermite(ZeroInitT, void*) noexcept: _inTangent{T(0)}, _point{T(0)}, _outTangent{T(0)} {} + + /* Called from CubicHermite(NoInit), either using the NoInit + constructor (if available) or not doing oanything */ + explicit CubicHermite(NoInitT, NoInitT*) noexcept: _inTangent{NoInit}, _point{NoInit}, _outTangent{NoInit} {} + explicit CubicHermite(NoInitT, void*) noexcept {} + + T _inTangent; + T _point; + T _outTangent; +}; + +/** +@brief Scalar cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite @ce. See @ref CubicHermite for +more information. +@see @ref CubicHermite2D, @ref CubicHermite3D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite1D, + @ref Magnum::CubicHermite1Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite1D = CubicHermite; +#endif + +/** +@brief Two-dimensional cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite3D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite2D, + @ref Magnum::CubicHermite2Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite2D = CubicHermite>; +#endif + +/** +@brief Three-dimensional cubic Hermite spline point + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermiteComplex, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermite3D, + @ref Magnum::CubicHermite3Dd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermite3D = CubicHermite>; +#endif + +/** +@brief Cubic Hermite spline complex number + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermite3D, + @ref CubicHermiteQuaternion, @ref Magnum::CubicHermiteComplex, + @ref Magnum::CubicHermiteComplexd +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermiteComplex = CubicHermite>; +#endif + +/** +@brief Cubic Hermite spline quaternion + +Convenience alternative to @cpp CubicHermite> @ce. See +@ref CubicHermite for more information. +@see @ref CubicHermite1D, @ref CubicHermite2D, @ref CubicHermite3D, + @ref CubicHermiteComplex, @ref Magnum::CubicHermiteQuaternion, + @ref Magnum::CubicHermiteQuaterniond +*/ +#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ +template using CubicHermiteQuaternion = CubicHermite>; +#endif + +/** @debugoperator{CubicHermite} */ +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const CubicHermite& value) { + return debug << "CubicHermite(" << Corrade::Utility::Debug::nospace + << value.inTangent() << Corrade::Utility::Debug::nospace << "," + << value.point() << Corrade::Utility::Debug::nospace << "," + << value.outTangent() << Corrade::Utility::Debug::nospace << ")"; +} + +/* Explicit instantiation for commonly used types */ +#ifndef DOXYGEN_GENERATING_OUTPUT +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +#endif + +/** @relatesalso CubicHermite +@brief Constant interpolation of two cubic Hermite spline points +@param a First value +@param b Second value +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \begin{cases} + \boldsymbol{p}_a, & t < 1 \\ + \boldsymbol{p}_b, & t \ge 1 + \end{cases} +@f] + +Equivalent to calling @ref select(const T&, const T&, U) on +@ref CubicHermite::point() extracted from both @p a and @p b. +@see @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermite&, const CubicHermite&, U) +*/ +template T select(const CubicHermite& a, const CubicHermite& b, U t) { + /* Not using select() from Functions.h to avoid the header dependency */ + return t < U(1) ? a.point() : b.point(); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite points +@param a First spline point +@param b Second spline point +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = (1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b +@f] + +Equivalent to calling @ref lerp(const T&, const T&, U) on +@ref CubicHermite::point() extracted from both @p a and @p b. +@see @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template T lerp(const CubicHermite& a, const CubicHermite& b, U t) { + /* To avoid dependency on Functions.h */ + return Implementation::lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite complex numbers + +Unlike @ref lerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \frac{(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b}{|(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b|} +@f] + +Equivalent to calling @ref lerp(const Complex&, const Complex&, T) on +@ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized complex number in both @p a and @p b. +@see @ref Complex::isNormalized(), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) +*/ +template Complex lerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { + return lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Linear interpolation of two cubic Hermite quaternions + +Unlike @ref lerp(const CubicHermite&, const CubicHermite&, U) this adds a +normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = \frac{(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b}{|(1 - t) \boldsymbol{p}_a + t \boldsymbol{p}_b|} +@f] + +Equivalent to calling @ref lerp(const Quaternion&, const Quaternion&, T) +on @ref CubicHermite::point() extracted from @p a and @p b. Expects that +@ref CubicHermite::point() is a normalized quaternion in both @p a and @p b. +@see @ref Quaternion::isNormalized(), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion lerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + return lerp(a.point(), b.point(), t); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite points +@param a First spline point +@param b Second spline point +@param t Interpolation phase + +Given segment points @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ +and out-tangents @f$ \boldsymbol{n}_i @f$, the interpolated value @f$ \boldsymbol{p} @f$ +at phase @f$ t @f$ is: @f[ + \boldsymbol{p}(t) = (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b +@f] + +@see @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermite&, const CubicHermite&, U), +*/ +template T splerp(const CubicHermite& a, const CubicHermite& b, U t) { + return (U(2)*t*t*t - U(3)*t*t + U(1))*a.point() + + (t*t*t - U(2)*t*t + t)*a.outTangent() + + (U(-2)*t*t*t + U(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent(); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite complex numbers + +Unlike @ref splerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \begin{array}{rcl} + \boldsymbol{p'}(t) & = & (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b \\ + \boldsymbol{p}(t) & = & \cfrac{\boldsymbol{p'}(t)}{|\boldsymbol{p'}(t)|} + \end{array} +@f] + +Expects that @ref CubicHermite::point() is a normalized complex number in both +@p a and @p b. +@see @ref Complex::isNormalized(), + @ref splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) +*/ +template Complex splerp(const CubicHermiteComplex& a, const CubicHermiteComplex& b, T t) { + CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), + "Math::splerp(): complex spline points must be normalized", {}); + return ((T(2)*t*t*t - T(3)*t*t + T(1))*a.point() + + (t*t*t - T(2)*t*t + t)*a.outTangent() + + (T(-2)*t*t*t + T(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent()).normalized(); +} + +/** @relatesalso CubicHermite +@brief Spline interpolation of two cubic Hermite quaternions + +Unlike @ref splerp(const CubicHermite&, const CubicHermite&, U) this adds +a normalization step after. Given segment points @f$ \boldsymbol{p}_i @f$, +in-tangents @f$ \boldsymbol{m}_i @f$ and out-tangents @f$ \boldsymbol{n}_i @f$, +the interpolated value @f$ \boldsymbol{p} @f$ at phase @f$ t @f$ is: @f[ + \begin{array}{rcl} + \boldsymbol{p'}(t) & = & (2 t^3 - 3 t^2 + 1) \boldsymbol{p}_a + (t^3 - 2 t^2 + t) \boldsymbol{n}_a + (-2 t^3 + 3 t^2) \boldsymbol{p}_b + (t^3 - t^2)\boldsymbol{m}_b \\ + \boldsymbol{p}(t) & = & \cfrac{\boldsymbol{p'}(t)}{|\boldsymbol{p'}(t)|} + \end{array} +@f] + +Expects that @ref CubicHermite::point() is a normalized quaternion in both @p a +and @p b. +@see @ref Quaternion::isNormalized(), + @ref splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref select(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) +*/ +template Quaternion splerp(const CubicHermiteQuaternion& a, const CubicHermiteQuaternion& b, T t) { + CORRADE_ASSERT(a.point().isNormalized() && b.point().isNormalized(), + "Math::splerp(): quaternion spline points must be normalized", {}); + return ((T(2)*t*t*t - T(3)*t*t + T(1))*a.point() + + (t*t*t - T(2)*t*t + t)*a.outTangent() + + (T(-2)*t*t*t + T(3)*t*t)*b.point() + + (t*t*t - t*t)*b.inTangent()).normalized(); +} + +template inline bool CubicHermite::operator==(const CubicHermite& other) const { + /* For non-scalar types default implementation of TypeTraits would be used, + which is just operator== */ + return TypeTraits::equals(_inTangent, other._inTangent) && + TypeTraits::equals(_point, other._point) && + TypeTraits::equals(_outTangent, other._outTangent); +} + +}} + +#endif diff --git a/src/Magnum/Math/Functions.h b/src/Magnum/Math/Functions.h index 0612b91c0..9909406b9 100644 --- a/src/Magnum/Math/Functions.h +++ b/src/Magnum/Math/Functions.h @@ -559,9 +559,13 @@ The interpolation for vectors is done as in following, similarly for scalars: @f \boldsymbol{v_{LERP}} = (1 - t) \boldsymbol{v_A} + t \boldsymbol{v_B} @f] -See @ref select() for constant interpolation using the same API. +See @ref select() for constant interpolation using the same API and +@ref splerp() for spline interpolation. @see @ref lerpInverted(), @ref lerp(const Complex&, const Complex&, T), - @ref lerp(const Quaternion&, const Quaternion&, T) + @ref lerp(const Quaternion&, const Quaternion&, T), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) @m_keyword{mix(),GLSL mix(),} */ template inline diff --git a/src/Magnum/Math/Math.h b/src/Magnum/Math/Math.h index 6b432f932..e721e2c95 100644 --- a/src/Magnum/Math/Math.h +++ b/src/Magnum/Math/Math.h @@ -43,14 +43,6 @@ template class BoolVector; /* Class Constants used only statically */ -template class Bezier; -template using QuadraticBezier = Bezier<2, dimensions, T>; -template using CubicBezier = Bezier<3, dimensions, T>; -template using QuadraticBezier2D = QuadraticBezier<2, T>; -template using QuadraticBezier3D = QuadraticBezier<3, T>; -template using CubicBezier2D = CubicBezier<2, T>; -template using CubicBezier3D = CubicBezier<3, T>; - template class Complex; template class Dual; template class DualComplex; @@ -90,6 +82,21 @@ template class Vector4; template class Color3; template class Color4; +template class Bezier; +template using QuadraticBezier = Bezier<2, dimensions, T>; +template using CubicBezier = Bezier<3, dimensions, T>; +template using QuadraticBezier2D = QuadraticBezier<2, T>; +template using QuadraticBezier3D = QuadraticBezier<3, T>; +template using CubicBezier2D = CubicBezier<2, T>; +template using CubicBezier3D = CubicBezier<3, T>; + +template class CubicHermite; +template using CubicHermite1D = CubicHermite; +template using CubicHermite2D = CubicHermite>; +template using CubicHermite3D = CubicHermite>; +template using CubicHermiteComplex = CubicHermite>; +template using CubicHermiteQuaternion = CubicHermite>; + template class Range; template using Range1D = Range<1, T>; template class Range2D; diff --git a/src/Magnum/Math/Quaternion.h b/src/Magnum/Math/Quaternion.h index 2256104a4..8917a5549 100644 --- a/src/Magnum/Math/Quaternion.h +++ b/src/Magnum/Math/Quaternion.h @@ -90,7 +90,10 @@ Expects that both quaternions are normalized. @f[ @see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion&, const Quaternion&, T), @ref sclerp(), @ref lerp(const T&, const T&, U), - @ref lerp(const Complex&, const Complex&, T) + @ref lerp(const Complex&, const Complex&, T), + @ref lerp(const CubicHermite&, const CubicHermite&, U), + @ref lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T), + @ref lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) */ template inline Quaternion lerp(const Quaternion& normalizedA, const Quaternion& normalizedB, T t) { CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index 9563699da..4b5a7d4c4 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -54,6 +54,7 @@ corrade_add_test(MathQuaternionTest QuaternionTest.cpp LIBRARIES MagnumMathTestL corrade_add_test(MathDualQuaternionTest DualQuaternionTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathBezierTest BezierTest.cpp LIBRARIES MagnumMathTestLib) +corrade_add_test(MathCubicHermiteTest CubicHermiteTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathFrustumTest FrustumTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib) @@ -66,6 +67,7 @@ set_property(TARGET MathMatrix3Test MathMatrix4Test MathComplexTest + MathCubicHermiteTest MathDualComplexTest MathQuaternionTest MathDualQuaternionTest diff --git a/src/Magnum/Math/Test/CubicHermiteTest.cpp b/src/Magnum/Math/Test/CubicHermiteTest.cpp new file mode 100644 index 000000000..ea3fd5213 --- /dev/null +++ b/src/Magnum/Math/Test/CubicHermiteTest.cpp @@ -0,0 +1,915 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Math/CubicHermite.h" +#include "Magnum/Math/Functions.h" +#include "Magnum/Math/Vector2.h" + +namespace Magnum { namespace Math { namespace Test { + +struct CubicHermiteTest: Corrade::TestSuite::Tester { + explicit CubicHermiteTest(); + + void constructScalar(); + void constructVector(); + void constructComplex(); + void constructQuaternion(); + + void constructDefaultScalar(); + void constructDefaultVector(); + void constructDefaultComplex(); + void constructDefaultQuaternion(); + + void constructZeroScalar(); + void constructZeroVector(); + void constructZeroComplex(); + void constructZeroQuaternion(); + + void constructIdentityScalar(); + void constructIdentityVector(); + void constructIdentityComplex(); + void constructIdentityQuaternion(); + + void constructNoInitScalar(); + void constructNoInitVector(); + void constructNoInitComplex(); + void constructNoInitQuaternion(); + + void constructConversionScalar(); + void constructConversionVector(); + void constructConversionComplex(); + void constructConversionQuaternion(); + + void constructCopyScalar(); + void constructCopyVector(); + void constructCopyComplex(); + void constructCopyQuaternion(); + + void dataScalar(); + void dataVector(); + void dataComplex(); + void dataQuaternion(); + + void compareScalar(); + void compareVector(); + void compareComplex(); + void compareQuaternion(); + + void selectScalar(); + void selectVector(); + void selectComplex(); + void selectQuaternion(); + + void lerpScalar(); + void lerpVector(); + void lerpComplex(); + void lerpComplexNotNormalized(); + void lerpQuaternion(); + void lerpQuaternionNotNormalized(); + + void splerpScalar(); + void splerpVector(); + void splerpComplex(); + void splerpComplexNotNormalized(); + void splerpQuaternion(); + void splerpQuaternionNotNormalized(); + + void debugScalar(); + void debugVector(); + void debugComplex(); + void debugQuaternion(); +}; + +CubicHermiteTest::CubicHermiteTest() { + addTests({&CubicHermiteTest::constructScalar, + &CubicHermiteTest::constructVector, + &CubicHermiteTest::constructComplex, + &CubicHermiteTest::constructQuaternion, + + &CubicHermiteTest::constructDefaultScalar, + &CubicHermiteTest::constructDefaultVector, + &CubicHermiteTest::constructDefaultComplex, + &CubicHermiteTest::constructDefaultQuaternion, + + &CubicHermiteTest::constructZeroScalar, + &CubicHermiteTest::constructZeroVector, + &CubicHermiteTest::constructZeroComplex, + &CubicHermiteTest::constructZeroQuaternion, + + &CubicHermiteTest::constructIdentityScalar, + &CubicHermiteTest::constructIdentityVector, + &CubicHermiteTest::constructIdentityComplex, + &CubicHermiteTest::constructIdentityQuaternion, + + &CubicHermiteTest::constructNoInitScalar, + &CubicHermiteTest::constructNoInitVector, + &CubicHermiteTest::constructNoInitComplex, + &CubicHermiteTest::constructNoInitQuaternion, + + &CubicHermiteTest::constructConversionScalar, + &CubicHermiteTest::constructConversionVector, + &CubicHermiteTest::constructConversionComplex, + &CubicHermiteTest::constructConversionQuaternion, + + &CubicHermiteTest::constructCopyScalar, + &CubicHermiteTest::constructCopyVector, + &CubicHermiteTest::constructCopyComplex, + &CubicHermiteTest::constructCopyQuaternion, + + &CubicHermiteTest::dataScalar, + &CubicHermiteTest::dataVector, + &CubicHermiteTest::dataComplex, + &CubicHermiteTest::dataQuaternion, + + &CubicHermiteTest::compareScalar, + &CubicHermiteTest::compareVector, + &CubicHermiteTest::compareComplex, + &CubicHermiteTest::compareQuaternion, + + &CubicHermiteTest::selectScalar, + &CubicHermiteTest::selectVector, + &CubicHermiteTest::selectComplex, + &CubicHermiteTest::selectQuaternion, + + &CubicHermiteTest::lerpScalar, + &CubicHermiteTest::lerpVector, + &CubicHermiteTest::lerpComplex, + &CubicHermiteTest::lerpComplexNotNormalized, + &CubicHermiteTest::lerpQuaternion, + &CubicHermiteTest::lerpQuaternionNotNormalized, + + &CubicHermiteTest::splerpScalar, + &CubicHermiteTest::splerpVector, + &CubicHermiteTest::splerpComplex, + &CubicHermiteTest::splerpComplexNotNormalized, + &CubicHermiteTest::splerpQuaternion, + &CubicHermiteTest::splerpQuaternionNotNormalized, + + &CubicHermiteTest::debugScalar, + &CubicHermiteTest::debugVector, + &CubicHermiteTest::debugComplex, + &CubicHermiteTest::debugQuaternion}); +} + +typedef Math::Vector2 Vector2; +typedef Math::Complex Complex; +typedef Math::Quaternion Quaternion; +typedef Math::CubicHermite1D CubicHermite1D; +typedef Math::CubicHermite2D CubicHermite2D; +typedef Math::CubicHermiteComplex CubicHermiteComplex; +typedef Math::CubicHermiteQuaternion CubicHermiteQuaternion; + +void CubicHermiteTest::constructScalar() { + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + CubicHermite1D b{2.0f, -2.0f, -0.5f}; + + CORRADE_COMPARE(a.inTangent(), 2.0f); + CORRADE_COMPARE(b.inTangent(), 2.0f); + CORRADE_COMPARE(a.point(), -2.0f); + CORRADE_COMPARE(b.point(), -2.0f); + CORRADE_COMPARE(a.outTangent(), -0.5f); + CORRADE_COMPARE(b.outTangent(), -0.5f); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructVector() { + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + CubicHermite2D b{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + + CORRADE_COMPARE(a.inTangent(), (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(b.inTangent(), (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(a.point(), (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(b.point(), (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(a.outTangent(), (Vector2{3.0f, -0.5f})); + CORRADE_COMPARE(b.outTangent(), (Vector2{3.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructComplex() { + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + CubicHermiteComplex b{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + + CORRADE_COMPARE(a.inTangent(), (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(b.inTangent(), (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(a.point(), (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(b.point(), (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(a.outTangent(), (Complex{3.0f, -0.5f})); + CORRADE_COMPARE(b.outTangent(), (Complex{3.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructQuaternion() { + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + CubicHermiteQuaternion b{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + + CORRADE_COMPARE(a.inTangent(), (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(b.inTangent(), (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(a.point(), (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(b.point(), (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(a.outTangent(), (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + CORRADE_COMPARE(b.outTangent(), (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructDefaultScalar() { + constexpr CubicHermite1D a; + CubicHermite1D b; + + /* Equivalent to ZeroInit constructor */ + CORRADE_COMPARE(a, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + CORRADE_COMPARE(b, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultVector() { + constexpr CubicHermite2D a; + CubicHermite2D b; + + /* Equivalent to ZeroInit constructor */ + CORRADE_COMPARE(a, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultComplex() { + constexpr CubicHermiteComplex a; + CubicHermiteComplex b; + + /* Equivalent to IdentityInit constructor */ + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructDefaultQuaternion() { + constexpr CubicHermiteQuaternion a; + CubicHermiteQuaternion b; + + /* Equivalent to IdentityInit constructor */ + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); +} + +void CubicHermiteTest::constructZeroScalar() { + constexpr CubicHermite1D a{ZeroInit}; + CubicHermite1D b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + CORRADE_COMPARE(b, (CubicHermite1D{0.0f, 0.0f, 0.0f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroVector() { + constexpr CubicHermite2D a{ZeroInit}; + CubicHermite2D b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermite2D{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroComplex() { + constexpr CubicHermiteComplex a{ZeroInit}; + CubicHermiteComplex b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructZeroQuaternion() { + constexpr CubicHermiteQuaternion a{ZeroInit}; + CubicHermiteQuaternion b{ZeroInit}; + + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructIdentityScalar() { + CORRADE_VERIFY(!(std::is_constructible::value)); +} + +void CubicHermiteTest::constructIdentityVector() { + CORRADE_VERIFY(!(std::is_constructible::value)); +} + +void CubicHermiteTest::constructIdentityComplex() { + constexpr CubicHermiteComplex a{IdentityInit}; + CubicHermiteComplex b{IdentityInit}; + + CORRADE_COMPARE(a, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteComplex{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructIdentityQuaternion() { + constexpr CubicHermiteQuaternion a{IdentityInit}; + CubicHermiteQuaternion b{IdentityInit}; + + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{0.0f, 0.0f, 0.0f}, 0.0f}, + {{0.0f, 0.0f, 0.0f}, 1.0f}, + {{0.0f, 0.0f, 0.0f}, 0.0f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructNoInitScalar() { + CubicHermite1D spline{2.0f, -2.0f, -0.5f}; + new(&spline) CubicHermite1D{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermite1D{2.0f, -2.0f, -0.5f})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitVector() { + CubicHermite2D spline{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + new(&spline) CubicHermite2D{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermite2D{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitComplex() { + CubicHermiteComplex spline{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + new(&spline) CubicHermiteComplex{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermiteComplex{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructNoInitQuaternion() { + CubicHermiteQuaternion spline{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + new(&spline) CubicHermiteQuaternion{NoInit}; + + CORRADE_COMPARE(spline, (CubicHermiteQuaternion{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}})); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void CubicHermiteTest::constructConversionScalar() { + typedef Math::CubicHermite1D CubicHermite1Di; + + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + constexpr CubicHermite1Di b{a}; + CubicHermite1Di c{a}; + CORRADE_COMPARE(b, (CubicHermite1Di{2, -2, 0})); + CORRADE_COMPARE(c, (CubicHermite1Di{2, -2, 0})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionVector() { + typedef Math::CubicHermite2D CubicHermite2Di; + + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermite2Di b{a}; + CubicHermite2Di c{a}; + CORRADE_COMPARE(b, (CubicHermite2Di{{1, 2}, {1, -2}, {3, 0}})); + CORRADE_COMPARE(c, (CubicHermite2Di{{1, 2}, {1, -2}, {3, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionComplex() { + typedef Math::CubicHermiteComplex CubicHermiteComplexi; + + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermiteComplexi b{a}; + CubicHermiteComplexi c{a}; + CORRADE_COMPARE(b, (CubicHermiteComplexi{{1, 2}, {1, -2}, {3, 0}})); + CORRADE_COMPARE(c, (CubicHermiteComplexi{{1, 2}, {1, -2}, {3, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructConversionQuaternion() { + typedef Math::CubicHermiteQuaternion CubicHermiteQuaternioni; + + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr CubicHermiteQuaternioni b{a}; + CubicHermiteQuaternioni c{a}; + CORRADE_COMPARE(b, (CubicHermiteQuaternioni{ + {{1, 2, -1}, 3}, + {{1, -2, 0}, 1}, + {{3, 0, 1}, 0}})); + CORRADE_COMPARE(c, (CubicHermiteQuaternioni{ + {{1, 2, -1}, 3}, + {{1, -2, 0}, 1}, + {{3, 0, 1}, 0}})); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void CubicHermiteTest::constructCopyScalar() { + constexpr CubicHermite1D a{2.0f, -2.0f, -0.5f}; + constexpr CubicHermite1D b{a}; + + CORRADE_COMPARE(b, (CubicHermite1D{2.0f, -2.0f, -0.5f})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyVector() { + constexpr CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermite2D b{a}; + + CORRADE_COMPARE(b, (CubicHermite2D{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyComplex() { + constexpr CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr CubicHermiteComplex b{a}; + + CORRADE_COMPARE(b, (CubicHermiteComplex{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::constructCopyQuaternion() { + constexpr CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr CubicHermiteQuaternion b{a}; + + CORRADE_COMPARE(b, (CubicHermiteQuaternion{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}})); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void CubicHermiteTest::dataScalar() { + constexpr CubicHermite1D ca{2.0f, -2.0f, -0.5f}; + constexpr Float inTangent = ca.inTangent(); + constexpr Float point = ca.point(); + constexpr Float outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, 2.0f); + CORRADE_COMPARE(point, -2.0f); + CORRADE_COMPARE(outTangent, -0.5f); + + CubicHermite1D a{2.0f, -2.0f, -0.5f}; + a.inTangent() = 3.0f; + a.point() = 1.0f; + a.outTangent() = 2.0f; + CORRADE_COMPARE(a, (CubicHermite1D{3.0f, 1.0f, 2.0f})); +} + +void CubicHermiteTest::dataVector() { + constexpr CubicHermite2D ca{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr Vector2 inTangent = ca.inTangent(); + constexpr Vector2 point = ca.point(); + constexpr Vector2 outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, (Vector2{1.0f, 2.0f})); + CORRADE_COMPARE(point, (Vector2{1.5f, -2.0f})); + CORRADE_COMPARE(outTangent, (Vector2{3.0f, -0.5f})); + + CubicHermite2D a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + a.inTangent().y() = 3.0f; + a.point().x() = 1.0f; + a.outTangent().y() = 2.0f; + CORRADE_COMPARE(a, (CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::dataComplex() { + constexpr CubicHermiteComplex ca{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + constexpr Complex inTangent = ca.inTangent(); + constexpr Complex point = ca.point(); + constexpr Complex outTangent = ca.outTangent(); + CORRADE_COMPARE(inTangent, (Complex{1.0f, 2.0f})); + CORRADE_COMPARE(point, (Complex{1.5f, -2.0f})); + CORRADE_COMPARE(outTangent, (Complex{3.0f, -0.5f})); + + CubicHermiteComplex a{{1.0f, 2.0f}, {1.5f, -2.0f}, {3.0f, -0.5f}}; + a.inTangent().imaginary() = 3.0f; + a.point().real() = 1.0f; + a.outTangent().imaginary() = 2.0f; + CORRADE_COMPARE(a, (CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::dataQuaternion() { + constexpr CubicHermiteQuaternion ca{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + constexpr Quaternion inTangent = ca.inTangent(); + constexpr Quaternion point = ca.point(); + constexpr Quaternion outTangent = ca.outTangent(); + + CORRADE_COMPARE(inTangent, (Quaternion{{1.0f, 2.0f, -1.0f}, 3.0f})); + CORRADE_COMPARE(point, (Quaternion{{1.5f, -2.0f, 0.1f}, 1.1f})); + CORRADE_COMPARE(outTangent, (Quaternion{{3.0f, -0.5f, 1.2f}, 0.3f})); + + CubicHermiteQuaternion a{ + {{1.0f, 2.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.1f}, + {{3.0f, -0.5f, 1.2f}, 0.3f}}; + a.inTangent().vector().y() = 3.0f; + a.point().scalar() = 1.0f; + a.outTangent().vector().z() = 2.0f; + CORRADE_COMPARE(a, (CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); +} + +void CubicHermiteTest::compareScalar() { + CORRADE_VERIFY((CubicHermite1D{3.0f, 1.0f, 2.0f} == CubicHermite1D{3.0f, 1.0f + TypeTraits::epsilon()/2, 2.0f})); + CORRADE_VERIFY((CubicHermite1D{3.0f, 1.0f, 2.0f} != CubicHermite1D{3.0f + TypeTraits::epsilon()*6, 1.0f, 2.0f})); +} + +void CubicHermiteTest::compareVector() { + CORRADE_VERIFY((CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) == (CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f + TypeTraits::epsilon()/2}})); + CORRADE_VERIFY((CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermite2D{{1.0f + TypeTraits::epsilon()*2, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::compareComplex() { + CORRADE_VERIFY((CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) == (CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f + TypeTraits::epsilon()/2}})); + CORRADE_VERIFY((CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermiteComplex{{1.0f + TypeTraits::epsilon()*2, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}})); +} + +void CubicHermiteTest::compareQuaternion() { + CORRADE_VERIFY((CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}}) == (CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f + TypeTraits::epsilon()/2}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); + CORRADE_VERIFY((CubicHermiteQuaternion{ + {{1.0f, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}}) != (CubicHermiteQuaternion{ + {{1.0f + TypeTraits::epsilon()*2, 3.0f, -1.0f}, 3.0f}, + {{1.5f, -2.0f, 0.1f}, 1.0f}, + {{3.0f, -0.5f, 2.0f}, 0.3f}})); +} + +void CubicHermiteTest::selectScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::select(a, b, 0.8f), 3.0f); + CORRADE_COMPARE(Math::select(a, b, 1.0f), -2.0f); +} + +void CubicHermiteTest::selectVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); +} + +void CubicHermiteTest::selectComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Complex{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Complex{3.0f, 0.1f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Complex{-2.0f, 1.1f})); +} + +void CubicHermiteTest::selectQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{3.0f, 0.1f, 2.3f}, 0.7f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-2.0f, 1.1f, 1.0f}, 1.3f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::select(a, b, 0.0f), (Quaternion{{3.0f, 0.1f, 2.3f}, 0.7f})); + CORRADE_COMPARE(Math::select(a, b, 0.8f), (Quaternion{{3.0f, 0.1f, 2.3f}, 0.7f})); + CORRADE_COMPARE(Math::select(a, b, 1.0f), (Quaternion{{-2.0f, 1.1f, 1.0f}, 1.3f})); +} + +void CubicHermiteTest::lerpScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), -2.0f); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), 1.25f); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), 1.25f); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), -1.0f); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), -1.0f); +} + +void CubicHermiteTest::lerpVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Vector2{1.25f, 0.45f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Vector2{1.25f, 0.45f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Vector2{-1.0f, 0.9f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Vector2{-1.0f, 0.9f})); +} + +void CubicHermiteTest::lerpComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Complex{0.999445f, 0.0333148f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Complex{-0.876216f, 0.481919f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Complex{0.874384f, 0.485235f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Complex{0.874384f, 0.485235f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Complex{-0.78747f, 0.616353f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Complex{-0.78747f, 0.616353f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::lerpComplexNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + CORRADE_COMPARE(Math::lerp(CubicHermiteComplex{}, {}, 0.3f), Complex{}); + + /* These will, tho */ + CubicHermiteComplex a{{}, Complex{}*2.0f, {}}; + Math::lerp({}, a, 0.3f); + Math::lerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): complex numbers must be normalized\n" + "Math::lerp(): complex numbers must be normalized\n"); +} + +void CubicHermiteTest::lerpQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::lerp(a, b, 0.0f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f})); + CORRADE_COMPARE(Math::lerp(a, b, 1.0f), (Quaternion{{-0.711568f, 0.391362f, 0.355784f}, 0.462519f})); + + CORRADE_COMPARE(Math::lerp(a, b, 0.35f), (Quaternion{{0.392449f, 0.234067f, 0.780733f}, 0.426207f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.35f), (Quaternion{{0.392449f, 0.234067f, 0.780733f}, 0.426207f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::lerp(a, b, 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f})); + CORRADE_COMPARE(Math::lerp(a.point(), b.point(), 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f})); + CORRADE_VERIFY(Math::lerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::lerpQuaternionNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::lerp(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::lerp({}, a, 0.3f); + Math::lerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::lerp(): quaternions must be normalized\n" + "Math::lerp(): quaternions must be normalized\n"); +} + +void CubicHermiteTest::splerpScalar() { + CubicHermite1D a{2.0f, 3.0f, -1.0f}; + CubicHermite1D b{5.0f, -2.0f, 1.5f}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), 3.0f); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), -2.0f); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), 1.04525f); + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), -2.152f); +} + +void CubicHermiteTest::splerpVector() { + CubicHermite2D a{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CubicHermite2D b{{5.0f, 0.3f}, {-2.0f, 1.1f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Vector2{3.0f, 0.1f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Vector2{-2.0f, 1.1f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Vector2{1.04525f, 0.357862f})); + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Vector2{-2.152f, 0.9576f})); +} + +void CubicHermiteTest::splerpComplex() { + CubicHermiteComplex a{{2.0f, 1.5f}, {0.999445f, 0.0333148f}, {-1.0f, 0.0f}}; + CubicHermiteComplex b{{5.0f, 0.3f}, {-0.876216f, 0.481919f}, {1.5f, 0.3f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Complex{0.999445f, 0.0333148f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Complex{-0.876216f, 0.481919f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Complex{-0.483504f, 0.875342f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Complex{-0.95958f, 0.281435f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::splerpComplexNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + CORRADE_COMPARE(Math::splerp(CubicHermiteComplex{}, {}, 0.3f), Complex{}); + + /* These will, tho */ + CubicHermiteComplex a{{}, Complex{}*2.0f, {}}; + Math::splerp({}, a, 0.3f); + Math::splerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::splerp(): complex spline points must be normalized\n" + "Math::splerp(): complex spline points must be normalized\n"); +} + +void CubicHermiteTest::splerpQuaternion() { + CubicHermiteQuaternion a{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CubicHermiteQuaternion b{ + {{5.0f, 0.3f, 1.1f}, 0.5f}, + {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, + {{1.5f, 0.3f, 17.0f}, -7.0f}}; + + CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f})); + CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Quaternion{{-0.711568f, 0.391362f, 0.355784f}, 0.462519f})); + + CORRADE_COMPARE(Math::splerp(a, b, 0.35f), (Quaternion{{-0.309862f, 0.174831f, 0.809747f}, 0.466615f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.35f).isNormalized()); + + CORRADE_COMPARE(Math::splerp(a, b, 0.8f), (Quaternion{{-0.911408f, 0.23368f, 0.185318f}, 0.283524f})); + CORRADE_VERIFY(Math::splerp(a, b, 0.8f).isNormalized()); +} + +void CubicHermiteTest::splerpQuaternionNotNormalized() { + std::ostringstream out; + Error redirectError{&out}; + + /* This one should not assert as the default constructor should create + identity point */ + Math::splerp(CubicHermiteQuaternion{}, {}, 0.3f); + + /* These will, tho */ + CubicHermiteQuaternion a{{}, Quaternion{}*2.0f, {}}; + Math::splerp({}, a, 0.3f); + Math::splerp(a, {}, 0.3f); + CORRADE_COMPARE(out.str(), + "Math::splerp(): quaternion spline points must be normalized\n" + "Math::splerp(): quaternion spline points must be normalized\n"); +} + +void CubicHermiteTest::debugScalar() { + std::ostringstream out; + Debug{&out} << CubicHermite1D{2.0f, 3.0f, -1.0f}; + CORRADE_COMPARE(out.str(), "CubicHermite(2, 3, -1)\n"); +} + +void CubicHermiteTest::debugVector() { + std::ostringstream out; + Debug{&out} << CubicHermite2D{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Vector(2, 1.5), Vector(3, 0.1), Vector(-1, 0))\n"); +} + +void CubicHermiteTest::debugComplex() { + std::ostringstream out; + Debug{&out} << CubicHermiteComplex{{2.0f, 1.5f}, {3.0f, 0.1f}, {-1.0f, 0.0f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Complex(2, 1.5), Complex(3, 0.1), Complex(-1, 0))\n"); +} + +void CubicHermiteTest::debugQuaternion() { + std::ostringstream out; + Debug{&out} << CubicHermiteQuaternion{ + {{2.0f, 1.5f, 0.3f}, 1.1f}, + {{3.0f, 0.1f, 2.3f}, 0.7f}, + {{-1.0f, 0.0f, 0.3f}, 0.4f}}; + CORRADE_COMPARE(out.str(), "CubicHermite(Quaternion({2, 1.5, 0.3}, 1.1), Quaternion({3, 0.1, 2.3}, 0.7), Quaternion({-1, 0, 0.3}, 0.4))\n"); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Math::Test::CubicHermiteTest) diff --git a/src/Magnum/Math/instantiation.cpp b/src/Magnum/Math/instantiation.cpp index f6cf11909..baa5220fb 100644 --- a/src/Magnum/Math/instantiation.cpp +++ b/src/Magnum/Math/instantiation.cpp @@ -24,6 +24,7 @@ */ #include "Magnum/Math/Bezier.h" +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/Frustum.h" @@ -101,6 +102,17 @@ template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bez template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 2, Double>&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 3, Double>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); +template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite>&); + template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex&);