Browse Source

Merge branch 'master' of https://github.com/mosra/magnum

pull/280/head
Bas van Schaik 8 years ago
parent
commit
2e1cc29a52
  1. 48
      doc/animation.dox
  2. 17
      doc/changelog.dox
  3. 1
      doc/features.dox
  4. 4
      doc/namespaces.dox
  5. 14
      doc/snippets/MagnumMath.cpp
  6. 41
      doc/transformations.dox
  7. 16
      doc/types.dox
  8. 38
      src/Magnum/Animation/Interpolation.cpp
  9. 62
      src/Magnum/Animation/Interpolation.h
  10. 116
      src/Magnum/Animation/Test/InterpolationTest.cpp
  11. 4
      src/Magnum/Animation/Test/PlayerTest.cpp
  12. 34
      src/Magnum/Animation/Track.h
  13. 1
      src/Magnum/CMakeLists.txt
  14. 8
      src/Magnum/DebugTools/BufferData.h
  15. 8
      src/Magnum/DebugTools/ForceRenderer.h
  16. 8
      src/Magnum/DebugTools/ObjectRenderer.h
  17. 4
      src/Magnum/DebugTools/ResourceManager.h
  18. 10
      src/Magnum/DebugTools/ShapeRenderer.h
  19. 32
      src/Magnum/DebugTools/TextureImage.h
  20. 30
      src/Magnum/Magnum.h
  21. 39
      src/Magnum/Math/Bezier.h
  22. 1
      src/Magnum/Math/CMakeLists.txt
  23. 73
      src/Magnum/Math/Complex.h
  24. 465
      src/Magnum/Math/CubicHermite.h
  25. 17
      src/Magnum/Math/Dual.h
  26. 10
      src/Magnum/Math/DualComplex.h
  27. 123
      src/Magnum/Math/DualQuaternion.h
  28. 9
      src/Magnum/Math/Functions.h
  29. 41
      src/Magnum/Math/Half.cpp
  30. 14
      src/Magnum/Math/Half.h
  31. 23
      src/Magnum/Math/Math.h
  32. 133
      src/Magnum/Math/Quaternion.h
  33. 15
      src/Magnum/Math/Test/BezierTest.cpp
  34. 4
      src/Magnum/Math/Test/CMakeLists.txt
  35. 85
      src/Magnum/Math/Test/ComplexTest.cpp
  36. 956
      src/Magnum/Math/Test/CubicHermiteTest.cpp
  37. 93
      src/Magnum/Math/Test/DualQuaternionTest.cpp
  38. 23
      src/Magnum/Math/Test/DualTest.cpp
  39. 6
      src/Magnum/Math/Test/HalfTest.cpp
  40. 152
      src/Magnum/Math/Test/InterpolationBenchmark.cpp
  41. 164
      src/Magnum/Math/Test/QuaternionTest.cpp
  42. 4
      src/Magnum/Math/Vector.h
  43. 12
      src/Magnum/Math/instantiation.cpp
  44. 11
      src/Magnum/Trade/AnimationData.cpp
  45. 97
      src/Magnum/Trade/AnimationData.h

48
doc/animation.dox

@ -0,0 +1,48 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
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.
*/
namespace Magnum {
/** @page animation Keyframe-based animation
@brief Layered framework for authoring, loading, optimizing and playing back animations
@tableofcontents
@m_footernavigation
Sorry, this page is yet to be written. Please see the @ref Animation namespace,
especially documentation of the lowest-level @ref Animation::interpolate()
function, the higher-level @ref Animation::Track class and the top-level
@ref Animation::Player API. The @ref transformations-interpolation
documentation lists all available builtin interpolation methods.
@experimental
@todoc bottom-up description (interpolating values, keyframes, storing, at(), player, callbacks)
@todoc perf optimizations (shortest path, preprocessing..., extrapolation, ...), batchplayer
@todoc mention animation import
*/
}

17
doc/changelog.dox

@ -47,6 +47,9 @@ See also:
@subsubsection changelog-latest-new-math Math library @subsubsection changelog-latest-new-math Math library
- New @ref Math::CubicHermite class for cubic Hermite spline interpolation,
convertible to and from cubic Bézier curves using
@ref Math::Bezier::fromCubicHermite() and @ref Math::CubicHermite::fromBezier()
- Added @ref Math::Intersection::rangeFrustum(), - Added @ref Math::Intersection::rangeFrustum(),
@ref Math::Intersection::aabbFrustum(), @ref Math::Intersection::aabbFrustum(),
@ref Math::Intersection::sphereFrustum(), @ref Math::Intersection::sphereFrustum(),
@ -66,6 +69,14 @@ See also:
@ref Math::Color4::fromSrgb(UnsignedInt, T), @ref Math::Color3::toSrgbInt(), @ref Math::Color4::fromSrgb(UnsignedInt, T), @ref Math::Color3::toSrgbInt(),
and @ref Math::Color4::toSrgbAlphaInt() for easier conversion of packed and @ref Math::Color4::toSrgbAlphaInt() for easier conversion of packed
24-/32-bit sRGB colors to and from @ref Math::Color3 / @ref Math::Color4 24-/32-bit sRGB colors to and from @ref Math::Color3 / @ref Math::Color4
- Added @ref Math::lerp(const Complex<T>&, const Complex<T>&, T) and
@ref Math::slerp(const Complex<T>&, const Complex<T>&, T) for feature
parity with @ref Math::Quaternion
- Added @ref Math::lerpShortestPath(const Quaternion<T>&, const Quaternion<T>&, T),
@ref Math::slerpShortestPath(const Quaternion<T>&, const Quaternion<T>&, T)
and @ref Math::sclerpShortestPath(const DualQuaternion<T>&, const DualQuaternion<T>&, T)
variants; clarified that the original versions are explicitly *not*
shortest-path
- Added @ref Math::Range2D::x(), @ref Math::Range3D::x(), - Added @ref Math::Range2D::x(), @ref Math::Range3D::x(),
@ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z() @ref Math::Range2D::y(), @ref Math::Range3D::y(), @ref Math::Range3D::z()
and @ref Math::Range3D::y() for slicing ranges into lower dimensions and @ref Math::Range3D::y() for slicing ranges into lower dimensions
@ -85,6 +96,10 @@ See also:
@ref Math::Distance::pointPlane() and others @ref Math::Distance::pointPlane() and others
- Ability to convert @ref Math::BoolVector from and to external - Ability to convert @ref Math::BoolVector from and to external
representation representation
- Mutable overloads for @ref Math::Complex::real(),
@ref Math::Complex::imaginary(), @ref Math::Dual::real(),
@ref Math::Dual::dual(), @ref Math::Quaternion::vector() and
@ref Math::Quaternion::scalar()
- Ability to use @ref Math::Complex, @ref Math::DualComplex, - Ability to use @ref Math::Complex, @ref Math::DualComplex,
@ref Math::Quaternion, @ref Math::DualQuaternion with @ref Math::Quaternion, @ref Math::DualQuaternion with
@ref Corrade::Utility::Configuration and @ref Corrade::Utility::Arguments @ref Corrade::Utility::Configuration and @ref Corrade::Utility::Arguments
@ -297,6 +312,8 @@ See also:
- @ref Platform::GlfwApplication::exec() now asserts instead of crashing if - @ref Platform::GlfwApplication::exec() now asserts instead of crashing if
the constructor fails to create a window (see [mosra/magnum#192](https://github.com/mosra/magnum/issues/192), the constructor fails to create a window (see [mosra/magnum#192](https://github.com/mosra/magnum/issues/192),
[mosra/magnum#272](https://github.com/mosra/magnum/pull/272)) [mosra/magnum#272](https://github.com/mosra/magnum/pull/272))
- @ref Math::sclerp() was not properly interpolating the translation if
rotation was the same on both sides
@subsection changelog-latest-docs Documentation @subsection changelog-latest-docs Documentation

1
doc/features.dox

@ -35,6 +35,7 @@ necessary to read through everything, pick only what you need.
- @subpage types --- @copybrief types - @subpage types --- @copybrief types
- @subpage matrix-vector --- @copybrief matrix-vector - @subpage matrix-vector --- @copybrief matrix-vector
- @subpage transformations --- @copybrief transformations - @subpage transformations --- @copybrief transformations
- @subpage animation --- @copybrief animation
- @subpage plugins --- @copybrief plugins - @subpage plugins --- @copybrief plugins
- @subpage opengl-wrapping --- @copybrief opengl-wrapping - @subpage opengl-wrapping --- @copybrief opengl-wrapping
- @subpage shaders --- @copybrief shaders - @subpage shaders --- @copybrief shaders

4
doc/namespaces.dox

@ -186,8 +186,8 @@ See @ref building and @ref cmake for more information.
@brief Keyframe-based animation @brief Keyframe-based animation
This library is built as part of Magnum by default. To use it, you need to This library is built as part of Magnum by default. To use it, you need to
find `Magnum` package and link to `Magnum::Magnum` target. See @ref building find `Magnum` package and link to `Magnum::Magnum` target. See @ref building,
and @ref cmake for more information. @ref cmake and @ref animation for more information.
@experimental @experimental
*/ */

14
doc/snippets/MagnumMath.cpp

@ -25,6 +25,8 @@
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
#include "Magnum/Math/Color.h" #include "Magnum/Math/Color.h"
#include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualComplex.h"
#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/DualQuaternion.h"
#include "Magnum/Math/Half.h" #include "Magnum/Math/Half.h"
@ -839,6 +841,18 @@ Color4 a = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f}
static_cast<void>(a); static_cast<void>(a);
} }
{
/* [CubicHermite-fromBezier] */
CubicBezier2D segment;
auto startPoint = CubicHermite2D::fromBezier(
{Vector2{}, Vector2{}, Vector2{}, segment[3]}, segment);
auto endPoint = CubicHermite2D::fromBezier(segment,
{segment[0], Vector2{}, Vector2{}, Vector2{}});
/* [CubicHermite-fromBezier] */
static_cast<void>(startPoint);
static_cast<void>(endPoint);
}
{ {
/* [Half-usage] */ /* [Half-usage] */
using namespace Math::Literals; using namespace Math::Literals;

41
doc/transformations.dox

@ -243,8 +243,45 @@ and translation matrices using @ref DualComplex::fromMatrix() and
@section transformations-interpolation Transformation interpolation @section transformations-interpolation Transformation interpolation
@todoc Write this when interpolation is done also for (dual) complex numbers and Magnum provides various tools for interpolation, from basic constant/linear
dual quaternions interpolation of scalars and vectors to spline-based interpolation of
quaternions or dual quaternions. The table below is a complete list of all
builtin interpolation methods:
@m_class{m-fullwidth}
Interpolation | Value type | Result type | Interpolator
------------------- | ----------------- | ------------- | ------------
Constant | any `V` | `V` | @ref Math::select()
Constant | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::select(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::select()"
Linear | @cpp bool @ce <b></b> | @cpp bool @ce <b></b> | @ref Math::select()
Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select()
Linear | any scalar `V` | `V` | @ref Math::lerp()
Linear | any vector `V` | `V` | @ref Math::lerp()
Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex<T>&, const Complex<T>&, T) "Math::lerp()"
Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion<T>&, const Quaternion<T>&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion<T>&, const Quaternion<T>&, T) "Math::lerpShortestPath()"
Linear | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::lerp(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::lerp()"
Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T) "Math::lerp()"
Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T) "Math::lerp()"
Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex<T>&, const Complex<T>&, T) "Math::slerp()"
Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion<T>&, const Quaternion<T>&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion<T>&, const Quaternion<T>&, T) "Math::slerpShortestPath()"
Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion<T>&, const DualQuaternion<T>&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion<T>&, const DualQuaternion<T>&, T) "Math::sclerpShortestPath()"
Spline | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::splerp(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::splerp()"
Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T) "Math::splerp()"
Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T) "Math::splerp()"
The @ref Math::CubicHermite class is a generic implementation of cubic Hermite
splines to which other curve types such as Bézier are convertible. See its
documentation for more information.
@attention Direct interpolation of transformation matrices or Euler angles is
not supported due to their well known performance / correctness issues and
limitations such as gimbal lock --- you have to convert them to one of the
supported representations such as @ref Complex numbers or @ref Quaternion
and interpolate these instead.
Interpolation is most commonly used in animations --- see @ref animation for
more information.
@section transformations-normalization Normalizing transformations @section transformations-normalization Normalizing transformations

16
doc/types.dox

@ -42,14 +42,14 @@ consistency and reduce confusion (e.g. @ref std::int32_t, @cpp int @ce and
| Magnum type | Size | Equivalent GLSL type | | Magnum type | Size | Equivalent GLSL type |
| ------------------ | -------------- | ----------------------- | | ------------------ | -------------- | ----------------------- |
| @ref UnsignedByte | 8bit unsigned | | | @ref UnsignedByte | 8bit unsigned | (*none*) |
| @ref Byte | 8bit signed | | | @ref Byte | 8bit signed | (*none*) |
| @ref UnsignedShort | 16bit unsigned | | | @ref UnsignedShort | 16bit unsigned | (*none*) |
| @ref Short | 16bit signed | | | @ref Short | 16bit signed | (*none*) |
| @ref UnsignedInt | 32bit unsigned | @glsl uint @ce <b></b> | | @ref UnsignedInt | 32bit unsigned | @glsl uint @ce <b></b> |
| @ref Int | 32bit signed | @glsl int @ce <b></b> | | @ref Int | 32bit signed | @glsl int @ce <b></b> |
| @ref UnsignedLong | 64bit unsigned | | | @ref UnsignedLong | 64bit unsigned | (*none*) |
| @ref Long | 64bit signed | | | @ref Long | 64bit signed | (*none*) |
| @ref Half | 16bit | (*none*) | | @ref Half | 16bit | (*none*) |
| @ref Float | 32bit | @glsl float @ce <b></b> | | @ref Float | 32bit | @glsl float @ce <b></b> |
| @ref Double | 64bit | @glsl double @ce <b></b> | | @ref Double | 64bit | @glsl double @ce <b></b> |
@ -170,10 +170,14 @@ Other types, which don't have their GLSL equivalent, are:
or @ref QuadraticBezier3Dd or @ref QuadraticBezier3Dd
- @ref CubicBezier2D or @ref CubicBezier2Dd, @ref CubicBezier3D - @ref CubicBezier2D or @ref CubicBezier2Dd, @ref CubicBezier3D
or @ref CubicBezier3Dd 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 Complex or @ref Complexd, @ref DualComplex or @ref DualComplexd
- @ref Frustum or @ref Frustumd - @ref Frustum or @ref Frustumd
- @ref Quaternion or @ref Quaterniond, @ref DualQuaternion or - @ref Quaternion or @ref Quaterniond, @ref DualQuaternion or
@ref DualQuaterniond @ref DualQuaterniond
- @ref CubicHermiteComplex or @ref CubicHermiteComplexd
- @ref CubicHermiteQuaternion or @ref CubicHermiteQuaterniond
- @ref Range1D / @ref Range2D / @ref Range3D, @ref Range1Di / @ref Range2Di / - @ref Range1D / @ref Range2D / @ref Range3D, @ref Range1Di / @ref Range2Di /
@ref Range3Di or @ref Range1Dd / @ref Range2Dd / @ref Range3Dd @ref Range3Di or @ref Range1Dd / @ref Range2Dd / @ref Range3Dd

38
src/Magnum/Animation/Interpolation.cpp

@ -25,6 +25,7 @@
#include "Interpolation.h" #include "Interpolation.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/DualQuaternion.h"
namespace Magnum { namespace Animation { namespace Magnum { namespace Animation {
@ -36,6 +37,7 @@ Debug& operator<<(Debug& debug, const Interpolation value) {
#define _c(value) case Interpolation::value: return debug << "Animation::Interpolation::" #value; #define _c(value) case Interpolation::value: return debug << "Animation::Interpolation::" #value;
_c(Constant) _c(Constant)
_c(Linear) _c(Linear)
_c(Spline)
_c(Custom) _c(Custom)
#undef _c #undef _c
/* LCOV_EXCL_STOP */ /* LCOV_EXCL_STOP */
@ -61,11 +63,24 @@ Debug& operator<<(Debug& debug, const Extrapolation value) {
namespace Implementation { namespace Implementation {
template<class T> auto TypeTraits<Math::Quaternion<T>, Math::Quaternion<T>>::interpolator(Interpolation interpolation) -> Interpolator { template<class T> auto TypeTraits<Math::Complex<T>, Math::Complex<T>>::interpolator(Interpolation interpolation) -> Interpolator {
switch(interpolation) { switch(interpolation) {
case Interpolation::Constant: return Math::select; case Interpolation::Constant: return Math::select;
case Interpolation::Linear: return Math::slerp; case Interpolation::Linear: return Math::slerp;
case Interpolation::Spline:
case Interpolation::Custom: ; /* nope */
}
CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {});
}
template<class T> auto TypeTraits<Math::Quaternion<T>, Math::Quaternion<T>>::interpolator(Interpolation interpolation) -> Interpolator {
switch(interpolation) {
case Interpolation::Constant: return Math::select;
case Interpolation::Linear: return Math::slerpShortestPath;
case Interpolation::Spline:
case Interpolation::Custom: ; /* nope */ case Interpolation::Custom: ; /* nope */
} }
@ -75,7 +90,20 @@ template<class T> auto TypeTraits<Math::Quaternion<T>, Math::Quaternion<T>>::int
template<class T> auto TypeTraits<Math::DualQuaternion<T>, Math::DualQuaternion<T>>::interpolator(Interpolation interpolation) -> Interpolator { template<class T> auto TypeTraits<Math::DualQuaternion<T>, Math::DualQuaternion<T>>::interpolator(Interpolation interpolation) -> Interpolator {
switch(interpolation) { switch(interpolation) {
case Interpolation::Constant: return Math::select; case Interpolation::Constant: return Math::select;
case Interpolation::Linear: return Math::sclerp; case Interpolation::Linear: return Math::sclerpShortestPath;
case Interpolation::Spline:
case Interpolation::Custom: ; /* nope */
}
CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {});
}
template<class T> auto TypeTraits<Math::CubicHermite<T>, T>::interpolator(Interpolation interpolation) -> Interpolator {
switch(interpolation) {
case Interpolation::Constant: return Math::select;
case Interpolation::Linear: return Math::lerp;
case Interpolation::Spline: return Math::splerp;
case Interpolation::Custom: ; /* nope */ case Interpolation::Custom: ; /* nope */
} }
@ -83,8 +111,14 @@ template<class T> auto TypeTraits<Math::DualQuaternion<T>, Math::DualQuaternion<
CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {}); CORRADE_ASSERT(false, "Animation::interpolatorFor(): can't deduce interpolator function for" << interpolation, {});
} }
template struct MAGNUM_EXPORT TypeTraits<Math::Complex<Float>, Math::Complex<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::Quaternion<Float>, Math::Quaternion<Float>>; template struct MAGNUM_EXPORT TypeTraits<Math::Quaternion<Float>, Math::Quaternion<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::DualQuaternion<Float>, Math::DualQuaternion<Float>>; template struct MAGNUM_EXPORT TypeTraits<Math::DualQuaternion<Float>, Math::DualQuaternion<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<Float>, Float>;
template struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<Math::Vector2<Float>>, Math::Vector2<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<Math::Vector3<Float>>, Math::Vector3<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<Math::Complex<Float>>, Math::Complex<Float>>;
template struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<Math::Quaternion<Float>>, Math::Quaternion<Float>>;
} }

62
src/Magnum/Animation/Interpolation.h

@ -60,6 +60,13 @@ enum class Interpolation: UnsignedByte {
*/ */
Linear, Linear,
/**
* Spline interpolation.
*
* @see @ref Math::splerp()
*/
Spline,
/** /**
* Custom interpolation. An user-supplied interpolation function should be * Custom interpolation. An user-supplied interpolation function should be
* used. * used.
@ -74,8 +81,9 @@ MAGNUM_EXPORT Debug& operator<<(Debug& debug, Interpolation value);
@brief Animation result type for given value type @brief Animation result type for given value type
Result of interpolating two `V` values (for example interpolating two Result of interpolating two `V` values (for example interpolating two
@ref Color3 values gives back a @ref Color3 again, but interpolating a spline @ref Color3 values gives back a @ref Color3 again, but interpolating a
does not result in a spline). @ref Magnum::CubicHermite2D "CubicHermite2D" spline results in
@ref Magnum::Vector2 "Vector2").
@experimental @experimental
*/ */
#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */
@ -89,21 +97,30 @@ template<class V> using ResultOf = typename Implementation::ResultTraits<V>::Typ
Expects that @p interpolation is not @ref Interpolation::Custom. Favors Expects that @p interpolation is not @ref Interpolation::Custom. Favors
output correctness over performance, supply custom interpolator functions for output correctness over performance, supply custom interpolator functions for
faster but less precise results. faster but potentially less correct results.
@m_class{m-fullwidth} @m_class{m-fullwidth}
Interpolation type | Value type | Result type | Interpolator Interpolation | Value type | Result type | Interpolator
------------------- | ----------------- | ------------- | ------------ ------------------- | ----------------- | ------------- | ------------
@ref Interpolation::Constant | any `V` | `V` | @ref Math::select() @ref Interpolation::Constant "Constant" | any `V` | `V` | @ref Math::select()
@ref Interpolation::Linear | @cpp bool @ce <b></b> | @cpp bool @ce <b></b> | @ref Math::select() @ref Interpolation::Constant "Constant" | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::select(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::select()"
@ref Interpolation::Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() @ref Interpolation::Linear "Linear" | @cpp bool @ce <b></b> | @cpp bool @ce <b></b> | @ref Math::select()
@ref Interpolation::Linear | any scalar `V` | `V` | @ref Math::lerp() @ref Interpolation::Linear "Linear" | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select()
@ref Interpolation::Linear | any vector `V` | `V` | @ref Math::lerp() @ref Interpolation::Linear "Linear" | any scalar `V` | `V` | @ref Math::lerp()
@ref Interpolation::Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion<T>&, const Quaternion<T>&, T) "Math::slerp()" @ref Interpolation::Linear "Linear" | any vector `V` | `V` | @ref Math::lerp()
@ref Interpolation::Linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion<T>&, const DualQuaternion<T>&, T) "Math::sclerp()" @ref Interpolation::Linear "Linear" | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex<T>&, const Complex<T>&, T) "Math::slerp()"
@ref Interpolation::Linear "Linear" | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerpShortestPath(const Quaternion<T>&, const Quaternion<T>&, T) "Math::slerpShortestPath()"
@see @ref interpolate(), @ref interpolateStrict() @ref Interpolation::Linear "Linear" | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerpShortestPath(const DualQuaternion<T>&, const DualQuaternion<T>&, T) "Math::sclerpShortestPath()"
@ref Interpolation::Linear "Linear" | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::lerp(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::lerp()"
@ref Interpolation::Linear "Linear" | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T) "Math::lerp()"
@ref Interpolation::Linear "Linear" | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T) "Math::lerp()"
@ref Interpolation::Spline "Spline" | @ref Math::CubicHermite "Math::CubicHermite<T>" | `T` | @ref Math::splerp(const CubicHermite<T>&, const CubicHermite<T>&, U) "Math::splerp()"
@ref Interpolation::Spline "Spline" | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T) "Math::splerp()"
@ref Interpolation::Spline "Spline" | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T) "Math::splerp()"
@see @ref interpolate(), @ref interpolateStrict(),
@ref transformations-interpolation, @ref Trade::animationInterpolatorFor()
@experimental @experimental
*/ */
template<class V, class R = ResultOf<V>> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float); template<class V, class R = ResultOf<V>> auto interpolatorFor(Interpolation interpolation) -> R(*)(const V&, const V&, Float);
@ -212,6 +229,9 @@ namespace Implementation {
template<class V> struct ResultTraits { template<class V> struct ResultTraits {
typedef V Type; typedef V Type;
}; };
template<class T> struct ResultTraits<Math::CubicHermite<T>> {
typedef T Type;
};
template<class V> struct TypeTraits<V, V> { template<class V> struct TypeTraits<V, V> {
typedef V(*Interpolator)(const V&, const V&, Float); typedef V(*Interpolator)(const V&, const V&, Float);
@ -222,6 +242,7 @@ template<class V> auto TypeTraits<V, V>::interpolator(Interpolation interpolatio
case Interpolation::Constant: return Math::select; case Interpolation::Constant: return Math::select;
case Interpolation::Linear: return Math::lerp; case Interpolation::Linear: return Math::lerp;
case Interpolation::Spline:
case Interpolation::Custom: ; /* nope */ case Interpolation::Custom: ; /* nope */
} }
@ -239,6 +260,7 @@ template<class T> auto TypeTraitsBool<T>::interpolator(Interpolation interpolati
case Interpolation::Constant: case Interpolation::Constant:
case Interpolation::Linear: return Math::select; case Interpolation::Linear: return Math::select;
case Interpolation::Spline:
case Interpolation::Custom: ; /* nope */ case Interpolation::Custom: ; /* nope */
} }
@ -247,6 +269,13 @@ template<class T> auto TypeTraitsBool<T>::interpolator(Interpolation interpolati
template<> struct TypeTraits<bool, bool>: TypeTraitsBool<bool> {}; template<> struct TypeTraits<bool, bool>: TypeTraitsBool<bool> {};
template<std::size_t size> struct TypeTraits<Math::BoolVector<size>, Math::BoolVector<size>>: TypeTraitsBool<Math::BoolVector<size>> {}; template<std::size_t size> struct TypeTraits<Math::BoolVector<size>, Math::BoolVector<size>>: TypeTraitsBool<Math::BoolVector<size>> {};
/* Complex, preferring slerp() as it is more precise */
template<class T> struct MAGNUM_EXPORT TypeTraits<Math::Complex<T>, Math::Complex<T>> {
typedef Math::Complex<T>(*Interpolator)(const Math::Complex<T>&, const Math::Complex<T>&, Float);
static Interpolator interpolator(Interpolation interpolation);
};
/* Quaternions and dual quaternions, preferring slerp() as it is more precise */ /* Quaternions and dual quaternions, preferring slerp() as it is more precise */
template<class T> struct MAGNUM_EXPORT TypeTraits<Math::Quaternion<T>, Math::Quaternion<T>> { template<class T> struct MAGNUM_EXPORT TypeTraits<Math::Quaternion<T>, Math::Quaternion<T>> {
typedef Math::Quaternion<T>(*Interpolator)(const Math::Quaternion<T>&, const Math::Quaternion<T>&, Float); typedef Math::Quaternion<T>(*Interpolator)(const Math::Quaternion<T>&, const Math::Quaternion<T>&, Float);
@ -259,6 +288,13 @@ template<class T> struct MAGNUM_EXPORT TypeTraits<Math::DualQuaternion<T>, Math:
static Interpolator interpolator(Interpolation interpolation); static Interpolator interpolator(Interpolation interpolation);
}; };
/* Cubic Hermite spline point has a different result type */
template<class T> struct MAGNUM_EXPORT TypeTraits<Math::CubicHermite<T>, T> {
typedef T(*Interpolator)(const Math::CubicHermite<T>&, const Math::CubicHermite<T>&, Float);
static Interpolator interpolator(Interpolation interpolation);
};
} }
/* Needs to be defined later so it can pick up the TypeTraits definitions */ /* Needs to be defined later so it can pick up the TypeTraits definitions */

116
src/Magnum/Animation/Test/InterpolationTest.cpp

@ -27,6 +27,8 @@
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include "Magnum/Animation/Interpolation.h" #include "Magnum/Animation/Interpolation.h"
#include "Magnum/Math/Complex.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/DualQuaternion.h"
#include "Magnum/Math/Half.h" #include "Magnum/Math/Half.h"
@ -38,8 +40,13 @@ struct InterpolationTest: TestSuite::Tester {
void interpolatorFor(); void interpolatorFor();
void interpolatorForBool(); void interpolatorForBool();
void interpolatorForBoolVector(); void interpolatorForBoolVector();
void interpolatorForComplex();
void interpolatorForQuaternion(); void interpolatorForQuaternion();
void interpolatorForDualQuaternion(); void interpolatorForDualQuaternion();
void interpolatorForCubicHermiteScalar();
void interpolatorForCubicHermiteVector();
void interpolatorForCubicHermiteComplex();
void interpolatorForCubicHermiteQuaternion();
void interpolate(); void interpolate();
void interpolateStrict(); void interpolateStrict();
@ -138,8 +145,13 @@ InterpolationTest::InterpolationTest() {
addTests({&InterpolationTest::interpolatorFor, addTests({&InterpolationTest::interpolatorFor,
&InterpolationTest::interpolatorForBool, &InterpolationTest::interpolatorForBool,
&InterpolationTest::interpolatorForBoolVector, &InterpolationTest::interpolatorForBoolVector,
&InterpolationTest::interpolatorForComplex,
&InterpolationTest::interpolatorForQuaternion, &InterpolationTest::interpolatorForQuaternion,
&InterpolationTest::interpolatorForDualQuaternion}); &InterpolationTest::interpolatorForDualQuaternion,
&InterpolationTest::interpolatorForCubicHermiteScalar,
&InterpolationTest::interpolatorForCubicHermiteVector,
&InterpolationTest::interpolatorForCubicHermiteComplex,
&InterpolationTest::interpolatorForCubicHermiteQuaternion});
addInstancedTests({&InterpolationTest::interpolate, addInstancedTests({&InterpolationTest::interpolate,
&InterpolationTest::interpolateStrict}, &InterpolationTest::interpolateStrict},
@ -175,11 +187,11 @@ void InterpolationTest::interpolatorFor() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
Animation::interpolatorFor<Float>(Interpolation::Custom); Animation::interpolatorFor<Float>(Interpolation::Spline);
Animation::interpolatorFor<Float>(Interpolation(0xde)); Animation::interpolatorFor<Float>(Interpolation(0xde));
CORRADE_COMPARE(out.str(), CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Spline\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
} }
@ -215,6 +227,26 @@ void InterpolationTest::interpolatorForBoolVector() {
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
} }
void InterpolationTest::interpolatorForComplex() {
CORRADE_COMPARE(Animation::interpolatorFor<Complex>(Interpolation::Constant)(
Complex::rotation(25.0_degf),
Complex::rotation(75.0_degf), 0.5f),
Complex::rotation(25.0_degf));
CORRADE_COMPARE(Animation::interpolatorFor<Complex>(Interpolation::Linear)(
Complex::rotation(25.0_degf),
Complex::rotation(75.0_degf), 0.5f),
Complex::rotation(50.0_degf));
std::ostringstream out;
Error redirectError{&out};
Animation::interpolatorFor<Complex>(Interpolation::Custom);
Animation::interpolatorFor<Complex>(Interpolation(0xde));
CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
}
void InterpolationTest::interpolatorForQuaternion() { void InterpolationTest::interpolatorForQuaternion() {
CORRADE_COMPARE(Animation::interpolatorFor<Quaternion>(Interpolation::Constant)( CORRADE_COMPARE(Animation::interpolatorFor<Quaternion>(Interpolation::Constant)(
Quaternion::rotation(25.0_degf, Vector3::xAxis()), Quaternion::rotation(25.0_degf, Vector3::xAxis()),
@ -227,11 +259,11 @@ void InterpolationTest::interpolatorForQuaternion() {
std::ostringstream out; std::ostringstream out;
Error redirectError{&out}; Error redirectError{&out};
Animation::interpolatorFor<Quaternion>(Interpolation::Custom); Animation::interpolatorFor<Quaternion>(Interpolation::Spline);
Animation::interpolatorFor<Quaternion>(Interpolation(0xde)); Animation::interpolatorFor<Quaternion>(Interpolation(0xde));
CORRADE_COMPARE(out.str(), CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n" "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Spline\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
} }
@ -255,6 +287,80 @@ void InterpolationTest::interpolatorForDualQuaternion() {
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n"); "Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
} }
void InterpolationTest::interpolatorForCubicHermiteScalar() {
CubicHermite1D a{2.0f, 3.0f, -1.0f};
CubicHermite1D b{5.0f, -2.0f, 1.5f};
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermite1D>(Interpolation::Constant)(a, b, 0.8f), 3.0f);
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermite1D>(Interpolation::Linear)(a, b, 0.8f), -1.0f);
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermite1D>(Interpolation::Spline)(a, b, 0.8f), -2.152f);
std::ostringstream out;
Error redirectError{&out};
Animation::interpolatorFor<CubicHermite1D>(Interpolation::Custom);
Animation::interpolatorFor<CubicHermite1D>(Interpolation(0xde));
CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
}
void InterpolationTest::interpolatorForCubicHermiteVector() {
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(Animation::interpolatorFor<CubicHermite2D>(Interpolation::Constant)(a, b, 0.8f), (Vector2{3.0f, 0.1f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermite2D>(Interpolation::Linear)(a, b, 0.8f), (Vector2{-1.0f, 0.9f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermite2D>(Interpolation::Spline)(a, b, 0.8f), (Vector2{-2.152f, 0.9576f}));
std::ostringstream out;
Error redirectError{&out};
Animation::interpolatorFor<CubicHermite2D>(Interpolation::Custom);
Animation::interpolatorFor<CubicHermite2D>(Interpolation(0xde));
CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
}
void InterpolationTest::interpolatorForCubicHermiteComplex() {
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(Animation::interpolatorFor<CubicHermiteComplex>(Interpolation::Constant)(a, b, 0.8f), (Complex{0.999445f, 0.0333148f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermiteComplex>(Interpolation::Linear)(a, b, 0.8f), (Complex{-0.78747f, 0.616353f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermiteComplex>(Interpolation::Spline)(a, b, 0.8f), (Complex{-0.95958f, 0.281435f}));
std::ostringstream out;
Error redirectError{&out};
Animation::interpolatorFor<CubicHermiteComplex>(Interpolation::Custom);
Animation::interpolatorFor<CubicHermiteComplex>(Interpolation(0xde));
CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
}
void InterpolationTest::interpolatorForCubicHermiteQuaternion() {
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(Animation::interpolatorFor<CubicHermiteQuaternion>(Interpolation::Constant)(a, b, 0.8f), (Quaternion{{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermiteQuaternion>(Interpolation::Linear)(a, b, 0.8f), (Quaternion{{-0.533196f, 0.410685f, 0.521583f}, 0.524396f}));
CORRADE_COMPARE(Animation::interpolatorFor<CubicHermiteQuaternion>(Interpolation::Spline)(a, b, 0.8f), (Quaternion{{-0.911408f, 0.23368f, 0.185318f}, 0.283524f}));
std::ostringstream out;
Error redirectError{&out};
Animation::interpolatorFor<CubicHermiteQuaternion>(Interpolation::Custom);
Animation::interpolatorFor<CubicHermiteQuaternion>(Interpolation(0xde));
CORRADE_COMPARE(out.str(),
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation::Custom\n"
"Animation::interpolatorFor(): can't deduce interpolator function for Animation::Interpolation(0xde)\n");
}
namespace { namespace {
constexpr Float Keys[]{0.0f, 2.0f, 4.0f, 5.0f}; constexpr Float Keys[]{0.0f, 2.0f, 4.0f, 5.0f};
constexpr Float Values[]{3.0f, 1.0f, 2.5f, 0.5f}; constexpr Float Values[]{3.0f, 1.0f, 2.5f, 0.5f};

4
src/Magnum/Animation/Test/PlayerTest.cpp

@ -833,6 +833,10 @@ void PlayerTest::runFor100YearsFloat() {
CORRADE_COMPARE(player.state(), State::Playing); CORRADE_COMPARE(player.state(), State::Playing);
{ {
/* Asm.js uses doubles for all floating-point calculations, so we don't
lose any precision and thus even the "run for 100 years" test passes
there. Unfortunately it's not possible to detect if this is asm.js
so the XFAIL is done like this. */
#ifndef CORRADE_TARGET_EMSCRIPTEN #ifndef CORRADE_TARGET_EMSCRIPTEN
CORRADE_EXPECT_FAIL_IF(data.failsFuzzyFloat, "Imprecision larger than 2.5e-4f."); CORRADE_EXPECT_FAIL_IF(data.failsFuzzyFloat, "Imprecision larger than 2.5e-4f.");
#else #else

34
src/Magnum/Animation/Track.h

@ -55,28 +55,18 @@ interpolator function and extrapolation behavior.
@section Animation-Track-interpolators Types and interpolators @section Animation-Track-interpolators Types and interpolators
The track supports arbitrary types for keys, values and interpolators. These The track supports arbitrary types for keys, values and interpolators. See
are common combinations: @ref transformations-interpolation for an overview of builtin interpolation
functions.
@m_class{m-fullwidth}
Besides directly specifying an interpolator function as shown in the above
Interpolation type | Value type | Result type | Interpolator snippet, it's also possible to supply a generic interpolation behavior by
------------------- | ----------------- | ------------- | ------------ passing the @ref Interpolation enum to the constructor. In case the
Constant | any `V` | `V` | @ref Math::select() interpolator function is not passed in as well, it's autodetected using
Linear | @cpp bool @ce <b></b> | @cpp bool @ce <b></b> | @ref Math::select() @ref interpolatorFor(). See its documentation for more information. The
Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() @ref Interpolation enum is then stored in @ref interpolation() and acts as a
Linear | any scalar `V` | `V` | @ref Math::lerp() hint for desired interpolation behavior for users who might want to use their
Linear | any vector `V` | `V` | @ref Math::lerp() own interpolator.
Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion<T>&, const Quaternion<T>&, T) "Math::lerp()"
Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion<T>&, const Quaternion<T>&, T) "Math::slerp()"
Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion<T>&, const DualQuaternion<T>&, T) "Math::sclerp()"
It's also possible to supply a generic interpolation behavior by passing the
@ref Interpolation enum to the constructor. In case the interpolator function
is not passed in as well, it's autodetected using @ref interpolatorFor(). See
its documentation for more information. The @ref Interpolation enum is then
stored in @ref interpolation() and acts as a hint for desired interpolation
behavior for users who might want to use their own interpolator.
@section Animation-Track-performance Performance tuning @section Animation-Track-performance Performance tuning

1
src/Magnum/CMakeLists.txt

@ -133,6 +133,7 @@ endif()
# Files shared between main library and math unit test library # Files shared between main library and math unit test library
set(MagnumMath_SRCS set(MagnumMath_SRCS
Math/Color.cpp Math/Color.cpp
Math/Half.cpp
Math/Functions.cpp Math/Functions.cpp
Math/Packing.cpp Math/Packing.cpp
Math/instantiation.cpp) Math/instantiation.cpp)

8
src/Magnum/DebugTools/BufferData.h

@ -50,8 +50,8 @@ Emulates @ref GL::Buffer::subData() call on platforms that don't support it
(such as OpenGL ES) by using @ref GL::Buffer::map(). (such as OpenGL ES) by using @ref GL::Buffer::map().
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
@requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES @requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES
2.0. 2.0.
@ -70,8 +70,8 @@ Emulates @ref GL::Buffer::data() call on platforms that don't support it (such
as OpenGL ES) by using @ref GL::Buffer::map(). as OpenGL ES) by using @ref GL::Buffer::map().
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
@requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES @requires_gles30 Extension @gl_extension{EXT,map_buffer_range} in OpenGL ES
2.0. 2.0.

8
src/Magnum/DebugTools/ForceRenderer.h

@ -47,8 +47,8 @@ namespace Magnum { namespace DebugTools {
See @ref ForceRenderer documentation for more information. See @ref ForceRenderer documentation for more information.
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by
for more information. default). See @ref building-features for more information.
*/ */
class ForceRendererOptions { class ForceRendererOptions {
public: public:
@ -109,8 +109,8 @@ new DebugTools::ForceRenderer2D(object, {0.3f, 1.5f, -0.7f}, &force, "my", debug
@endcode @endcode
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by
for more information. default). See @ref building-features for more information.
@see @ref ForceRenderer2D, @ref ForceRenderer3D, @ref ForceRendererOptions @see @ref ForceRenderer2D, @ref ForceRenderer3D, @ref ForceRendererOptions
*/ */

8
src/Magnum/DebugTools/ObjectRenderer.h

@ -46,8 +46,8 @@ namespace Magnum { namespace DebugTools {
See @ref ObjectRenderer documentation for more information. See @ref ObjectRenderer documentation for more information.
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by
for more information. default). See @ref building-features for more information.
*/ */
class ObjectRendererOptions { class ObjectRendererOptions {
public: public:
@ -91,8 +91,8 @@ new DebugTools::ObjectRenderer2D(object, "my", debugDrawables);
@endcode @endcode
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" and `WITH_SCENEGRAPH` enabled (done by
for more information. default). See @ref building-features for more information.
@see @ref ObjectRenderer2D, @ref ObjectRenderer3D, @ref ObjectRendererOptions @see @ref ObjectRenderer2D, @ref ObjectRenderer3D, @ref ObjectRendererOptions
*/ */

4
src/Magnum/DebugTools/ResourceManager.h

@ -71,8 +71,8 @@ Stores various data used by debug renderers. See @ref debug-tools for more
information. information.
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
#ifdef MAGNUM_BUILD_DEPRECATED #ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_IGNORE_DEPRECATED_PUSH

10
src/Magnum/DebugTools/ShapeRenderer.h

@ -82,8 +82,9 @@ namespace Implementation {
See @ref ShapeRenderer documentation for more information. See @ref ShapeRenderer documentation for more information.
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default) and
for more information. `WITH_SHAPES` enabled (disabled by default). See @ref building-features for
more information.
*/ */
class CORRADE_DEPRECATED("scheduled for removal, see the docs for alternatives") ShapeRendererOptions { class CORRADE_DEPRECATED("scheduled for removal, see the docs for alternatives") ShapeRendererOptions {
public: public:
@ -177,8 +178,9 @@ new DebugTools::ShapeRenderer2D(shape, "red", debugDrawables);
@endcode @endcode
@note This class is available only if Magnum is compiled with @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default) and
for more information. `WITH_SHAPES` enabled (disabled by default). See @ref building-features for
more information.
@see @ref ShapeRenderer2D, @ref ShapeRenderer3D, @ref ShapeRendererOptions @see @ref ShapeRenderer2D, @ref ShapeRenderer3D, @ref ShapeRendererOptions

32
src/Magnum/DebugTools/TextureImage.h

@ -55,8 +55,8 @@ additional shader and the @glsl floatBitsToUint() @ce GLSL function and then
reinterpreted back to @ref GL::PixelType::Float when read to client memory. reinterpreted back to @ref GL::PixelType::Float when read to client memory.
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D& image); MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D& image);
@ -70,8 +70,8 @@ Image2D image = DebugTools::textureSubImage(texture, 0, rect, {PixelFormat::RGBA
@endcode @endcode
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D&& image); MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D&& image);
@ -87,8 +87,8 @@ marked as framebuffer readable are supported; their generic
@ref Magnum::PixelFormat "PixelFormat" counterparts are supported as well. @ref Magnum::PixelFormat "PixelFormat" counterparts are supported as well.
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D& image); MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D& image);
@ -102,8 +102,8 @@ Image2D image = DebugTools::textureSubImage(texture, CubeMapCoordinate::Positive
@endcode @endcode
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D&& image); MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, Image2D&& image);
@ -124,8 +124,8 @@ marked as framebuffer readable are supported; their generic
@requires_webgl20 Pixel buffer objects are not available in WebGL 1.0. @requires_webgl20 Pixel buffer objects are not available in WebGL 1.0.
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage); MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage);
@ -139,8 +139,8 @@ BufferImage2D image = DebugTools::textureSubImage(texture, 0, rect, {PixelFormat
@endcode @endcode
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage); MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage);
@ -158,8 +158,8 @@ marked as framebuffer readable are supported; their generic
@requires_webgl20 Pixel buffer objects are not available in WebGL 1.0. @requires_webgl20 Pixel buffer objects are not available in WebGL 1.0.
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage); MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D& image, GL::BufferUsage usage);
@ -173,8 +173,8 @@ BufferImage2D image = DebugTools::textureSubImage(texture, CubeMapCoordinate::Po
@endcode @endcode
@note This function is available only if Magnum is compiled with @note This function is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
for more information. @ref building-features for more information.
*/ */
MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage); MAGNUM_DEBUGTOOLS_EXPORT GL::BufferImage2D textureSubImage(GL::CubeMapTexture& texture, GL::CubeMapCoordinate coordinate, Int level, const Range2Di& range, GL::BufferImage2D&& image, GL::BufferUsage usage);
#endif #endif

30
src/Magnum/Magnum.h

@ -456,6 +456,21 @@ typedef Math::CubicBezier2D<Float> CubicBezier2D;
/** @brief Float three-dimensional cubic Bézier curve */ /** @brief Float three-dimensional cubic Bézier curve */
typedef Math::CubicBezier3D<Float> CubicBezier3D; typedef Math::CubicBezier3D<Float> CubicBezier3D;
/** @brief Float scalar cubic Hermite spline point */
typedef Math::CubicHermite1D<Float> CubicHermite1D;
/** @brief Float two-dimensional cubic Hermite spline point */
typedef Math::CubicHermite2D<Float> CubicHermite2D;
/** @brief Float three-dimensional cubic Hermite spline point */
typedef Math::CubicHermite3D<Float> CubicHermite3D;
/** @brief Float cubic Hermite spline complex number */
typedef Math::CubicHermiteComplex<Float> CubicHermiteComplex;
/** @brief Float cubic Hermite spline quaternion */
typedef Math::CubicHermiteQuaternion<Float> CubicHermiteQuaternion;
/** @brief Float complex number */ /** @brief Float complex number */
typedef Math::Complex<Float> Complex; typedef Math::Complex<Float> Complex;
@ -641,6 +656,21 @@ typedef Math::CubicBezier2D<Float> CubicBezier2Dd;
/** @brief Double three-dimensional cubic Bézier curve */ /** @brief Double three-dimensional cubic Bézier curve */
typedef Math::CubicBezier3D<Float> CubicBezier3Dd; typedef Math::CubicBezier3D<Float> CubicBezier3Dd;
/** @brief Double scalar cubic Hermite spline point */
typedef Math::CubicHermite1D<Double> CubicHermite1Dd;
/** @brief Double two-dimensional cubic Hermite spline point */
typedef Math::CubicHermite2D<Double> CubicHermite2Dd;
/** @brief Double three-dimensional cubic Hermite spline point */
typedef Math::CubicHermite3D<Double> CubicHermite3Dd;
/** @brief Double cubic Hermite spline complex number */
typedef Math::CubicHermiteComplex<Double> CubicHermiteComplexd;
/** @brief Double cubic Hermite spline quaternion */
typedef Math::CubicHermiteQuaternion<Double> CubicHermiteQuaterniond;
/** @brief Double complex number */ /** @brief Double complex number */
typedef Math::Complex<Double> Complexd; typedef Math::Complex<Double> Complexd;

39
src/Magnum/Math/Bezier.h

@ -46,8 +46,12 @@ namespace Implementation {
@tparam dimensions Dimensions of control points @tparam dimensions Dimensions of control points
@tparam T Underlying data type @tparam T Underlying data type
Implementation of M-order N-dimensional Represents a M-order N-dimensional
[Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) segment.
Cubic Bézier curves are fully interchangeable with cubic Hermite splines, use
@ref fromCubicHermite() and @ref CubicHermite::fromBezier() for the conversion.
@see @ref QuadraticBezier, @ref CubicBezier, @ref QuadraticBezier2D, @see @ref QuadraticBezier, @ref CubicBezier, @ref QuadraticBezier2D,
@ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D @ref QuadraticBezier3D, @ref CubicBezier2D, @ref CubicBezier3D
*/ */
@ -64,6 +68,37 @@ template<UnsignedInt order, UnsignedInt dimensions, class T> class Bezier {
Dimensions = dimensions /**< Dimensions of control points */ Dimensions = dimensions /**< Dimensions of control points */
}; };
/**
* @brief Create cubic Hermite spline point from adjacent Bézier curve segments
*
* Given two cubic Hermite spline points defined by points
* @f$ \boldsymbol{p}_i @f$, in-tangents @f$ \boldsymbol{m}_i @f$ and
* out-tangents @f$ \boldsymbol{n}_i @f$, the corresponding cubic
* Bezier curve segment with points @f$ \boldsymbol{c}_0 @f$,
* @f$ \boldsymbol{c}_1 @f$, @f$ \boldsymbol{c}_2 @f$ and
* @f$ \boldsymbol{c}_3 @f$ is defined as: @f[
* \begin{array}{rcl}
* \boldsymbol{c}_0 & = & \boldsymbol{p}_a \\
* \boldsymbol{c}_1 & = & \frac{1}{3} \boldsymbol{n}_a - \boldsymbol{p}_a \\
* \boldsymbol{c}_2 & = & \boldsymbol{p}_b - \frac{1}{3} \boldsymbol{m}_b \\
* \boldsymbol{c}_3 & = & \boldsymbol{p}_b
* \end{array}
* @f]
*
* Enabled only on @ref CubicBezier for @ref CubicHermite with vector
* underlying types. See @ref CubicHermite::fromBezier() for the
* inverse operation.
*/
template<class VectorType> static
#ifndef DOXYGEN_GENERATING_OUTPUT
typename std::enable_if<std::is_base_of<Vector<dimensions, T>, VectorType>::value && order == 3, Bezier<order, dimensions, T>>::type
#else
Bezier<order, dimensions, T>
#endif
fromCubicHermite(const CubicHermite<VectorType>& a, const CubicHermite<VectorType>& b) {
return {a.point(), a.outTangent()/T(3) - a.point(), b.point() - b.inTangent()/T(3), b.point()};
}
/** /**
* @brief Default constructor * @brief Default constructor
* *

1
src/Magnum/Math/CMakeLists.txt

@ -30,6 +30,7 @@ set(MagnumMath_HEADERS
Color.h Color.h
Complex.h Complex.h
Constants.h Constants.h
CubicHermite.h
Distance.h Distance.h
Dual.h Dual.h
DualComplex.h DualComplex.h

73
src/Magnum/Math/Complex.h

@ -63,7 +63,7 @@ template<class T> inline T dot(const Complex<T>& a, const Complex<T>& b) {
@brief Angle between normalized complex numbers @brief Angle between normalized complex numbers
Expects that both complex numbers are normalized. @f[ Expects that both complex numbers are normalized. @f[
\theta = acos \left( \frac{Re(c_0 \cdot c_1))}{|c_0| |c_1|} \right) = acos (a_0 a_1 + b_0 b_1) \theta = \arccos \left( \frac{Re(c_0 \cdot c_1))}{|c_0| |c_1|} \right) = \arccos (a_0 a_1 + b_0 b_1)
@f] @f]
@see @ref Complex::isNormalized(), @see @ref Complex::isNormalized(),
@ref angle(const Quaternion<T>&, const Quaternion<T>&), @ref angle(const Quaternion<T>&, const Quaternion<T>&),
@ -72,14 +72,20 @@ Expects that both complex numbers are normalized. @f[
template<class T> inline Rad<T> angle(const Complex<T>& normalizedA, const Complex<T>& normalizedB) { template<class T> inline Rad<T> angle(const Complex<T>& normalizedA, const Complex<T>& normalizedB) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::angle(): complex numbers must be normalized", {}); "Math::angle(): complex numbers must be normalized", {});
return Rad<T>(std::acos(normalizedA.real()*normalizedB.real() + normalizedA.imaginary()*normalizedB.imaginary())); return Rad<T>(std::acos(dot(normalizedA, normalizedB)));
} }
/** /**
@brief Complex number @brief Complex number
@tparam T Data type @tparam T Data type
Represents 2D rotation. See @ref transformations for brief introduction. Represents 2D rotation. Usually denoted as the following in equations, with
@f$ a_0 @f$ being the @ref real() part and @f$ a_i @f$ the @ref imaginary()
part: @f[
c = a_0 + i a_i
@f]
See @ref transformations for brief introduction.
@see @ref Magnum::Complex, @ref Magnum::Complexd, @ref Matrix3 @see @ref Magnum::Complex, @ref Magnum::Complexd, @ref Matrix3
*/ */
template<class T> class Complex { template<class T> class Complex {
@ -191,11 +197,13 @@ template<class T> class Complex {
return Implementation::isNormalizedSquared(dot()); return Implementation::isNormalizedSquared(dot());
} }
/** @brief Real part */ /** @brief Real part (@f$ a_0 @f$) */
constexpr T real() const { return _real; } T& real() { return _real; }
constexpr T real() const { return _real; } /**< @overload */
/** @brief Imaginary part */ /** @brief Imaginary part (@f$ a_i @f$) */
constexpr T imaginary() const { return _imaginary; } T& imaginary() { return _imaginary; }
constexpr T imaginary() const { return _imaginary; } /**< @overload */
/** /**
* @brief Convert complex number to vector * @brief Convert complex number to vector
@ -456,6 +464,57 @@ template<class T> inline Complex<T> operator/(T scalar, const Complex<T>& comple
return {scalar/complex.real(), scalar/complex.imaginary()}; return {scalar/complex.real(), scalar/complex.imaginary()};
} }
/** @relatesalso Complex
@brief Linear interpolation of two complex numbers
@param normalizedA First complex number
@param normalizedB Second complex number
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both complex numbers are normalized. @f[
c_{LERP} = \frac{(1 - t) c_A + t c_B}{|(1 - t) c_A + t c_B|}
@f]
@see @ref Complex::isNormalized(), @ref slerp(const Complex<T>&, const Complex<T>&, T),
@ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref lerp(const T&, const T&, U),
@ref lerp(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T),
@ref lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
*/
template<class T> inline Complex<T> lerp(const Complex<T>& normalizedA, const Complex<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::lerp(): complex numbers must be normalized", {});
return ((T(1) - t)*normalizedA + t*normalizedB).normalized();
}
/** @relatesalso Complex
@brief Spherical linear interpolation of two complex numbers
@param normalizedA First complex number
@param normalizedB Second complex number
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both complex numbers are normalized. If the complex numbers are
the same, returns the first argument. @f[
\begin{array}{rcl}
\theta & = & \arccos \left( \frac{c_A \cdot c_B}{|c_A| |c_B|} \right) = \arccos(c_A \cdot c_B) \\[6pt]
c_{SLERP} & = & \cfrac{\sin((1 - t) \theta) c_A + \sin(t \theta) c_B}{\sin(\theta)}
\end{array}
@f]
@see @ref Complex::isNormalized(), @ref lerp(const Complex<T>&, const Complex<T>&, T),
@ref slerp(const Quaternion<T>&, const Quaternion<T>&, T)
*/
template<class T> inline Complex<T> slerp(const Complex<T>& normalizedA, const Complex<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::slerp(): complex numbers must be normalized", {});
const T cosAngle = dot(normalizedA, normalizedB);
/* Avoid division by zero */
if(std::abs(cosAngle) >= T(1)) return Complex<T>{normalizedA};
/** @todo couldn't this be done somewhat simpler? */
const T a = std::acos(cosAngle);
return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a);
}
/** @debugoperator{Complex} */ /** @debugoperator{Complex} */
template<class T> Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const Complex<T>& value) { template<class T> Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const Complex<T>& value) {
return debug << "Complex(" << Corrade::Utility::Debug::nospace return debug << "Complex(" << Corrade::Utility::Debug::nospace

465
src/Magnum/Math/CubicHermite.h

@ -0,0 +1,465 @@
#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š <mosra@centrum.cz>
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.
Cubic Hermite splines are fully interchangeable with cubic Bézier curves, use
@ref fromBezier() and @ref Bezier::fromCubicHermite() for the conversion.
@see @ref CubicHermite2D, @ref CubicHermite3D,
@ref Magnum::CubicHermite2D, @ref Magnum::CubicHermite2Dd,
@ref Magnum::CubicHermite3D, @ref Magnum::CubicHermite3Dd,
@ref CubicBezier
@experimental
*/
template<class T> class CubicHermite {
public:
typedef T Type; /**< @brief Underlying data type */
/**
* @brief Create cubic Hermite spline point from adjacent Bézier curve segments
*
* Given two adjacent cubic Bézier curve segments defined by points
* @f$ \boldsymbol{a}_i @f$ and @f$ \boldsymbol{b}_i @f$,
* @f$ i \in \{ 0, 1, 2, 3 \} @f$, the corresponding cubic Hermite
* spline point @f$ \boldsymbol{p} @f$, in-tangent @f$ \boldsymbol{m} @f$
* and out-tangent @f$ \boldsymbol{n} @f$ is defined as: @f[
* \begin{array}{rcl}
* \boldsymbol{m} & = & 3 (\boldsymbol{a}_3 - \boldsymbol{a}_2)
* = 3 (\boldsymbol{b}_0 - \boldsymbol{a}_2) \\
* \boldsymbol{p} & = & \boldsymbol{a}_3 = \boldsymbol{b}_0 \\
* \boldsymbol{n} & = & 3 (\boldsymbol{b}_1 - \boldsymbol{a}_3)
* = 3 (\boldsymbol{b}_1 - \boldsymbol{b}_0)
* \end{array}
* @f]
*
* Expects that the two segments are adjacent (i.e., the endpoint of
* first segment is the start point of the second). If you need to
* create a cubic Hermite spline point that's at the beginning or at
* the end of a curve, simply pass a dummy Bézier segment that
* satisfies this constraint as the other parameter:
*
* @snippet MagnumMath.cpp CubicHermite-fromBezier
*
* Enabled only on vector underlying types. See
* @ref Bezier::fromCubicHermite() for the inverse operation.
*/
template<UnsignedInt dimensions, class U> static
#ifdef DOXYGEN_GENERATING_OUTPUT
CubicHermite<T>
#else
typename std::enable_if<std::is_base_of<Vector<dimensions, U>, T>::value, CubicHermite<T>>::type
#endif
fromBezier(const CubicBezier<dimensions, U>& a, const CubicBezier<dimensions, U>& b) {
return CORRADE_CONSTEXPR_ASSERT(a[3] == b[0],
"Math::CubicHermite::fromBezier(): segments are not adjacent"),
CubicHermite<T>{3*(a[3] - a[2]), a[3], 3*(b[1] - a[3])};
}
/**
* @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<std::is_constructible<T, IdentityInitT>::value, IdentityInitT, ZeroInitT>::type{typename std::conditional<std::is_constructible<T, IdentityInitT>::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<std::is_constructible<T, ZeroInitT>::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<class U = T, class = typename std::enable_if<std::is_constructible<U, IdentityInitT>::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<std::is_constructible<T, NoInitT>::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<class U> constexpr explicit CubicHermite(const CubicHermite<U>& other) noexcept: _inTangent{T(other._inTangent)}, _point{T(other._point)}, _outTangent{T(other._outTangent)} {}
/** @brief Equality comparison */
bool operator==(const CubicHermite<T>& other) const;
/** @brief Non-equality comparison */
bool operator!=(const CubicHermite<T>& 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<class> 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<T> @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<class T> using CubicHermite1D = CubicHermite<T>;
#endif
/**
@brief Two-dimensional cubic Hermite spline point
Convenience alternative to @cpp CubicHermite<Vector2<T>> @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<class T> using CubicHermite2D = CubicHermite<Vector2<T>>;
#endif
/**
@brief Three-dimensional cubic Hermite spline point
Convenience alternative to @cpp CubicHermite<Vector2<T>> @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<class T> using CubicHermite3D = CubicHermite<Vector3<T>>;
#endif
/**
@brief Cubic Hermite spline complex number
Convenience alternative to @cpp CubicHermite<Complex<T>> @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<class T> using CubicHermiteComplex = CubicHermite<Complex<T>>;
#endif
/**
@brief Cubic Hermite spline quaternion
Convenience alternative to @cpp CubicHermite<Quaternion<T>> @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<class T> using CubicHermiteQuaternion = CubicHermite<Quaternion<T>>;
#endif
/** @debugoperator{CubicHermite} */
template<class T> Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, const CubicHermite<T>& 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<Float>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Double>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector2<Float>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector3<Float>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector4<Float>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector2<Double>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector3<Double>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector4<Double>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Complex<Float>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Complex<Double>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Quaternion<Float>>&);
extern template MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Quaternion<Double>>&);
#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<T>&, const CubicHermite<T>&, U),
@ref splerp(const CubicHermite<T>&, const CubicHermite<T>&, U)
*/
template<class T, class U> T select(const CubicHermite<T>& a, const CubicHermite<T>& 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<T>&, const CubicHermiteComplex<T>&, T),
@ref lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref splerp(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref splerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T),
@ref splerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
*/
template<class T, class U> T lerp(const CubicHermite<T>& a, const CubicHermite<T>& 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<T>&, const CubicHermite<T>&, 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<T>&, const Complex<T>&, 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<T>&, const CubicHermiteQuaternion<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref splerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T)
*/
template<class T> Complex<T> lerp(const CubicHermiteComplex<T>& a, const CubicHermiteComplex<T>& 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<T>&, const CubicHermite<T>&, 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<T>&, const Quaternion<T>&, 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<T>&, const CubicHermiteComplex<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref splerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
*/
template<class T> Quaternion<T> lerp(const CubicHermiteQuaternion<T>& a, const CubicHermiteQuaternion<T>& 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<T>&, const CubicHermiteComplex<T>&, T),
@ref splerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermite<T>&, const CubicHermite<T>&, U),
*/
template<class T, class U> T splerp(const CubicHermite<T>& a, const CubicHermite<T>& 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<T>&, const CubicHermite<T>&, 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<T>&, const CubicHermiteQuaternion<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T)
*/
template<class T> Complex<T> splerp(const CubicHermiteComplex<T>& a, const CubicHermiteComplex<T>& 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<T>&, const CubicHermite<T>&, 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<T>&, const CubicHermiteComplex<T>&, T),
@ref select(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
*/
template<class T> Quaternion<T> splerp(const CubicHermiteQuaternion<T>& a, const CubicHermiteQuaternion<T>& 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<class T> inline bool CubicHermite<T>::operator==(const CubicHermite<T>& other) const {
/* For non-scalar types default implementation of TypeTraits would be used,
which is just operator== */
return TypeTraits<T>::equals(_inTangent, other._inTangent) &&
TypeTraits<T>::equals(_point, other._point) &&
TypeTraits<T>::equals(_outTangent, other._outTangent);
}
}}
#endif

17
src/Magnum/Math/Dual.h

@ -45,6 +45,11 @@ namespace Implementation {
/** /**
@brief Dual number @brief Dual number
@tparam T Underlying data type @tparam T Underlying data type
Usually denoted as the following in equations, with @f$ a_0 @f$ being the
@ref real() part and @f$ a_\epsilon @f$ the @ref dual() part: @f[
\hat a = a_0 + \epsilon a_\epsilon
@f]
*/ */
template<class T> class Dual { template<class T> class Dual {
template<class> friend class Dual; template<class> friend class Dual;
@ -121,13 +126,17 @@ template<class T> class Dual {
return !operator==(other); return !operator==(other);
} }
/** @brief Real part */ /** @brief Real part (@f$ a_0 @f$) */
T& real() { return _real; } T& real() { return _real; }
constexpr T real() const { return _real; } /**< @overload */ /* Returning const so it's possible to call constexpr functions on the
result. WTF, C++?! */
constexpr const T real() const { return _real; } /**< @overload */
/** @brief Dual part */ /** @brief Dual part (@f$ a_\epsilon @f$) */
T& dual() { return _dual; } T& dual() { return _dual; }
constexpr T dual() const { return _dual; } /**< @overload */ /* Returning const so it's possible to call constexpr functions on the
result. WTF, C++?! */
constexpr const T dual() const { return _dual; } /**< @overload */
/** /**
* @brief Add and assign dual number * @brief Add and assign dual number

10
src/Magnum/Math/DualComplex.h

@ -43,8 +43,14 @@ namespace Implementation {
@brief Dual complex number @brief Dual complex number
@tparam T Underlying data type @tparam T Underlying data type
Represents 2D rotation and translation. See @ref transformations for brief Represents 2D rotation and translation. Usually denoted as the following in
introduction. equations, with @f$ q_0 @f$ being the @ref real() part and @f$ q_\epsilon @f$
the @ref dual() part: @f[
\hat q = q_0 + \epsilon q_\epsilon
@f]
See @ref Dual and @ref Complex for further notation description and
@ref transformations for brief introduction.
@see @ref Magnum::DualComplex, @ref Magnum::DualComplexd, @ref Dual, @see @ref Magnum::DualComplex, @ref Magnum::DualComplexd, @ref Dual,
@ref Complex, @ref Matrix3 @ref Complex, @ref Matrix3
@todo Can this be done similarly as in dual quaternions? It sort of works, but @todo Can this be done similarly as in dual quaternions? It sort of works, but

123
src/Magnum/Math/DualQuaternion.h

@ -48,33 +48,114 @@ namespace Implementation {
@param normalizedB Second dual quaternion @param normalizedB Second dual quaternion
@param t Interpolation phase (from range @f$ [0; 1] @f$) @param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both dual quaternions are normalized. @f[ Expects that both dual quaternions are normalized. If the real parts are the
same or one is a negation of the other, returns the @ref DualQuaternion::rotation()
(real) part combined with interpolated @ref DualQuaternion::translation(): @f[
\begin{array}{rcl} \begin{array}{rcl}
l + \epsilon m & = & \hat q_A^* \hat q_B \\ d & = & q_{A_0} \cdot q_{B_0} \\[5pt]
\frac{\hat a} 2 & = & acos \left( l_S \right) - \epsilon m_S \frac 1 {|l_V|} \\ {\hat q}_{ScLERP} & = & 2 \left[(1 - t)(q_{A_\epsilon} q_{A_0}^*)_V + t (q_{B_\epsilon} q_{B_0}^*)_V \right] q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1}
\hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon \end{array}
~~~~~~~~ \boldsymbol n_0 = l_V \frac 1 {|l_V|} @f]
~~~~~~~~ \boldsymbol n_\epsilon = \left( m_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|l_V|} \\
@m_class{m-noindent}
otherwise, the interpolation is performed as: @f[
\begin{array}{rcl}
l + \epsilon m & = & \hat q_A^* \hat q_B \\[5pt]
\frac{\hat a} 2 & = & \arccos \left( l_S \right) - \epsilon m_S \frac 1 {|\boldsymbol{l}_V|} \\[5pt]
\hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon,
~~~~~~~~ \boldsymbol n_0 = \boldsymbol{l}_V \frac 1 {|\boldsymbol{l}_V|},
~~~~~~~~ \boldsymbol n_\epsilon = \left(\boldsymbol{m}_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|\boldsymbol{l}_V|} \\[5pt]
{\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t = {\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t =
\hat q_A \left[ \hat {\boldsymbol n} sin \left( t \frac {\hat a} 2 \right), cos \left( t \frac {\hat a} 2 \right) \right] \\ \hat q_A \left[ \hat {\boldsymbol n} \sin \left( t \frac {\hat a} 2 \right), \cos \left( t \frac {\hat a} 2 \right) \right]
\end{array} \end{array}
@f] @f]
@see @ref DualQuaternion::isNormalized(),
@ref slerp(const Quaternion<T>&, const Quaternion<T>&, T), Note that this function does not check for shortest path interpolation, see
@ref lerp(const T&, const T&, U) @ref sclerpShortestPath() for an alternative.
@see @ref DualQuaternion::isNormalized(), @ref DualQuaternion::quaternionConjugated(),
@ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref slerp(const Quaternion<T>&, const Quaternion<T>&, T)
*/ */
template<class T> inline DualQuaternion<T> sclerp(const DualQuaternion<T>& normalizedA, const DualQuaternion<T>& normalizedB, const T t) { template<class T> inline DualQuaternion<T> sclerp(const DualQuaternion<T>& normalizedA, const DualQuaternion<T>& normalizedB, const T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::sclerp(): dual quaternions must be normalized", {}); "Math::sclerp(): dual quaternions must be normalized", {});
const T dotResult = dot(normalizedA.real().vector(), normalizedB.real().vector()); const T cosHalfAngle = dot(normalizedA.real(), normalizedB.real());
/* Avoid division by zero: interpolate just the translation part */
/** @todo could this be optimized somehow? */
if(std::abs(cosHalfAngle) >= T(1) - TypeTraits<T>::epsilon())
return DualQuaternion<T>::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion<T>{normalizedA.real()};
/* l + εm = q_A^**q_B */
const DualQuaternion<T> diff = normalizedA.quaternionConjugated()*normalizedB;
const Quaternion<T>& l = diff.real();
const Quaternion<T>& m = diff.dual();
/* a/2 = acos(l_S) - εm_S/|l_V| */
const T invr = l.vector().lengthInverted();
const Dual<T> aHalf{std::acos(l.scalar()), -m.scalar()*invr};
/* direction = n_0 = l_V/|l_V|
moment = n_ε = (m_V - n_0*(a_ε/2)*l_S)/|l_V| */
const Vector3<T> direction = l.vector()*invr;
const Vector3<T> moment = (m.vector() - direction*(aHalf.dual()*l.scalar()))*invr;
const Dual<Vector3<T>> n{direction, moment};
/* q_ScLERP = q_A*(cos(t*a/2) + n*sin(t*a/2)) */
Dual<T> sin, cos;
std::tie(sin, cos) = Math::sincos(t*Dual<Rad<T>>(aHalf));
return normalizedA*DualQuaternion<T>{n*sin, cos};
}
/** @relatesalso DualQuaternion
@brief Screw linear shortest-path interpolation of two dual quaternions
@param normalizedA First dual quaternion
@param normalizedB Second dual quaternion
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Unlike @ref sclerp(const DualQuaternion<T>&, const DualQuaternion<T>&, T) this
function interpolates on the shortest path. Expects that both dual quaternions
are normalized. If the real parts are the same or one is a negation of the
other, returns the @ref DualQuaternion::rotation() (real) part combined with
interpolated @ref DualQuaternion::translation(): @f[
\begin{array}{rcl}
d & = & q_{A_0} \cdot q_{B_0} \\[5pt]
{\hat q}_{ScLERP} & = & 2 \left((1 - t)(q_{A_\epsilon} q_{A_0}^*)_V + t (q_{B_\epsilon} q_{B_0}^*)_V \right) (q_{A_0} + \epsilon [\boldsymbol{0}, 0]), ~ {\color{m-primary} \text{if} ~ d \ge 1}
\end{array}
@f]
@m_class{m-noindent}
otherwise, the interpolation is performed as: @f[
\begin{array}{rcl}
l + \epsilon m & = & \begin{cases}
\phantom{-}\hat q_A^* \hat q_B, & d \ge 0 \\
-\hat q_A^* \hat q_B, & d < 0 \\
\end{cases} \\[15pt]
\frac{\hat a} 2 & = & \arccos \left( l_S \right) - \epsilon m_S \frac 1 {|\boldsymbol{l}_V|} \\[5pt]
\hat {\boldsymbol n} & = & \boldsymbol n_0 + \epsilon \boldsymbol n_\epsilon,
~~~~~~~~ \boldsymbol n_0 = \boldsymbol{l}_V \frac 1 {|\boldsymbol{l}_V|},
~~~~~~~~ \boldsymbol n_\epsilon = \left(\boldsymbol{m}_V - {\boldsymbol n}_0 \frac {a_\epsilon} 2 l_S \right)\frac 1 {|\boldsymbol{l}_V|} \\[5pt]
{\hat q}_{ScLERP} & = & \hat q_A (\hat q_A^* \hat q_B)^t =
\hat q_A \left[ \hat {\boldsymbol n} \sin \left( t \frac {\hat a} 2 \right), \cos \left( t \frac {\hat a} 2 \right) \right]
\end{array}
@f]
@see @ref DualQuaternion::isNormalized(), @ref lerpShortestPath(),
@ref slerpShortestPath()
*/
template<class T> inline DualQuaternion<T> sclerpShortestPath(const DualQuaternion<T>& normalizedA, const DualQuaternion<T>& normalizedB, const T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::sclerp(): dual quaternions must be normalized", {});
const T cosHalfAngle = dot(normalizedA.real(), normalizedB.real());
/* Avoid division by zero */ /* Avoid division by zero: interpolate just the translation part */
const T cosHalfAngle = dotResult + normalizedA.real().scalar()*normalizedB.real().scalar(); /** @todo could this be optimized somehow? */
if(std::abs(cosHalfAngle) >= T(1)) if(std::abs(cosHalfAngle) >= T(1) - TypeTraits<T>::epsilon())
return {normalizedA.real(), {Implementation::lerp(normalizedA.dual().vector(), normalizedB.dual().vector(), t), T(0)}}; return DualQuaternion<T>::translation(Implementation::lerp(normalizedA.translation(), normalizedB.translation(), t))*DualQuaternion<T>{normalizedA.real()};
/* l + εm = q_A^**q_B, multiplying with -1 ensures shortest path when dot < 0 */ /* l + εm = q_A^**q_B, multiplying with -1 ensures shortest path when dot < 0 */
const DualQuaternion<T> diff = normalizedA.quaternionConjugated()*(dotResult < T(0) ? -normalizedB : normalizedB); const DualQuaternion<T> diff = normalizedA.quaternionConjugated()*(cosHalfAngle < T(0) ? -normalizedB : normalizedB);
const Quaternion<T>& l = diff.real(); const Quaternion<T>& l = diff.real();
const Quaternion<T>& m = diff.dual(); const Quaternion<T>& m = diff.dual();
@ -98,8 +179,14 @@ template<class T> inline DualQuaternion<T> sclerp(const DualQuaternion<T>& norma
@brief Dual quaternion @brief Dual quaternion
@tparam T Underlying data type @tparam T Underlying data type
Represents 3D rotation and translation. See @ref transformations for brief Represents 3D rotation and translation. Usually denoted as the following in
introduction. equations, with @f$ q_0 @f$ being the @ref real() part and @f$ q_\epsilon @f$
the @ref dual() part: @f[
\hat q = q_0 + \epsilon q_\epsilon
@f]
See @ref Dual and @ref Quaternion for further notation description and
@ref transformations for a brief introduction.
@see @ref Magnum::DualQuaternion, @ref Magnum::DualQuaterniond, @ref Dual, @see @ref Magnum::DualQuaternion, @ref Magnum::DualQuaterniond, @ref Dual,
@ref Quaternion, @ref Matrix4 @ref Quaternion, @ref Matrix4
*/ */

9
src/Magnum/Math/Functions.h

@ -559,8 +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} \boldsymbol{v_{LERP}} = (1 - t) \boldsymbol{v_A} + t \boldsymbol{v_B}
@f] @f]
See @ref select() for constant interpolation using the same API. See @ref select() for constant interpolation using the same API and
@see @ref lerpInverted(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T) @ref splerp() for spline interpolation.
@see @ref lerpInverted(), @ref lerp(const Complex<T>&, const Complex<T>&, T),
@ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref lerp(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T),
@ref lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
@m_keyword{mix(),GLSL mix(),} @m_keyword{mix(),GLSL mix(),}
*/ */
template<class T, class U> inline template<class T, class U> inline

41
src/Magnum/Math/Half.cpp

@ -0,0 +1,41 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
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 "Magnum/Math/Half.h"
#include <iomanip>
#include <sstream>
namespace Magnum { namespace Math {
Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value) {
std::ostringstream out;
/* Wikipedia says it's 3 or 4 decimal places:
https://en.wikipedia.org/wiki/Half-precision_floating-point_format */
out << std::setprecision(4) << Float(value);
return debug << out.str();
}
}}

14
src/Magnum/Math/Half.h

@ -150,10 +150,16 @@ inline Half operator "" _h(long double value) { return Half(Float(value)); }
} }
/** @debugoperator{Half} */ /**
inline Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value) { @debugoperator{Half}
return debug << Float(value);
} Prints the value with 4 significant digits.
@see @ref Corrade::Utility::Debug::operator<<(float),
@ref Corrade::Utility::Debug::operator<<(double),
@ref Corrade::Utility::Debug::operator<<(long double value)
@todoc remove `long double value` once doxygen can link to long double overloads properly
*/
MAGNUM_EXPORT Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value);
}} }}

23
src/Magnum/Math/Math.h

@ -43,14 +43,6 @@ template<std::size_t> class BoolVector;
/* Class Constants used only statically */ /* Class Constants used only statically */
template<UnsignedInt, UnsignedInt, class> class Bezier;
template<UnsignedInt dimensions, class T> using QuadraticBezier = Bezier<2, dimensions, T>;
template<UnsignedInt dimensions, class T> using CubicBezier = Bezier<3, dimensions, T>;
template<class T> using QuadraticBezier2D = QuadraticBezier<2, T>;
template<class T> using QuadraticBezier3D = QuadraticBezier<3, T>;
template<class T> using CubicBezier2D = CubicBezier<2, T>;
template<class T> using CubicBezier3D = CubicBezier<3, T>;
template<class> class Complex; template<class> class Complex;
template<class> class Dual; template<class> class Dual;
template<class> class DualComplex; template<class> class DualComplex;
@ -90,6 +82,21 @@ template<class> class Vector4;
template<class> class Color3; template<class> class Color3;
template<class> class Color4; template<class> class Color4;
template<UnsignedInt, UnsignedInt, class> class Bezier;
template<UnsignedInt dimensions, class T> using QuadraticBezier = Bezier<2, dimensions, T>;
template<UnsignedInt dimensions, class T> using CubicBezier = Bezier<3, dimensions, T>;
template<class T> using QuadraticBezier2D = QuadraticBezier<2, T>;
template<class T> using QuadraticBezier3D = QuadraticBezier<3, T>;
template<class T> using CubicBezier2D = CubicBezier<2, T>;
template<class T> using CubicBezier3D = CubicBezier<3, T>;
template<class> class CubicHermite;
template<class T> using CubicHermite1D = CubicHermite<T>;
template<class T> using CubicHermite2D = CubicHermite<Vector2<T>>;
template<class T> using CubicHermite3D = CubicHermite<Vector3<T>>;
template<class T> using CubicHermiteComplex = CubicHermite<Complex<T>>;
template<class T> using CubicHermiteQuaternion = CubicHermite<Quaternion<T>>;
template<UnsignedInt, class> class Range; template<UnsignedInt, class> class Range;
template<class T> using Range1D = Range<1, T>; template<class T> using Range1D = Range<1, T>;
template<class> class Range2D; template<class> class Range2D;

133
src/Magnum/Math/Quaternion.h

@ -66,7 +66,7 @@ namespace Implementation {
@brief Angle between normalized quaternions @brief Angle between normalized quaternions
Expects that both quaternions are normalized. @f[ Expects that both quaternions are normalized. @f[
\theta = acos \left( \frac{p \cdot q}{|p| |q|} \right) = acos(p \cdot q) \theta = \arccos \left( \frac{p \cdot q}{|p| |q|} \right) = \arccos(p \cdot q)
@f] @f]
@see @ref Quaternion::isNormalized(), @see @ref Quaternion::isNormalized(),
@ref angle(const Complex<T>&, const Complex<T>&), @ref angle(const Complex<T>&, const Complex<T>&),
@ -87,8 +87,16 @@ template<class T> inline Rad<T> angle(const Quaternion<T>& normalizedA, const Qu
Expects that both quaternions are normalized. @f[ Expects that both quaternions are normalized. @f[
q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|} q_{LERP} = \frac{(1 - t) q_A + t q_B}{|(1 - t) q_A + t q_B|}
@f] @f]
@see @ref Quaternion::isNormalized(), @ref slerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref lerp(const T&, const T&, U), @ref sclerp() Note that this function does not check for shortest path interpolation, see
@ref lerpShortestPath() for an alternative.
@see @ref Quaternion::isNormalized(),
@ref slerp(const Quaternion<T>&, const Quaternion<T>&, T), @ref sclerp(),
@ref lerp(const T&, const T&, U),
@ref lerp(const Complex<T>&, const Complex<T>&, T),
@ref lerp(const CubicHermite<T>&, const CubicHermite<T>&, U),
@ref lerp(const CubicHermiteComplex<T>&, const CubicHermiteComplex<T>&, T),
@ref lerp(const CubicHermiteQuaternion<T>&, const CubicHermiteQuaternion<T>&, T)
*/ */
template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) { template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
@ -96,6 +104,31 @@ template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, co
return ((T(1) - t)*normalizedA + t*normalizedB).normalized(); return ((T(1) - t)*normalizedA + t*normalizedB).normalized();
} }
/** @relatesalso Quaternion
@brief Linear shortest-path interpolation of two quaternions
@param normalizedA First quaternion
@param normalizedB Second quaternion
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Unlike @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T), this
interpolates on the shortest path at some performance expense. Expects that
both quaternions are normalized. @f[
\begin{array}{rcl}
d & = & q_A \cdot q_B \\[5pt]
q'_A & = & \begin{cases}
\phantom{-}q_A, & d \ge 0 \\
-q_A, & d < 0
\end{cases} \\[15pt]
q_{LERP} & = & \cfrac{(1 - t) q'_A + t q_B}{|(1 - t) q'_A + t q_B|}
\end{array}
@f]
@see @ref Quaternion::isNormalized(), @ref slerpShortestPath(),
@ref sclerpShortestPath()
*/
template<class T> inline Quaternion<T> lerpShortestPath(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
return lerp(dot(normalizedA, normalizedB) < T(0) ? -normalizedA : normalizedA, normalizedB, t);
}
/** @relatesalso Quaternion /** @relatesalso Quaternion
@brief Spherical linear interpolation of two quaternions @brief Spherical linear interpolation of two quaternions
@param normalizedA First quaternion @param normalizedA First quaternion
@ -103,13 +136,26 @@ template<class T> inline Quaternion<T> lerp(const Quaternion<T>& normalizedA, co
@param t Interpolation phase (from range @f$ [0; 1] @f$) @param t Interpolation phase (from range @f$ [0; 1] @f$)
Expects that both quaternions are normalized. If the quaternions are the same Expects that both quaternions are normalized. If the quaternions are the same
or one is a negation of the other, returns the first argument. @f[ or one is a negation of the other, it just returns the first argument: @f[
q_{SLERP} = \frac{sin((1 - t) \theta) q_A + sin(t \theta) q_B}{sin \theta} \begin{array}{rcl}
~ ~ ~ ~ ~ ~ ~ d & = & q_A \cdot q_B \\[5pt]
\theta = acos \left( \frac{q_A \cdot q_B}{|q_A| \cdot |q_B|} \right) = acos(q_A \cdot q_B) q_{SLERP} & = & q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1}
\end{array}
@f]
@m_class{m-noindent}
otherwise, the interpolation is performed as: @f[
\begin{array}{rcl}
\theta & = & \arccos \left( \frac{q_A \cdot q_B}{|q_A| |q_B|} \right) = \arccos(q_A \cdot q_B) = \arccos(d) \\[5pt]
q_{SLERP} & = & \cfrac{\sin((1 - t) \theta) q_A + \sin(t \theta) q_B}{\sin(\theta)}
\end{array}
@f] @f]
Note that this function does not check for shortest path interpolation, see
@ref slerpShortestPath() for an alternative.
@see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T), @see @ref Quaternion::isNormalized(), @ref lerp(const Quaternion<T>&, const Quaternion<T>&, T),
@ref sclerp() @ref slerp(const Complex<T>&, const Complex<T>&, T), @ref sclerp()
*/ */
template<class T> inline Quaternion<T> slerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) { template<class T> inline Quaternion<T> slerp(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(), CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
@ -117,17 +163,70 @@ template<class T> inline Quaternion<T> slerp(const Quaternion<T>& normalizedA, c
const T cosHalfAngle = dot(normalizedA, normalizedB); const T cosHalfAngle = dot(normalizedA, normalizedB);
/* Avoid division by zero */ /* Avoid division by zero */
if(std::abs(cosHalfAngle) >= T(1)) return Quaternion<T>{normalizedA}; if(std::abs(cosHalfAngle) >= T(1) - TypeTraits<T>::epsilon())
return normalizedA;
const T a = std::acos(cosHalfAngle); const T a = std::acos(cosHalfAngle);
return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a); return (std::sin((T(1) - t)*a)*normalizedA + std::sin(t*a)*normalizedB)/std::sin(a);
} }
/** @relatesalso Quaternion
@brief Spherical linear shortest-path interpolation of two quaternions
@param normalizedA First quaternion
@param normalizedB Second quaternion
@param t Interpolation phase (from range @f$ [0; 1] @f$)
Unlike @ref slerp(const Quaternion<T>&, const Quaternion<T>&, T) this function
interpolates on the shortest path. Expects that both quaternions are
normalized. If the quaternions are the same or one is a negation of the other,
it just returns the first argument: @f[
\begin{array}{rcl}
d & = & q_A \cdot q_B \\
q_{SLERP} & = & q_A, ~ {\color{m-primary} \text{if} ~ d \ge 1}
\end{array}
@f]
@m_class{m-noindent}
otherwise, the interpolation is performed as: @f[
\begin{array}{rcl}
q'_A & = & \begin{cases}
\phantom{-}q_A, & d \ge 0 \\
-q_A, & d < 0
\end{cases} \\[15pt]
\theta & = & \arccos \left( \frac{|q'_A \cdot q_B|}{|q'_A| |q_B|} \right) = \arccos(|q'_A \cdot q_B|) = \arccos(|d|) \\[5pt]
q_{SLERP} & = & \cfrac{\sin((1 - t) \theta) q'_A + \sin(t \theta) q_B}{\sin(\theta)}
\end{array}
@f]
@see @ref Quaternion::isNormalized(), @ref lerpShortestPath(),
@ref sclerpShortestPath()
*/
template<class T> inline Quaternion<T> slerpShortestPath(const Quaternion<T>& normalizedA, const Quaternion<T>& normalizedB, T t) {
CORRADE_ASSERT(normalizedA.isNormalized() && normalizedB.isNormalized(),
"Math::slerp(): quaternions must be normalized", {});
const T cosHalfAngle = dot(normalizedA, normalizedB);
/* Avoid division by zero */
if(std::abs(cosHalfAngle) >= T(1) - TypeTraits<T>::epsilon())
return normalizedA;
const Quaternion<T> shortestNormalizedA = cosHalfAngle < 0 ? -normalizedA : normalizedA;
const T a = std::acos(std::abs(cosHalfAngle));
return (std::sin((T(1) - t)*a)*shortestNormalizedA + std::sin(t*a)*normalizedB)/std::sin(a);
}
/** /**
@brief Quaternion @brief Quaternion
@tparam T Underlying data type @tparam T Underlying data type
Represents 3D rotation. See @ref transformations for brief introduction. Represents 3D rotation. Usually denoted as the following in equations, with
@f$ \boldsymbol{q}_V @f$ being the @ref vector() part and @f$ q_S @f$ being the
@ref scalar() part: @f[
q = [\boldsymbol{q}_V, q_S]
@f]
See @ref transformations for a brief introduction.
@see @ref Magnum::Quaternion, @ref Magnum::Quaterniond, @ref DualQuaternion, @see @ref Magnum::Quaternion, @ref Magnum::Quaterniond, @ref DualQuaternion,
@ref Matrix4 @ref Matrix4
*/ */
@ -236,17 +335,21 @@ template<class T> class Quaternion {
return Implementation::isNormalizedSquared(dot()); return Implementation::isNormalizedSquared(dot());
} }
/** @brief Vector part */ /** @brief Vector part (@f$ \boldsymbol{q}_V @f$) */
constexpr const Vector3<T> vector() const { return _vector; } Vector3<T>& vector() { return _vector; }
/* Returning const so it's possible to call constexpr functions on the
result. WTF, C++?! */
constexpr const Vector3<T> vector() const { return _vector; } /**< @overload */
/** @brief Scalar part */ /** @brief Scalar part (@f$ q_S @f$) */
constexpr T scalar() const { return _scalar; } T& scalar() { return _scalar; }
constexpr T scalar() const { return _scalar; } /**< @overload */
/** /**
* @brief Rotation angle of unit quaternion * @brief Rotation angle of unit quaternion
* *
* Expects that the quaternion is normalized. @f[ * Expects that the quaternion is normalized. @f[
* \theta = 2 \cdot acos q_S * \theta = 2 \cdot \arccos(q_S)
* @f] * @f]
* @see @ref isNormalized(), @ref axis(), @ref rotation() * @see @ref isNormalized(), @ref axis(), @ref rotation()
*/ */

15
src/Magnum/Math/Test/BezierTest.cpp

@ -29,6 +29,7 @@
#include <Corrade/Utility/Configuration.h> #include <Corrade/Utility/Configuration.h>
#include "Magnum/Math/Bezier.h" #include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/Vector2.h" #include "Magnum/Math/Vector2.h"
#include "Magnum/Math/Functions.h" #include "Magnum/Math/Functions.h"
@ -60,6 +61,7 @@ typedef Math::Bezier<1, 2, Float> LinearBezier2D;
typedef Math::QuadraticBezier2D<Float> QuadraticBezier2D; typedef Math::QuadraticBezier2D<Float> QuadraticBezier2D;
typedef Math::QuadraticBezier2D<Double> QuadraticBezier2Dd; typedef Math::QuadraticBezier2D<Double> QuadraticBezier2Dd;
typedef Math::CubicBezier2D<Float> CubicBezier2D; typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite2D<Float> CubicHermite2D;
struct BezierTest: Corrade::TestSuite::Tester { struct BezierTest: Corrade::TestSuite::Tester {
explicit BezierTest(); explicit BezierTest();
@ -68,6 +70,7 @@ struct BezierTest: Corrade::TestSuite::Tester {
void constructDefault(); void constructDefault();
void constructNoInit(); void constructNoInit();
void constructConversion(); void constructConversion();
void constructFromCubicHermite();
void constructCopy(); void constructCopy();
void convert(); void convert();
@ -91,6 +94,7 @@ BezierTest::BezierTest() {
&BezierTest::constructDefault, &BezierTest::constructDefault,
&BezierTest::constructNoInit, &BezierTest::constructNoInit,
&BezierTest::constructConversion, &BezierTest::constructConversion,
&BezierTest::constructFromCubicHermite,
&BezierTest::constructCopy, &BezierTest::constructCopy,
&BezierTest::convert, &BezierTest::convert,
@ -160,6 +164,16 @@ void BezierTest::constructConversion() {
CORRADE_VERIFY((std::is_nothrow_constructible<QuadraticBezier2D, QuadraticBezier2Dd>::value)); CORRADE_VERIFY((std::is_nothrow_constructible<QuadraticBezier2D, QuadraticBezier2Dd>::value));
} }
void BezierTest::constructFromCubicHermite() {
/* See CubicHermiterTest::constructFromBezier() for the inverse. Expected
value the same as in valueCubic() to test also interpolation with it. */
CubicHermite2D a{{}, Vector2{0.0f, 0.0f}, Vector2{30.0f, 45.0f}};
CubicHermite2D b{Vector2{-45, -72}, Vector2{5.0f, -20.0f}, {}};
auto bezier = CubicBezier2D::fromCubicHermite(a, b);
CORRADE_COMPARE(bezier, (CubicBezier2D{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}));
}
void BezierTest::constructCopy() { void BezierTest::constructCopy() {
constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}}; constexpr QuadraticBezier2D a{Vector2{0.5f, 1.0f}, Vector2{1.1f, 0.3f}, Vector2{0.1f, 1.2f}};
constexpr QuadraticBezier2D b{a}; constexpr QuadraticBezier2D b{a};
@ -237,6 +251,7 @@ void BezierTest::valueQuadratic() {
void BezierTest::valueCubic() { void BezierTest::valueCubic() {
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}}; CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
/* Values should be exactly the same as in CubicHermiterTest::splerpVectorFromBezier() */
CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f})); CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f})); CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f})); CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f}));

4
src/Magnum/Math/Test/CMakeLists.txt

@ -54,18 +54,22 @@ corrade_add_test(MathQuaternionTest QuaternionTest.cpp LIBRARIES MagnumMathTestL
corrade_add_test(MathDualQuaternionTest DualQuaternionTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathDualQuaternionTest DualQuaternionTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathBezierTest BezierTest.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(MathFrustumTest FrustumTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathDistanceTest DistanceTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathIntersectionTest IntersectionTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathIntersectionTest IntersectionTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathIntersectionBenchmark IntersectionBenchmark.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(MathIntersectionBenchmark IntersectionBenchmark.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathInterpolationBenchmark InterpolationBenchmark.cpp LIBRARIES MagnumMathTestLib)
set_property(TARGET set_property(TARGET
MathVectorTest MathVectorTest
MathMatrixTest MathMatrixTest
MathMatrix3Test MathMatrix3Test
MathMatrix4Test MathMatrix4Test
MathComplexTest MathComplexTest
MathCubicHermiteTest
MathDualComplexTest MathDualComplexTest
MathQuaternionTest MathQuaternionTest
MathDualQuaternionTest MathDualQuaternionTest

85
src/Magnum/Math/Test/ComplexTest.cpp

@ -64,6 +64,8 @@ struct ComplexTest: Corrade::TestSuite::Tester {
void constructCopy(); void constructCopy();
void convert(); void convert();
void data();
void compare(); void compare();
void isNormalized(); void isNormalized();
template<class T> void isNormalizedEpsilon(); template<class T> void isNormalizedEpsilon();
@ -86,6 +88,10 @@ struct ComplexTest: Corrade::TestSuite::Tester {
void angle(); void angle();
void rotation(); void rotation();
void matrix(); void matrix();
void lerp();
void lerpNotNormalized();
void slerp();
void slerpNotNormalized();
void transformVector(); void transformVector();
void debug(); void debug();
@ -102,6 +108,8 @@ ComplexTest::ComplexTest() {
&ComplexTest::constructCopy, &ComplexTest::constructCopy,
&ComplexTest::convert, &ComplexTest::convert,
&ComplexTest::data,
&ComplexTest::compare, &ComplexTest::compare,
&ComplexTest::isNormalized, &ComplexTest::isNormalized,
&ComplexTest::isNormalizedEpsilon<Float>, &ComplexTest::isNormalizedEpsilon<Float>,
@ -128,6 +136,10 @@ ComplexTest::ComplexTest() {
&ComplexTest::angle, &ComplexTest::angle,
&ComplexTest::rotation, &ComplexTest::rotation,
&ComplexTest::matrix, &ComplexTest::matrix,
&ComplexTest::lerp,
&ComplexTest::lerpNotNormalized,
&ComplexTest::slerp,
&ComplexTest::slerpNotNormalized,
&ComplexTest::transformVector, &ComplexTest::transformVector,
&ComplexTest::debug, &ComplexTest::debug,
@ -141,14 +153,13 @@ typedef Math::Vector2<Float> Vector2;
typedef Math::Matrix3<Float> Matrix3; typedef Math::Matrix3<Float> Matrix3;
typedef Math::Matrix2x2<Float> Matrix2x2; typedef Math::Matrix2x2<Float> Matrix2x2;
using namespace Math::Literals;
void ComplexTest::construct() { void ComplexTest::construct() {
constexpr Complex a = {0.5f, -3.7f}; constexpr Complex a = {0.5f, -3.7f};
CORRADE_COMPARE(a, Complex(0.5f, -3.7f)); CORRADE_COMPARE(a, Complex(0.5f, -3.7f));
CORRADE_COMPARE(a.real(), 0.5f);
constexpr Float b = a.real(); CORRADE_COMPARE(a.imaginary(), -3.7f);
constexpr Float c = a.imaginary();
CORRADE_COMPARE(b, 0.5f);
CORRADE_COMPARE(c, -3.7f);
CORRADE_VERIFY((std::is_nothrow_constructible<Complex, Float, Float>::value)); CORRADE_VERIFY((std::is_nothrow_constructible<Complex, Float, Float>::value));
} }
@ -245,6 +256,19 @@ void ComplexTest::convert() {
CORRADE_VERIFY(!(std::is_convertible<Complex, Cmpl>::value)); CORRADE_VERIFY(!(std::is_convertible<Complex, Cmpl>::value));
} }
void ComplexTest::data() {
constexpr Complex ca{1.5f, -3.5f};
constexpr Float real = ca.real();
constexpr Float imaginary = ca.imaginary();
CORRADE_COMPARE(real, 1.5f);
CORRADE_COMPARE(imaginary, -3.5f);
Complex a{1.5f, -3.5f};
a.real() = 2.0f;
a.imaginary() = -3.5f;
CORRADE_COMPARE(a, (Complex{2.0f, -3.5f}));
}
void ComplexTest::compare() { void ComplexTest::compare() {
CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits<Float>::epsilon()/2) == Complex(3.7f, -1.0f)); CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits<Float>::epsilon()/2) == Complex(3.7f, -1.0f));
CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits<Float>::epsilon()*2) != Complex(3.7f, -1.0f)); CORRADE_VERIFY(Complex(3.7f, -1.0f+TypeTraits<Float>::epsilon()*2) != Complex(3.7f, -1.0f));
@ -411,6 +435,57 @@ void ComplexTest::matrix() {
CORRADE_COMPARE(b, a); CORRADE_COMPARE(b, a);
} }
void ComplexTest::lerp() {
/* Results should be consistent with QuaternionTest::lerp2D() (but not
equivalent, probably because quaternions double cover and complex
numbers not) */
Complex a = Complex::rotation(15.0_degf);
Complex b = Complex::rotation(57.0_degf);
Complex lerp = Math::lerp(a, b, 0.35f);
CORRADE_VERIFY(lerp.isNormalized());
CORRADE_COMPARE(lerp.angle(), 29.4308_degf); /* almost but not quite 29.7 */
CORRADE_COMPARE(lerp, (Complex{0.87095f, 0.491372f}));
}
void ComplexTest::lerpNotNormalized() {
std::ostringstream out;
Error redirectError{&out};
Complex a;
Math::lerp(a*3.0f, a, 0.35f);
Math::lerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::lerp(): complex numbers must be normalized\n"
"Math::lerp(): complex numbers must be normalized\n");
}
void ComplexTest::slerp() {
/* Result angle should be equivalent to QuaternionTest::slerp2D() */
Complex a = Complex::rotation(15.0_degf);
Complex b = Complex::rotation(57.0_degf);
Complex slerp = Math::slerp(a, b, 0.35f);
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */
CORRADE_COMPARE(slerp, (Complex{0.868632f, 0.495459f}));
/* Avoid division by zero */
CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a);
}
void ComplexTest::slerpNotNormalized() {
std::ostringstream out;
Error redirectError{&out};
Complex a;
Math::slerp(a*3.0f, a, 0.35f);
Math::slerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::slerp(): complex numbers must be normalized\n"
"Math::slerp(): complex numbers must be normalized\n");
}
void ComplexTest::transformVector() { void ComplexTest::transformVector() {
Complex a = Complex::rotation(Deg(23.0f)); Complex a = Complex::rotation(Deg(23.0f));
Matrix3 m = Matrix3::rotation(Deg(23.0f)); Matrix3 m = Matrix3::rotation(Deg(23.0f));

956
src/Magnum/Math/Test/CubicHermiteTest.cpp

@ -0,0 +1,956 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
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 <sstream>
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/Math/Bezier.h"
#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 constructFromBezier();
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 splerpVectorFromBezier();
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::constructFromBezier,
&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::splerpVectorFromBezier,
&CubicHermiteTest::splerpComplex,
&CubicHermiteTest::splerpComplexNotNormalized,
&CubicHermiteTest::splerpQuaternion,
&CubicHermiteTest::splerpQuaternionNotNormalized,
&CubicHermiteTest::debugScalar,
&CubicHermiteTest::debugVector,
&CubicHermiteTest::debugComplex,
&CubicHermiteTest::debugQuaternion});
}
typedef Math::Vector2<Float> Vector2;
typedef Math::Complex<Float> Complex;
typedef Math::Quaternion<Float> Quaternion;
typedef Math::CubicBezier2D<Float> CubicBezier2D;
typedef Math::CubicHermite1D<Float> CubicHermite1D;
typedef Math::CubicHermite2D<Float> CubicHermite2D;
typedef Math::CubicHermiteComplex<Float> CubicHermiteComplex;
typedef Math::CubicHermiteQuaternion<Float> 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<CubicHermite1D, Float, Float, Float>::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<CubicHermite2D, Vector2, Vector2, Vector2>::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<CubicHermiteComplex, Complex, Complex, Complex>::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<CubicHermiteQuaternion, Quaternion, Quaternion, Quaternion>::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<CubicHermite1D>::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<CubicHermite2D>::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<CubicHermiteComplex>::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<CubicHermiteQuaternion>::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<CubicHermite1D, ZeroInitT>::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<CubicHermite2D, ZeroInitT>::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<CubicHermiteComplex, ZeroInitT>::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<CubicHermiteQuaternion, ZeroInitT>::value));
}
void CubicHermiteTest::constructIdentityScalar() {
CORRADE_VERIFY(!(std::is_constructible<CubicHermite1D, IdentityInitT>::value));
}
void CubicHermiteTest::constructIdentityVector() {
CORRADE_VERIFY(!(std::is_constructible<CubicHermite2D, IdentityInitT>::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<CubicHermiteComplex, IdentityInitT>::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<CubicHermiteComplex, IdentityInitT>::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<CubicHermite1D, NoInitT>::value));
/* Implicit construction is not allowed */
CORRADE_VERIFY(!(std::is_convertible<NoInitT, CubicHermite1D>::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<CubicHermite2D, NoInitT>::value));
/* Implicit construction is not allowed */
CORRADE_VERIFY(!(std::is_convertible<NoInitT, CubicHermite2D>::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<CubicHermiteComplex, NoInitT>::value));
/* Implicit construction is not allowed */
CORRADE_VERIFY(!(std::is_convertible<NoInitT, CubicHermiteComplex>::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<CubicHermiteQuaternion, NoInitT>::value));
/* Implicit construction is not allowed */
CORRADE_VERIFY(!(std::is_convertible<NoInitT, CubicHermiteQuaternion>::value));
}
void CubicHermiteTest::constructConversionScalar() {
typedef Math::CubicHermite1D<Int> 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<CubicHermite1D, CubicHermite1Di>::value));
CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermite1D, CubicHermite1Di>::value));
}
void CubicHermiteTest::constructConversionVector() {
typedef Math::CubicHermite2D<Int> 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<CubicHermite2D, CubicHermite2Di>::value));
CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermite2D, CubicHermite2Di>::value));
}
void CubicHermiteTest::constructConversionComplex() {
typedef Math::CubicHermiteComplex<Int> 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<CubicHermiteComplex, CubicHermiteComplexi>::value));
CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermiteComplex, CubicHermiteComplexi>::value));
}
void CubicHermiteTest::constructConversionQuaternion() {
typedef Math::CubicHermiteQuaternion<Int> 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<CubicHermiteQuaternion, CubicHermiteQuaternioni>::value));
CORRADE_VERIFY((std::is_nothrow_constructible<CubicHermiteQuaternion, CubicHermiteQuaternioni>::value));
}
void CubicHermiteTest::constructFromBezier() {
/* Taken from BezierTest::valueCubic() -- we're testing the same values
also in splerpVectorFromBezier(). See
BezierTest::constructFromCubicHermite() for the inverse. */
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier);
auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}});
CORRADE_COMPARE(a.point(), bezier[0]);
CORRADE_COMPARE(a.outTangent(), (Vector2{30.0f, 45.0f}));
CORRADE_COMPARE(b.inTangent(), (Vector2{-45, -72}));
CORRADE_COMPARE(b.point(), bezier[3]);
}
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<CubicHermite1D>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<CubicHermite1D>::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<CubicHermite2D>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<CubicHermite2D>::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<CubicHermiteComplex>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<CubicHermiteComplex>::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<CubicHermiteComplex>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<CubicHermiteComplex>::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<Float>::epsilon()/2, 2.0f}));
CORRADE_VERIFY((CubicHermite1D{3.0f, 1.0f, 2.0f} != CubicHermite1D{3.0f + TypeTraits<Float>::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<Float>::epsilon()/2}}));
CORRADE_VERIFY((CubicHermite2D{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermite2D{{1.0f + TypeTraits<Float>::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<Float>::epsilon()/2}}));
CORRADE_VERIFY((CubicHermiteComplex{{1.0f, 3.0f}, {1.0f, -2.0f}, {3.0f, 2.0f}}) != (CubicHermiteComplex{{1.0f + TypeTraits<Float>::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<Float>::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<Float>::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::splerpVectorFromBezier() {
/* Taken from BezierTest::valueCubic() */
CubicBezier2D bezier{Vector2{0.0f, 0.0f}, Vector2{10.0f, 15.0f}, Vector2{20.0f, 4.0f}, Vector2{5.0f, -20.0f}};
auto a = CubicHermite2D::fromBezier({Vector2{}, Vector2{}, Vector2{}, bezier[0]}, bezier);
auto b = CubicHermite2D::fromBezier(bezier, {bezier[3], Vector2{}, Vector2{}, Vector2{}});
CORRADE_COMPARE(bezier.value(0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.0f), (Vector2{0.0f, 0.0f}));
CORRADE_COMPARE(bezier.value(0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.2f), (Vector2{5.8f, 5.984f}));
CORRADE_COMPARE(bezier.value(0.5f), (Vector2{11.875f, 4.625f}));
CORRADE_COMPARE(Math::splerp(a, b, 0.5f), (Vector2{11.875f, 4.625f}));
CORRADE_COMPARE(bezier.value(1.0f), (Vector2{5.0f, -20.0f}));
CORRADE_COMPARE(Math::splerp(a, b, 1.0f), (Vector2{5.0f, -20.0f}));
}
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)

93
src/Magnum/Math/Test/DualQuaternionTest.cpp

@ -90,6 +90,7 @@ struct DualQuaternionTest: Corrade::TestSuite::Tester {
void transformPointNormalized(); void transformPointNormalized();
void sclerp(); void sclerp();
void sclerpShortestPath();
void debug(); void debug();
void configuration(); void configuration();
@ -144,6 +145,7 @@ DualQuaternionTest::DualQuaternionTest() {
&DualQuaternionTest::transformPointNormalized, &DualQuaternionTest::transformPointNormalized,
&DualQuaternionTest::sclerp, &DualQuaternionTest::sclerp,
&DualQuaternionTest::sclerpShortestPath,
&DualQuaternionTest::debug, &DualQuaternionTest::debug,
&DualQuaternionTest::configuration}); &DualQuaternionTest::configuration});
@ -479,34 +481,99 @@ void DualQuaternionTest::transformPointNormalized() {
} }
void DualQuaternionTest::sclerp() { void DualQuaternionTest::sclerp() {
const DualQuaternion from = DualQuaternion::translation(Vector3{20.0f, .0f, .0f})*DualQuaternion::rotation(180.0_degf, Vector3{.0f, 1.0f, .0f}); auto from = DualQuaternion::translation({20.0f, 0.0f, 0.0f})*
const DualQuaternion to = DualQuaternion::translation(Vector3{42.0f, 42.0f, 42.0f})*DualQuaternion::rotation(75.0_degf, Vector3{1.0f, .0f, .0f}); DualQuaternion::rotation(65.0_degf, Vector3::yAxis());
auto to = DualQuaternion::translation({42.0f, 42.0f, 42.0f})*
constexpr DualQuaternion expected1{Quaternion{{.23296291314453416f, .9238795325112867f, .0f}, .303603179340959f}, DualQuaternion::rotation(75.0_degf, Vector3::xAxis());
Quaternion{{2.235619101917766f, 2.8169719855488395f, 10.722240915237789f}, -10.287636336847847f}};
constexpr DualQuaternion expected2{Quaternion{{.4437679833315842f, .6845471059286887f, .0f}, .5783296955322937f}, const DualQuaternion begin = Math::sclerp(from, to, 0.0f);
Quaternion{{5.764394870292371f, 11.161306653193549f, 9.671267015501789f}, -17.634394590712066f}}; const DualQuaternion beginShortestPath = Math::sclerpShortestPath(from, to, 0.0f);
constexpr DualQuaternion expected3{Quaternion{{.5979785904506439f, .18738131458572468f, .0f}, .7793008714910992f}, const DualQuaternion end = Math::sclerp(from, to, 1.0f);
Quaternion{{13.409627907069353f, 25.452124456683414f, 5.681581047706807f}, -16.409481115504978f}}; const DualQuaternion endShortestPath = Math::sclerpShortestPath(from, to, 1.0f);
CORRADE_COMPARE(begin, from);
CORRADE_COMPARE(beginShortestPath, from);
CORRADE_COMPARE(end, to);
CORRADE_COMPARE(endShortestPath, to);
DualQuaternion expected1{
{{0.170316f, 0.424975f, 0.0f}, 0.889038f},
{{10.689f, 7.47059f, 5.33428f}, -5.61881f}};
DualQuaternion expected2{
{{0.34568f, 0.282968f, 0.0f}, 0.89467f},
{{12.8764f, 15.8357f, 5.03088f}, -9.98371f}};
DualQuaternion expected3{
{{0.550678f, 0.072563f, 0.0f}, 0.831558f},
{{15.6916f, 26.3477f, 4.23219f}, -12.6905f}};
const DualQuaternion interp1 = Math::sclerp(from, to, 0.25f); const DualQuaternion interp1 = Math::sclerp(from, to, 0.25f);
const DualQuaternion interp1ShortestPath = Math::sclerpShortestPath(from, to, 0.25f);
const DualQuaternion interp2 = Math::sclerp(from, to, 0.52f); const DualQuaternion interp2 = Math::sclerp(from, to, 0.52f);
const DualQuaternion interp2ShortestPath = Math::sclerpShortestPath(from, to, 0.52f);
const DualQuaternion interp3 = Math::sclerp(from, to, 0.88f); const DualQuaternion interp3 = Math::sclerp(from, to, 0.88f);
const DualQuaternion interp3ShortestPath = Math::sclerpShortestPath(from, to, 0.88f);
CORRADE_COMPARE(interp1, expected1); CORRADE_COMPARE(interp1, expected1);
CORRADE_COMPARE(interp1ShortestPath, expected1);
CORRADE_COMPARE(interp2, expected2); CORRADE_COMPARE(interp2, expected2);
CORRADE_COMPARE(interp2ShortestPath, expected2);
CORRADE_COMPARE(interp3, expected3); CORRADE_COMPARE(interp3, expected3);
CORRADE_COMPARE(interp3ShortestPath, expected3);
/* Edge cases: */ /* Edge cases: */
/* Dual quaternions with identical rotation */ /* Dual quaternions with identical rotation */
CORRADE_COMPARE(Math::sclerp(from, from, 0.42f), from); CORRADE_COMPARE(Math::sclerp(from, from, 0.42f), from);
CORRADE_COMPARE(Math::sclerp(from, DualQuaternion(-from.real(), from.dual()), 0.42f), from); CORRADE_COMPARE(Math::sclerpShortestPath(from, from, 0.42f), from);
CORRADE_COMPARE(Math::sclerp(from, -from, 0.42f), from);
CORRADE_COMPARE(Math::sclerpShortestPath(from, -from, 0.42f), from);
/* No difference in rotation, but in translation */ /* No difference in rotation, but in translation */
const auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}); {
CORRADE_COMPARE(Math::sclerp(DualQuaternion::translation(Vector3{1.0f, 2.0f, 4.0f})*rotation, DualQuaternion::translation(Vector3{5, -6, 2})*rotation, 0.25f), auto rotation = DualQuaternion::rotation(35.0_degf, Vector3{0.3f, 0.2f, 0.1f}.normalized());
DualQuaternion::translation(Vector3{2.0f, 0.0f, 3.5f})*rotation); auto a = DualQuaternion::translation({1.0f, 2.0f, 4.0f})*rotation;
auto b = DualQuaternion::translation({5.0f, -6.0f, 2.0f})*rotation;
auto expected = DualQuaternion::translation({2.0f, 0.0f, 3.5f})*rotation;
auto interpolateTranslation = Math::sclerp(a, b, 0.25f);
auto interpolateTranslationShortestPath = Math::sclerpShortestPath(a, b, 0.25f);
CORRADE_VERIFY(interpolateTranslation.isNormalized());
CORRADE_VERIFY(interpolateTranslationShortestPath.isNormalized());
CORRADE_COMPARE(interpolateTranslation, expected);
CORRADE_COMPARE(interpolateTranslationShortestPath, expected);
}
}
void DualQuaternionTest::sclerpShortestPath() {
DualQuaternion a = DualQuaternion::translation({1.5f, 0.3f, 0.0f})*
DualQuaternion::rotation(0.0_degf, Vector3::zAxis());
DualQuaternion b = DualQuaternion::translation({3.5f, 0.3f, 1.0f})*
DualQuaternion::rotation(225.0_degf, Vector3::zAxis());
DualQuaternion sclerp = Math::sclerp(a, b, 0.25f);
DualQuaternion sclerpShortestPath = Math::sclerpShortestPath(a, b, 0.25f);
CORRADE_VERIFY(sclerp.isNormalized());
CORRADE_VERIFY(sclerpShortestPath.isNormalized());
CORRADE_COMPARE(sclerp.rotation().axis(), Vector3::zAxis());
/** @todo why is this inverted compared to QuaternionTest::slerpShortestPath()? */
CORRADE_COMPARE(sclerpShortestPath.rotation().axis(), -Vector3::zAxis());
CORRADE_COMPARE(sclerp.rotation().angle(), 56.25_degf);
/* Because the axis is inverted, this is also inverted compared to
QuaternionTest::slerpShortestPath() */
CORRADE_COMPARE(sclerpShortestPath.rotation().angle(), 360.0_degf - 326.25_degf);
CORRADE_COMPARE(sclerp, (DualQuaternion{
{{0.0f, 0.0f, 0.471397f}, 0.881921f},
{{0.536892f, -0.692656f, 0.11024f}, -0.0589246f}}));
/* Also inverted compared to QuaternionTest::slerpShortestPath() */
CORRADE_COMPARE(sclerpShortestPath, (DualQuaternion{
{{0.0f, 0.0f, -0.290285f}, 0.95694f},
{{0.794402f, 0.651539f, 0.119618f}, 0.0362856f}}));
/* Translation along Z should be the same in both, in 25% of the way.
Translation in the XY plane is along a screw, so that's different. */
CORRADE_COMPARE(sclerpShortestPath.translation().z(), 0.25f);
CORRADE_COMPARE(sclerpShortestPath.translation().z(), 0.25f);
} }
void DualQuaternionTest::debug() { void DualQuaternionTest::debug() {

23
src/Magnum/Math/Test/DualTest.cpp

@ -42,6 +42,8 @@ struct DualTest: Corrade::TestSuite::Tester {
void constructConversion(); void constructConversion();
void constructCopy(); void constructCopy();
void data();
void compare(); void compare();
void addSubtract(); void addSubtract();
@ -79,6 +81,8 @@ DualTest::DualTest() {
&DualTest::constructConversion, &DualTest::constructConversion,
&DualTest::constructCopy, &DualTest::constructCopy,
&DualTest::data,
&DualTest::compare, &DualTest::compare,
&DualTest::addSubtract, &DualTest::addSubtract,
@ -101,10 +105,8 @@ DualTest::DualTest() {
void DualTest::construct() { void DualTest::construct() {
constexpr Dual a = {2.0f, -7.5f}; constexpr Dual a = {2.0f, -7.5f};
constexpr Float b = a.real(); CORRADE_COMPARE(a.real(), 2.0f);
constexpr Float c = a.dual(); CORRADE_COMPARE(a.dual(), -7.5f);
CORRADE_COMPARE(b, 2.0f);
CORRADE_COMPARE(c, -7.5f);
constexpr Dual d(3.0f); constexpr Dual d(3.0f);
CORRADE_COMPARE(d.real(), 3.0f); CORRADE_COMPARE(d.real(), 3.0f);
@ -176,6 +178,19 @@ void DualTest::constructCopy() {
CORRADE_VERIFY(std::is_nothrow_copy_assignable<Dual>::value); CORRADE_VERIFY(std::is_nothrow_copy_assignable<Dual>::value);
} }
void DualTest::data() {
constexpr Dual ca{1.5f, -3.5f};
constexpr Float real = ca.real();
constexpr Float dual = ca.dual();
CORRADE_COMPARE(real, 1.5f);
CORRADE_COMPARE(dual, -3.5f);
Dual a{1.5f, -3.5f};
a.real() = 2.0f;
a.dual() = -3.5f;
CORRADE_COMPARE(a, (Dual{2.0f, -3.5f}));
}
void DualTest::compare() { void DualTest::compare() {
CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits<Float>::epsilon()/2) == Dual(1.0f, 1.0f)); CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits<Float>::epsilon()/2) == Dual(1.0f, 1.0f));
CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits<Float>::epsilon()*2) != Dual(1.0f, 1.0f)); CORRADE_VERIFY(Dual(1.0f, 1.0f+TypeTraits<Float>::epsilon()*2) != Dual(1.0f, 1.0f));

6
src/Magnum/Math/Test/HalfTest.cpp

@ -609,12 +609,12 @@ void HalfTest::debug() {
std::ostringstream out; std::ostringstream out;
Debug{&out} << -3.64_h << Half{Constants::inf()} Debug{&out} << -36.41_h << Half{Constants::inf()}
<< Math::Vector3<Half>{3.14159_h, -1.4142_h, 1.618_h}; << Math::Vector3<Half>{3.14159_h, -1.4142_h, 1.618_h};
#ifdef _MSC_VER #ifdef _MSC_VER
CORRADE_COMPARE(out.str(), "-3.64063 inf Vector(3.14063, -1.41406, 1.61816)\n"); CORRADE_COMPARE(out.str(), "-36.41 inf Vector(3.141, -1.414, 1.618)\n");
#else #else
CORRADE_COMPARE(out.str(), "-3.64062 inf Vector(3.14062, -1.41406, 1.61816)\n"); CORRADE_COMPARE(out.str(), "-36.41 inf Vector(3.141, -1.414, 1.618)\n");
#endif #endif
} }

152
src/Magnum/Math/Test/InterpolationBenchmark.cpp

@ -0,0 +1,152 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <mosra@centrum.cz>
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 <Corrade/TestSuite/Tester.h>
#define CORRADE_NO_ASSERT
#include "Magnum/Math/DualQuaternion.h"
namespace Magnum { namespace Math { namespace Test {
struct InterpolationBenchmark: Corrade::TestSuite::Tester {
explicit InterpolationBenchmark();
void baseline();
void quaternionLerp();
void quaternionLerpShortestPath();
void quaternionSlerp();
void quaternionSlerpShortestPath();
void dualQuaternionSclerp();
void dualQuaternionSclerpShortestPath();
};
using namespace Math::Literals;
typedef Math::Quaternion<Float> Quaternion;
typedef Math::DualQuaternion<Float> DualQuaternion;
typedef Math::Vector3<Float> Vector3;
InterpolationBenchmark::InterpolationBenchmark() {
addBenchmarks({&InterpolationBenchmark::baseline,
&InterpolationBenchmark::quaternionLerp,
&InterpolationBenchmark::quaternionLerpShortestPath,
&InterpolationBenchmark::quaternionSlerp,
&InterpolationBenchmark::quaternionSlerpShortestPath,
&InterpolationBenchmark::dualQuaternionSclerp,
&InterpolationBenchmark::dualQuaternionSclerpShortestPath}, 100);
}
void InterpolationBenchmark::baseline() {
Quaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(10000) {
c += Quaternion{};
t += 0.0002f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::quaternionLerp() {
auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(10000) {
c += lerp(a, b, t);
t += 0.0002f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::quaternionLerpShortestPath() {
auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(10000) {
c += lerpShortestPath(a, b, t);
t += 0.0002f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::quaternionSlerp() {
auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(10000) {
c += slerp(a, b, t);
t += 0.0002f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::quaternionSlerpShortestPath() {
auto a = Quaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(10000) {
c += slerpShortestPath(a, b, t);
t += 0.0002f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::dualQuaternionSclerp() {
auto a = DualQuaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = DualQuaternion::rotation(0.0_degf, Vector3::zAxis());
DualQuaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(1000) {
c += sclerp(a, b, t);
t += 0.001f;
}
CORRADE_VERIFY(!c.isNormalized());
}
void InterpolationBenchmark::dualQuaternionSclerpShortestPath() {
auto a = DualQuaternion::rotation(225.0_degf, Vector3::zAxis());
auto b = DualQuaternion::rotation(0.0_degf, Vector3::zAxis());
DualQuaternion c;
Float t = 0.0f;
CORRADE_BENCHMARK(1000) {
c += sclerpShortestPath(a, b, t);
t += 0.001f;
}
CORRADE_VERIFY(!c.isNormalized());
}
}}}
CORRADE_TEST_MAIN(Magnum::Math::Test::InterpolationBenchmark)

164
src/Magnum/Math/Test/QuaternionTest.cpp

@ -65,6 +65,8 @@ struct QuaternionTest: Corrade::TestSuite::Tester {
void constructCopy(); void constructCopy();
void convert(); void convert();
void data();
void compare(); void compare();
void isNormalized(); void isNormalized();
template<class T> void isNormalizedEpsilon(); template<class T> void isNormalizedEpsilon();
@ -87,8 +89,16 @@ struct QuaternionTest: Corrade::TestSuite::Tester {
void rotation(); void rotation();
void angle(); void angle();
void matrix(); void matrix();
void lerp(); void lerp();
void lerpShortestPath();
void lerp2D();
void lerpNotNormalized();
void slerp(); void slerp();
void slerpShortestPath();
void slerp2D();
void slerpNotNormalized();
void transformVector(); void transformVector();
void transformVectorNormalized(); void transformVectorNormalized();
@ -104,6 +114,8 @@ typedef Math::Quaternion<Float> Quaternion;
typedef Math::Vector3<Float> Vector3; typedef Math::Vector3<Float> Vector3;
typedef Math::Vector4<Float> Vector4; typedef Math::Vector4<Float> Vector4;
using namespace Math::Literals;
QuaternionTest::QuaternionTest() { QuaternionTest::QuaternionTest() {
addTests({&QuaternionTest::construct, addTests({&QuaternionTest::construct,
&QuaternionTest::constructIdentity, &QuaternionTest::constructIdentity,
@ -114,6 +126,8 @@ QuaternionTest::QuaternionTest() {
&QuaternionTest::constructCopy, &QuaternionTest::constructCopy,
&QuaternionTest::convert, &QuaternionTest::convert,
&QuaternionTest::data,
&QuaternionTest::compare, &QuaternionTest::compare,
&QuaternionTest::isNormalized, &QuaternionTest::isNormalized,
&QuaternionTest::isNormalizedEpsilon<Float>, &QuaternionTest::isNormalizedEpsilon<Float>,
@ -140,8 +154,16 @@ QuaternionTest::QuaternionTest() {
&QuaternionTest::rotation, &QuaternionTest::rotation,
&QuaternionTest::angle, &QuaternionTest::angle,
&QuaternionTest::matrix, &QuaternionTest::matrix,
&QuaternionTest::lerp, &QuaternionTest::lerp,
&QuaternionTest::lerpShortestPath,
&QuaternionTest::lerp2D,
&QuaternionTest::lerpNotNormalized,
&QuaternionTest::slerp, &QuaternionTest::slerp,
&QuaternionTest::slerpShortestPath,
&QuaternionTest::slerp2D,
&QuaternionTest::slerpNotNormalized,
&QuaternionTest::transformVector, &QuaternionTest::transformVector,
&QuaternionTest::transformVectorNormalized, &QuaternionTest::transformVectorNormalized,
@ -152,11 +174,8 @@ QuaternionTest::QuaternionTest() {
void QuaternionTest::construct() { void QuaternionTest::construct() {
constexpr Quaternion a = {{1.0f, 2.0f, 3.0f}, -4.0f}; constexpr Quaternion a = {{1.0f, 2.0f, 3.0f}, -4.0f};
CORRADE_COMPARE(a, Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); CORRADE_COMPARE(a, Quaternion({1.0f, 2.0f, 3.0f}, -4.0f));
CORRADE_COMPARE(a.vector(), Vector3(1.0f, 2.0f, 3.0f));
constexpr Vector3 b = a.vector(); CORRADE_COMPARE(a.scalar(), -4.0f);
constexpr Float c = a.scalar();
CORRADE_COMPARE(b, Vector3(1.0f, 2.0f, 3.0f));
CORRADE_COMPARE(c, -4.0f);
CORRADE_VERIFY((std::is_nothrow_constructible<Quaternion, Vector3, Float>::value)); CORRADE_VERIFY((std::is_nothrow_constructible<Quaternion, Vector3, Float>::value));
} }
@ -249,6 +268,19 @@ void QuaternionTest::convert() {
CORRADE_VERIFY(!(std::is_convertible<Quaternion, Quat>::value)); CORRADE_VERIFY(!(std::is_convertible<Quaternion, Quat>::value));
} }
void QuaternionTest::data() {
constexpr Quaternion ca{{1.0f, 2.0f, 3.0f}, -4.0f};
constexpr Vector3 vector = ca.vector();
constexpr Float scalar = ca.scalar();
CORRADE_COMPARE(vector, (Vector3{1.0f, 2.0f, 3.0f}));
CORRADE_COMPARE(scalar, -4.0f);
Quaternion a{{1.0f, 2.0f, 3.0f}, -4.0f};
a.vector().y() = 4.3f;
a.scalar() = 1.1f;
CORRADE_COMPARE(a, (Quaternion{{1.0f, 4.3f, 3.0f}, 1.1f}));
}
void QuaternionTest::compare() { void QuaternionTest::compare() {
CORRADE_VERIFY(Quaternion({1.0f+TypeTraits<Float>::epsilon()/2, 2.0f, 3.0f}, -4.0f) == Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); CORRADE_VERIFY(Quaternion({1.0f+TypeTraits<Float>::epsilon()/2, 2.0f, 3.0f}, -4.0f) == Quaternion({1.0f, 2.0f, 3.0f}, -4.0f));
CORRADE_VERIFY(Quaternion({1.0f+TypeTraits<Float>::epsilon()*2, 2.0f, 3.0f}, -4.0f) != Quaternion({1.0f, 2.0f, 3.0f}, -4.0f)); CORRADE_VERIFY(Quaternion({1.0f+TypeTraits<Float>::epsilon()*2, 2.0f, 3.0f}, -4.0f) != Quaternion({1.0f, 2.0f, 3.0f}, -4.0f));
@ -455,43 +487,121 @@ void QuaternionTest::matrix() {
} }
void QuaternionTest::lerp() { void QuaternionTest::lerp() {
Quaternion a = Quaternion::rotation(Deg(15.0f), Vector3(1.0f/Constants<Float>::sqrt3())); Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants<Float>::sqrt3()));
Quaternion b = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis());
std::ostringstream o; Quaternion lerp = Math::lerp(a, b, 0.35f);
Error redirectError{&o}; Quaternion lerpShortestPath = Math::lerpShortestPath(a, b, 0.35f);
Quaternion expected{{0.119127f, 0.049134f, 0.049134f}, 0.990445f};
/* Both should give the same result */
CORRADE_VERIFY(lerp.isNormalized());
CORRADE_VERIFY(lerpShortestPath.isNormalized());
CORRADE_COMPARE(lerp, expected);
CORRADE_COMPARE(lerpShortestPath, expected);
}
Math::lerp(a*3.0f, b, 0.35f); void QuaternionTest::lerpShortestPath() {
CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n"); Quaternion a = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(225.0_degf, Vector3::zAxis());
o.str({}); Quaternion slerp = Math::lerp(a, b, 0.25f);
Math::lerp(a, b*-3.0f, 0.35f); Quaternion slerpShortestPath = Math::lerpShortestPath(a, b, 0.25f);
CORRADE_COMPARE(o.str(), "Math::lerp(): quaternions must be normalized\n");
Quaternion lerp = Math::lerp(a, b, 0.35f); CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(lerp, Quaternion({0.119127f, 0.049134f, 0.049134f}, 0.990445f)); CORRADE_VERIFY(slerpShortestPath.isNormalized());
CORRADE_COMPARE(slerp.axis(), Vector3::zAxis());
CORRADE_COMPARE(slerpShortestPath.axis(), Vector3::zAxis());
CORRADE_COMPARE(slerp.angle(), 38.8848_degf);
CORRADE_COMPARE(slerpShortestPath.angle(), 329.448_degf);
CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.332859f}, 0.942977f}));
CORRADE_COMPARE(slerpShortestPath, (Quaternion{{0.0f, 0.0f, 0.26347f}, -0.964667f}));
} }
void QuaternionTest::slerp() { void QuaternionTest::lerp2D() {
Quaternion a = Quaternion::rotation(Deg(15.0f), Vector3(1.0f/Constants<Float>::sqrt3())); /* Results should be consistent with ComplexTest::lerp() */
Quaternion b = Quaternion::rotation(Deg(23.0f), Vector3::xAxis()); Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis());
Quaternion lerp = Math::lerp(a, b, 0.35f);
std::ostringstream o; CORRADE_VERIFY(lerp.isNormalized());
Error redirectError{&o}; CORRADE_COMPARE(lerp.angle(), 29.6351_degf); /* almost but not quite 29.7 */
CORRADE_COMPARE(lerp, (Quaternion{{0.0f, 0.0f, 0.255742f}, 0.966745f}));
}
Math::slerp(a*3.0f, b, 0.35f); void QuaternionTest::lerpNotNormalized() {
CORRADE_COMPARE(o.str(), "Math::slerp(): quaternions must be normalized\n"); std::ostringstream out;
Error redirectError{&out};
o.str({}); Quaternion a;
Math::slerp(a, b*-3.0f, 0.35f); Math::lerp(a*3.0f, a, 0.35f);
CORRADE_COMPARE(o.str(), "Math::slerp(): quaternions must be normalized\n"); Math::lerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::lerp(): quaternions must be normalized\n"
"Math::lerp(): quaternions must be normalized\n");
}
void QuaternionTest::slerp() {
Quaternion a = Quaternion::rotation(15.0_degf, Vector3(1.0f/Constants<Float>::sqrt3()));
Quaternion b = Quaternion::rotation(23.0_degf, Vector3::xAxis());
Quaternion slerp = Math::slerp(a, b, 0.35f); Quaternion slerp = Math::slerp(a, b, 0.35f);
CORRADE_COMPARE(slerp, Quaternion({0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f)); Quaternion slerpShortestPath = Math::slerpShortestPath(a, b, 0.35f);
Quaternion expected{{0.1191653f, 0.0491109f, 0.0491109f}, 0.9904423f};
/* Both should give the same result */
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(slerp, expected);
CORRADE_VERIFY(slerpShortestPath.isNormalized());
CORRADE_COMPARE(slerpShortestPath, expected);
/* Avoid division by zero */ /* Avoid division by zero */
CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a); CORRADE_COMPARE(Math::slerp(a, a, 0.25f), a);
CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a); CORRADE_COMPARE(Math::slerp(a, -a, 0.42f), a);
CORRADE_COMPARE(Math::slerpShortestPath(a, a, 0.25f), a);
CORRADE_COMPARE(Math::slerpShortestPath(a, -a, 0.25f), a);
}
void QuaternionTest::slerpShortestPath() {
Quaternion a = Quaternion::rotation(0.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(225.0_degf, Vector3::zAxis());
Quaternion slerp = Math::slerp(a, b, 0.25f);
Quaternion slerpShortestPath = Math::slerpShortestPath(a, b, 0.25f);
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_VERIFY(slerpShortestPath.isNormalized());
CORRADE_COMPARE(slerp.axis(), Vector3::zAxis());
CORRADE_COMPARE(slerpShortestPath.axis(), Vector3::zAxis());
CORRADE_COMPARE(slerp.angle(), 56.25_degf);
CORRADE_COMPARE(slerpShortestPath.angle(), 326.25_degf);
CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.471397f}, 0.881921f}));
CORRADE_COMPARE(slerpShortestPath, (Quaternion{{0.0f, 0.0f, 0.290285f}, -0.95694f}));
}
void QuaternionTest::slerp2D() {
/* Result angle should be equivalent to ComplexTest::slerp() */
Quaternion a = Quaternion::rotation(15.0_degf, Vector3::zAxis());
Quaternion b = Quaternion::rotation(57.0_degf, Vector3::zAxis());
Quaternion slerp = Math::slerp(a, b, 0.35f);
CORRADE_VERIFY(slerp.isNormalized());
CORRADE_COMPARE(slerp.angle(), 29.7_degf); /* 15 + (57-15)*0.35 */
CORRADE_COMPARE(slerp, (Quaternion{{0.0f, 0.0f, 0.256289f}, 0.9666f}));
}
void QuaternionTest::slerpNotNormalized() {
std::ostringstream out;
Error redirectError{&out};
Quaternion a;
Math::slerp(a*3.0f, a, 0.35f);
Math::slerp(a, a*-3.0f, 0.35f);
CORRADE_COMPARE(out.str(),
"Math::slerp(): quaternions must be normalized\n"
"Math::slerp(): quaternions must be normalized\n");
} }
void QuaternionTest::transformVector() { void QuaternionTest::transformVector() {

4
src/Magnum/Math/Vector.h

@ -72,7 +72,7 @@ namespace Implementation {
Returns `0` when two vectors are perpendicular, `> 0` when two vectors are in Returns `0` when two vectors are perpendicular, `> 0` when two vectors are in
the same general direction, `1` when two *normalized* vectors are parallel, the same general direction, `1` when two *normalized* vectors are parallel,
`< 0` when two vectors are in opposite general direction and `-1` when two `< 0` when two vectors are in opposite general direction and `-1` when two
*normalized* vectors are antiparallel. @f[ * *normalized* vectors are antiparallel. @f[
\boldsymbol a \cdot \boldsymbol b = \sum_{i=0}^{n-1} \boldsymbol a_i \boldsymbol b_i \boldsymbol a \cdot \boldsymbol b = \sum_{i=0}^{n-1} \boldsymbol a_i \boldsymbol b_i
@f] @f]
@see @ref Vector::dot() const, @ref Vector::operator-(), @ref Vector2::perpendicular() @see @ref Vector::dot() const, @ref Vector::operator-(), @ref Vector2::perpendicular()
@ -86,7 +86,7 @@ template<std::size_t size, class T> inline T dot(const Vector<size, T>& a, const
Expects that both vectors are normalized. Enabled only for floating-point Expects that both vectors are normalized. Enabled only for floating-point
types. @f[ types. @f[
\theta = acos \left( \frac{\boldsymbol a \cdot \boldsymbol b}{|\boldsymbol a| |\boldsymbol b|} \right) = acos (\boldsymbol a \cdot \boldsymbol b) \theta = \arccos \left( \frac{\boldsymbol a \cdot \boldsymbol b}{|\boldsymbol a| |\boldsymbol b|} \right) = \arccos (\boldsymbol a \cdot \boldsymbol b)
@f] @f]
@see @ref Vector::isNormalized(), @see @ref Vector::isNormalized(),
@ref angle(const Complex<T>&, const Complex<T>&), @ref angle(const Complex<T>&, const Complex<T>&),

12
src/Magnum/Math/instantiation.cpp

@ -24,6 +24,7 @@
*/ */
#include "Magnum/Math/Bezier.h" #include "Magnum/Math/Bezier.h"
#include "Magnum/Math/CubicHermite.h"
#include "Magnum/Math/DualComplex.h" #include "Magnum/Math/DualComplex.h"
#include "Magnum/Math/DualQuaternion.h" #include "Magnum/Math/DualQuaternion.h"
#include "Magnum/Math/Frustum.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, 2, Double>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 3, Double>&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Bezier<3, 3, Double>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Float>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Double>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector2<Float>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector3<Float>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector2<Double>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Vector3<Double>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Complex<Float>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Complex<Double>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Quaternion<Float>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const CubicHermite<Quaternion<Double>>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex<Float>&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex<Float>&);
template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex<Double>&); template Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug&, const Complex<Double>&);

11
src/Magnum/Trade/AnimationData.cpp

@ -97,8 +97,13 @@ template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4, Vector4>(Ani
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4d, Vector4d>(Animation::Interpolation) -> Vector4d(*)(const Vector4d&, const Vector4d&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4d, Vector4d>(Animation::Interpolation) -> Vector4d(*)(const Vector4d&, const Vector4d&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4i, Vector4i>(Animation::Interpolation) -> Vector4i(*)(const Vector4i&, const Vector4i&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4i, Vector4i>(Animation::Interpolation) -> Vector4i(*)(const Vector4i&, const Vector4i&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4ui, Vector4ui>(Animation::Interpolation) -> Vector4ui(*)(const Vector4ui&, const Vector4ui&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Vector4ui, Vector4ui>(Animation::Interpolation) -> Vector4ui(*)(const Vector4ui&, const Vector4ui&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Complex, Complex>(Animation::Interpolation) -> Complex(*)(const Complex&, const Complex&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Quaternion, Quaternion>(Animation::Interpolation) -> Quaternion(*)(const Quaternion&, const Quaternion&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<Quaternion, Quaternion>(Animation::Interpolation) -> Quaternion(*)(const Quaternion&, const Quaternion&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<DualQuaternion, DualQuaternion>(Animation::Interpolation) -> DualQuaternion(*)(const DualQuaternion&, const DualQuaternion&, Float); template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<DualQuaternion, DualQuaternion>(Animation::Interpolation) -> DualQuaternion(*)(const DualQuaternion&, const DualQuaternion&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<CubicHermite2D, Math::Vector2<Float>>(Animation::Interpolation) -> Math::Vector2<Float>(*)(const CubicHermite2D&, const CubicHermite2D&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<CubicHermite3D, Math::Vector3<Float>>(Animation::Interpolation) -> Math::Vector3<Float>(*)(const CubicHermite3D&, const CubicHermite3D&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<CubicHermiteComplex, Complex>(Animation::Interpolation) -> Complex(*)(const CubicHermiteComplex&, const CubicHermiteComplex&, Float);
template MAGNUM_TRADE_EXPORT auto animationInterpolatorFor<CubicHermiteQuaternion, Quaternion>(Animation::Interpolation) -> Quaternion(*)(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, Float);
Debug& operator<<(Debug& debug, const AnimationTrackType value) { Debug& operator<<(Debug& debug, const AnimationTrackType value) {
switch(value) { switch(value) {
@ -120,8 +125,14 @@ Debug& operator<<(Debug& debug, const AnimationTrackType value) {
_c(Vector4) _c(Vector4)
_c(Vector4ui) _c(Vector4ui)
_c(Vector4i) _c(Vector4i)
_c(Complex)
_c(Quaternion) _c(Quaternion)
_c(DualQuaternion) _c(DualQuaternion)
_c(CubicHermite1D)
_c(CubicHermite2D)
_c(CubicHermite3D)
_c(CubicHermiteComplex)
_c(CubicHermiteQuaternion)
#undef _c #undef _c
/* LCOV_EXCL_STOP */ /* LCOV_EXCL_STOP */
} }

97
src/Magnum/Trade/AnimationData.h

@ -75,13 +75,46 @@ enum class AnimationTrackType: UnsignedByte {
Vector4ui, /**< @ref Magnum::Vector4ui "Vector4ui" */ Vector4ui, /**< @ref Magnum::Vector4ui "Vector4ui" */
Vector4i, /**< @ref Magnum::Vector4i "Vector4i" */ Vector4i, /**< @ref Magnum::Vector4i "Vector4i" */
/**
* @ref Magnum::Complex "Complex". Usually used for
* @ref AnimationTrackTarget::Rotation2D.
*/
Complex,
/** /**
* @ref Magnum::Quaternion "Quaternion". Usually used for * @ref Magnum::Quaternion "Quaternion". Usually used for
* @ref AnimationTrackTarget::Rotation3D. * @ref AnimationTrackTarget::Rotation3D.
*/ */
Quaternion, Quaternion,
DualQuaternion /**< @ref Magnum::DualQuaternion "DualQuaternion" */ DualQuaternion, /**< @ref Magnum::DualQuaternion "DualQuaternion" */
CubicHermite1D, /**< @ref Magnum::CubicHermite1D "CubicHermite1D" */
/**
* @ref Magnum::CubicHermite2D "CubicHermite2D". Usually used for
* spline-interpolated @ref AnimationTrackTarget::Translation2D and
* @ref AnimationTrackTarget::Scaling2D.
*/
CubicHermite2D,
/**
* @ref Magnum::CubicHermite3D "CubicHermite3D". Usually used for
* spline-interpolated @ref AnimationTrackTarget::Translation3D and
* @ref AnimationTrackTarget::Scaling3D.
*/
CubicHermite3D,
/**
* @ref Magnum::CubicHermiteComplex "CubicHermiteComplex". Usually used for
* spline-interpolated @ref AnimationTrackTarget::Rotation2D.
*/
CubicHermiteComplex,
/**
* @ref Magnum::CubicHermiteQuaternion "CubicHermiteQuaternion". Usually
* used for spline-interpolated @ref AnimationTrackTarget::Rotation3D.
*/
CubicHermiteQuaternion
}; };
/** @debugoperatorenum{AnimationTrackType} */ /** @debugoperatorenum{AnimationTrackType} */
@ -96,49 +129,73 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AnimationTrackType value);
enum class AnimationTrackTarget: UnsignedByte { enum class AnimationTrackTarget: UnsignedByte {
/** /**
* Modifies 2D object translation. Type is usually * Modifies 2D object translation. Type is usually
* @ref Magnum::Vector2 "Vector2". * @ref Magnum::Vector2 "Vector2" or
* @ref Magnum::CubicHermite2D "CubicHermite2D" for spline-interpolated
* data.
* *
* @see @ref AnimationTrackType::Vector2, @ref ObjectData2D::translation() * @see @ref AnimationTrackType::Vector2,
* @ref AnimationTrackType::CubicHermite2D,
* @ref ObjectData2D::translation()
*/ */
Translation2D, Translation2D,
/** /**
* Modifies 3D object translation. Type is usually * Modifies 3D object translation. Type is usually
* @ref Magnum::Vector3 "Vector3". * @ref Magnum::Vector3 "Vector3" or
* @ref Magnum::CubicHermite3D "CubicHermite3D" for spline-interpolated
* data.
* *
* @see @ref AnimationTrackType::Vector3, @ref ObjectData3D::translation() * @see @ref AnimationTrackType::Vector3,
* @ref AnimationTrackType::CubicHermite3D,
* @ref ObjectData3D::translation()
*/ */
Translation3D, Translation3D,
/** /**
* Modifies 2D object rotation. Type is usually * Modifies 2D object rotation. Type is usually
* @ref Magnum::Complex "Complex". * @ref Magnum::Complex "Complex" or
* @ref Magnum::CubicHermiteComplex "CubicHermiteComplex" for
* spline-interpolated data.
* *
* @see @ref ObjectData2D::rotation() * @see @ref AnimationTrackType::Complex,
* @ref AnimationTrackType::CubicHermiteComplex,
* @ref ObjectData2D::rotation()
*/ */
Rotation2D, Rotation2D,
/** /**
* Modifies 3D object rotation. Type is usually * Modifies 3D object rotation. Type is usually
* @ref Magnum::Quaternion "Quaternion". * @ref Magnum::Quaternion "Quaternion" or
* @ref Magnum::CubicHermiteQuaternion "CubicHermiteQuaternion" for
* spline-interpolated data.
* *
* @see @ref AnimationTrackType::Quaternion, @ref ObjectData3D::rotation() * @see @ref AnimationTrackType::Quaternion,
* @ref AnimationTrackType::CubicHermiteQuaternion,
* @ref ObjectData3D::rotation()
*/ */
Rotation3D, Rotation3D,
/** /**
* Modifies 2D object scaling. Type is usually * Modifies 2D object scaling. Type is usually
* @ref Magnum::Vector2 "Vector2". * @ref Magnum::Vector2 "Vector2" or
* @ref Magnum::CubicHermite2D "CubicHermite2D" for spline-interpolated
* data.
* *
* @see @ref AnimationTrackType::Vector2, @ref ObjectData2D::scaling() * @see @ref AnimationTrackType::Vector2,
* @ref AnimationTrackType::CubicHermite2D,
* @ref ObjectData2D::scaling()
*/ */
Scaling2D, Scaling2D,
/** /**
* Modifies 3D object scaling. Type is usually * Modifies 3D object scaling. Type is usually
* @ref Magnum::Vector3 "Vector3". * @ref Magnum::Vector3 "Vector3" or
* @ref Magnum::CubicHermite3D "CubicHermite3D" for spline-interpolated
* data.
* *
* @see @ref AnimationTrackType::Vector3, @ref ObjectData3D::scaling() * @see @ref AnimationTrackType::Vector3,
* @ref AnimationTrackType::CubicHermite3D,
* @ref ObjectData3D::scaling()
*/ */
Scaling3D, Scaling3D,
@ -395,10 +452,9 @@ class MAGNUM_TRADE_EXPORT AnimationData {
/** @relatesalso AnimationData /** @relatesalso AnimationData
@brief Animation interpolator function for given interpolation behavior @brief Animation interpolator function for given interpolation behavior
To be used from importer plugins --- unlike @ref Animation::interpolatorFor() To be used from importer plugins --- wrapper around @ref Animation::interpolatorFor(),
guarantees that the returned function pointer is not instantiated inside plugin guaranteeing that the returned function pointer is not instantiated inside the
binary to avoid dangling function pointers on plugin unload. See plugin binary to avoid dangling function pointers on plugin unload.
@ref Animation::interpolatorFor() for more information.
@see @ref AnimationData @see @ref AnimationData
@experimental @experimental
*/ */
@ -441,8 +497,15 @@ namespace Implementation {
template<> constexpr AnimationTrackType animationTypeFor<Math::Vector<3, Int>>() { return AnimationTrackType::Vector3i; } template<> constexpr AnimationTrackType animationTypeFor<Math::Vector<3, Int>>() { return AnimationTrackType::Vector3i; }
template<> constexpr AnimationTrackType animationTypeFor<Math::Vector<4, Int>>() { return AnimationTrackType::Vector4i; } template<> constexpr AnimationTrackType animationTypeFor<Math::Vector<4, Int>>() { return AnimationTrackType::Vector4i; }
template<> constexpr AnimationTrackType animationTypeFor<Complex>() { return AnimationTrackType::Complex; }
template<> constexpr AnimationTrackType animationTypeFor<Quaternion>() { return AnimationTrackType::Quaternion; } template<> constexpr AnimationTrackType animationTypeFor<Quaternion>() { return AnimationTrackType::Quaternion; }
template<> constexpr AnimationTrackType animationTypeFor<DualQuaternion>() { return AnimationTrackType::DualQuaternion; } template<> constexpr AnimationTrackType animationTypeFor<DualQuaternion>() { return AnimationTrackType::DualQuaternion; }
template<> constexpr AnimationTrackType animationTypeFor<CubicHermite1D>() { return AnimationTrackType::CubicHermite1D; }
template<> constexpr AnimationTrackType animationTypeFor<CubicHermite2D>() { return AnimationTrackType::CubicHermite2D; }
template<> constexpr AnimationTrackType animationTypeFor<CubicHermite3D>() { return AnimationTrackType::CubicHermite3D; }
template<> constexpr AnimationTrackType animationTypeFor<CubicHermiteComplex>() { return AnimationTrackType::CubicHermiteComplex; }
template<> constexpr AnimationTrackType animationTypeFor<CubicHermiteQuaternion>() { return AnimationTrackType::CubicHermiteQuaternion; }
/* LCOV_EXCL_STOP */ /* LCOV_EXCL_STOP */
} }
#endif #endif

Loading…
Cancel
Save