Browse Source

Animation: make Easing a template struct, add an Easingd typedef.

Similarly to how Constants and Constantsd are made.
pull/601/head
Vladimír Vondruš 3 years ago
parent
commit
3c8fd70c12
  1. 13
      doc/changelog.dox
  2. 44
      doc/generated/easings.cpp
  3. 231
      src/Magnum/Animation/Easing.h
  4. 135
      src/Magnum/Animation/Test/EasingTest.cpp

13
doc/changelog.dox

@ -393,6 +393,13 @@ See also:
@relativeref{Timeline,previousFrameDuration()} (see
[mosra/magnum#604](https://github.com/mosra/magnum/pull/604))
@subsubsection changelog-latest-changes-animation Animation library
- Added a @ref Animation::Easingd typedef with
@ref Animation::BasicEasing "easing functions" implemented with
double precision, in addition to single-precision @ref Animation::Easing,
and similar in spirit to the @ref Constants and @ref Constantsd typedefs.
@subsubsection changelog-latest-changes-debugtools DebugTools library
- @ref DebugTools::bufferData() and @ref DebugTools::bufferSubData() now
@ -1146,6 +1153,12 @@ See also:
@ref Corrade::Containers::ArrayView are now removed. This should have a
significant positive effect on compile times of code using the @ref GL,
@ref Audio, @ref Trade and @ref Text libraries
- @ref Animation::Easing is now a typedef to a new
@ref Animation::BasicEasing struct instead of being a namespace in order to
expose the easing functions in double precision as @ref Animation::Easingd.
The change is API-compatible and shouldn't require any changes from user
side, however existing code that contained
@cpp using namespace Animation::Easing @ce will break.
- As part of the ongoing STL header dependency cleanup, the following
breaking changes related to @ref std::string, @ref std::vector and
@ref std::pair are done:

44
doc/generated/easings.cpp

@ -202,9 +202,7 @@ const Color3 success = 0x3bd267_srgbf;
/** @todo better bezier approximations for more complex curves, easings.net has it awful */
int main() {
using namespace Animation::Easing;
#define _c(name) Utility::String::lowercase(std::string{#name}), name
#define _c(name) Utility::String::lowercase(std::string{#name}), Animation::Easing::name
generate(_c(linear), {},
/* [linear] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f},
@ -212,127 +210,127 @@ CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f},
/* [linear] */
, success, success);
generate(_c(step), {}, {}, success, success);
generate(_c(smoothstep), {smootherstep},
generate(_c(smoothstep), {Animation::Easing::smootherstep},
/* [smoothstep] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f, 0.0f},
Vector2{2.0f/3.0f, 1.0f}, Vector2{1.0f}}
/* [smoothstep] */
, success, success);
generate(_c(smootherstep), {smoothstep}, {}, success, success);
generate(_c(quadraticIn), {cubicIn, quarticIn, quinticIn},
generate(_c(smootherstep), {Animation::Easing::smoothstep}, {}, success, success);
generate(_c(quadraticIn), {Animation::Easing::cubicIn, Animation::Easing::quarticIn, Animation::Easing::quinticIn},
/* [quadraticIn] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f, 0.0f},
Vector2{2.0f/3.0f, 1.0f/3.0f}, Vector2{1.0f}}
/* [quadraticIn] */
, danger, success);
generate(_c(quadraticOut), {cubicOut, quarticOut, quinticOut},
generate(_c(quadraticOut), {Animation::Easing::cubicOut, Animation::Easing::quarticOut, Animation::Easing::quinticOut},
/* [quadraticOut] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f, 2.0f/3.0f},
Vector2{2.0f/3.0f, 1.0f}, Vector2{1.0f}}
/* [quadraticOut] */
, success, danger);
generate(_c(quadraticInOut), {cubicInOut, quarticInOut, quinticInOut},
generate(_c(quadraticInOut), {Animation::Easing::cubicInOut, Animation::Easing::quarticInOut, Animation::Easing::quinticInOut},
/* [quadraticInOut] */
CubicBezier2D{Vector2{0.0f}, Vector2{0.455f, 0.0f},
Vector2{0.545f, 1.0f}, Vector2{1.0f}}
/* [quadraticInOut] */
);
generate(_c(cubicIn), {quadraticIn, quarticIn, quinticIn},
generate(_c(cubicIn), {Animation::Easing::quadraticIn, Animation::Easing::quarticIn, Animation::Easing::quinticIn},
/* [cubicIn] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f, 0.0f},
Vector2{2.0f/3.0f, 0.0f}, Vector2{1.0f}}
/* [cubicIn] */
, danger, success);
generate(_c(cubicOut), {quadraticOut, quarticOut, quinticOut},
generate(_c(cubicOut), {Animation::Easing::quadraticOut, Animation::Easing::quarticOut, Animation::Easing::quinticOut},
/* [cubicOut] */
CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f, 1.0f},
Vector2{2.0f/3.0f, 1.0f}, Vector2{1.0f}}
/* [cubicOut] */
, success, danger);
generate(_c(cubicInOut), {quadraticInOut, quarticInOut, quinticInOut},
generate(_c(cubicInOut), {Animation::Easing::quadraticInOut, Animation::Easing::quarticInOut, Animation::Easing::quinticInOut},
/* [cubicInOut] */
CubicBezier2D{Vector2{0.0f}, Vector2{0.645f, 0.0f},
Vector2{0.355f, 1.0f}, Vector2{1.0f}}
/* [cubicInOut] */
);
generate(_c(quarticIn), {quadraticIn, cubicIn, quinticIn}, {}
generate(_c(quarticIn), {Animation::Easing::quadraticIn, Animation::Easing::cubicIn, Animation::Easing::quinticIn}, {}
// ,
// /* [quarticIn] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.895f, 0.03f},
// Vector2{0.685f, 0.22f}, Vector2{1.0f}}
// /* [quarticIn] */
, danger, success);
generate(_c(quarticOut), {quadraticOut, cubicOut, quinticOut}, {}
generate(_c(quarticOut), {Animation::Easing::quadraticOut, Animation::Easing::cubicOut, Animation::Easing::quinticOut}, {}
// ,
// /* [quarticOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.165f, 0.84f},
// Vector2{0.44f, 1.0f}, Vector2{1.0f}}
// /* [quarticOut] */
, success, danger);
generate(_c(quarticInOut), {quadraticInOut, cubicInOut, quinticInOut}
generate(_c(quarticInOut), {Animation::Easing::quadraticInOut, Animation::Easing::cubicInOut, Animation::Easing::quinticInOut}
// ,
// /* [quarticInOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.77f, 0.0f},
// Vector2{0.175f, 1.0f}, Vector2{1.0f}}
// /* [quarticInOut] */
);
generate(_c(quinticIn), {quadraticIn, cubicIn, quarticIn}, {}
generate(_c(quinticIn), {Animation::Easing::quadraticIn, Animation::Easing::cubicIn, Animation::Easing::quarticIn}, {}
// ,
// /* [quinticIn] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.755f, 0.05f},
// Vector2{0.855f, 0.06f}, Vector2{1.0f}}
// /* [quinticIn] */
, danger, success);
generate(_c(quinticOut), {quadraticOut, cubicOut, quarticOut}, {}
generate(_c(quinticOut), {Animation::Easing::quadraticOut, Animation::Easing::cubicOut, Animation::Easing::quarticOut}, {}
// ,
// /* [quinticOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.23f, 1.0f},
// Vector2{0.32f, 1.0f}, Vector2{1.0f}}
// /* [quinticOut] */
, success, danger);
generate(_c(quinticInOut), {quadraticInOut, cubicInOut, quarticInOut}
generate(_c(quinticInOut), {Animation::Easing::quadraticInOut, Animation::Easing::cubicInOut, Animation::Easing::quarticInOut}
// ,
// /* [quinticInOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.86f, 0.0f},
// Vector2{0.07f, 1.0f}, Vector2{1.0f}}
// /* [quinticInOut] */
);
generate(_c(sineIn), {circularIn}
generate(_c(sineIn), {Animation::Easing::circularIn}
// ,
// /* [sineIn] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.47f, 0.0f},
// Vector2{0.745f, 0.715f}, Vector2{1.0f}}
// /* [sineIn] */
);
generate(_c(sineOut), {circularOut}
generate(_c(sineOut), {Animation::Easing::circularOut}
// ,
// /* [sineOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.39f, 0.575f},
// Vector2{0.565f, 1.0f}, Vector2{1.0f}}
// /* [sineOut] */
);
generate(_c(sineInOut), {circularInOut}
generate(_c(sineInOut), {Animation::Easing::circularInOut}
// ,
// /* [sineInOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.445f, 0.05f},
// Vector2{0.55f, 0.95f}, Vector2{1.0f}}
// /* [sineInOut] */
);
generate(_c(circularIn), {sineIn}
generate(_c(circularIn), {Animation::Easing::sineIn}
// ,
// /* [circularIn] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.6f, 0.04f},
// Vector2{0.98f, 0.335f}, Vector2{1.0f}}
// /* [circularIn] */
);
generate(_c(circularOut), {sineOut}
generate(_c(circularOut), {Animation::Easing::sineOut}
// ,
// /* [circularOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.075f, 0.085f},
// Vector2{0.165f, 1.0f}, Vector2{1.0f}}
// /* [circularOut] */
);
generate(_c(circularInOut), {sineInOut}
generate(_c(circularInOut), {Animation::Easing::sineInOut}
// ,
// /* [circularInOut] */
// CubicBezier2D{Vector2{0.0f}, Vector2{0.785f, 0.135f},

231
src/Magnum/Animation/Easing.h

@ -26,7 +26,7 @@
*/
/** @file
* @brief Namespace @ref Magnum::Animation::Easing
* @brief Struct @ref Magnum::Animation::BasicEasing, typedef @ref Magnum::Animation::Easing, @ref Magnum::Animation::Easingd
*/
#include "Magnum/Magnum.h"
@ -37,11 +37,13 @@ namespace Magnum { namespace Animation {
/**
@brief Easing functions
@m_since_latest
@m_keywords{Tweening Inbetweening}
A collection of predefined [easing / tweening](https://en.wikipedia.org/wiki/Inbetweening)
functions for adding life to animation interpolation.
functions for adding life to animation interpolation. Meant to be used through
the @ref Easing and @ref Easingd typedefs.
This library is built as part of Magnum by default. To use this library with
CMake, find the `Magnum` package and link to the `Magnum::Magnum` target:
@ -298,7 +300,7 @@ https://easings.net/,
[https://github.com/bkaradzic/bx](https://github.com/bkaradzic/bx/blob/master/include/bx/inline/easing.inl),
https://blog.demofox.org/2014/08/28/one-dimensional-bezier-curves/.
*/
namespace Easing {
template<class T> struct BasicEasing {
/**
* @brief Linear
*
@ -312,14 +314,14 @@ namespace Easing {
*
* @snippet easings.cpp linear
*/
inline Float linear(Float t) { return t; }
static T linear(T t) { return t; }
/**
* @brief Step
*
* Similar to @ref Math::select(), but does the step in the middle of the
* range instead of at the end. Implementation matching the GLSL
* @glsl step() @ce function with @cpp edge = 0.5f @ce.
* @glsl step() @ce function with @cpp edge = T(0.5) @ce.
*
* @htmlinclude easings-step.svg
*
@ -333,7 +335,7 @@ namespace Easing {
* @m_keyword{step(),GLSL step(),}
* @see @ref smoothstep(), @ref smootherstep()
*/
inline Float step(Float t) { return t < 0.5f ? 0.0f : 1.0f; }
static T step(T t) { return t < T(0.5) ? T(0.0) : T(1.0); }
/**
* @brief [Smoothstep](https://en.wikipedia.org/wiki/Smoothstep).
@ -360,12 +362,12 @@ namespace Easing {
* @m_keyword{smoothstep(),GLSL smoothstep(),}
* @see @ref smootherstep(), @ref Math::clamp()
*/
inline Float smoothstep(Float t) {
static T smoothstep(T t) {
/* Deliberately *not* using Math::clamp() because that would drag in
unneeded vector headers */
if(t <= 0.0f) return 0.0f;
if(t >= 1.0f) return 1.0f;
return (3.0f - 2.0f*t)*t*t;
if(t <= T(0.0)) return T(0.0);
if(t >= T(1.0)) return T(1.0);
return (T(3.0) - T(2.0)*t)*t*t;
}
/**
@ -385,12 +387,12 @@ namespace Easing {
*
* @see @ref Math::clamp()
*/
inline Float smootherstep(Float t) {
static T smootherstep(T t) {
/* Deliberately *not* using Math::clamp() because that would drag in
unneeded vector headers */
if(t <= 0.0f) return 0.0f;
if(t >= 1.0f) return 1.0f;
return t*t*t*(t*(t* 6.0f - 15.0f) + 10.0f);
if(t <= T(0.0)) return T(0.0);
if(t >= T(1.0)) return T(1.0);
return t*t*t*(t*(t*T(6.0) - T(15.0)) + T(10.0));
}
/**
@ -408,7 +410,7 @@ namespace Easing {
*
* @see @ref cubicIn(), @ref quarticIn(), @ref quinticIn()
*/
inline Float quadraticIn(Float t) { return t*t; }
static T quadraticIn(T t) { return t*t; }
/**
* @brief Quadratic out
@ -425,7 +427,7 @@ namespace Easing {
*
* @see @ref cubicOut(), @ref quarticOut(), @ref quinticOut()
*/
inline Float quadraticOut(Float t) { return -t*(t - 2.0f); }
static T quadraticOut(T t) { return -t*(t - T(2.0)); }
/**
* @brief Quadratic in and out
@ -447,11 +449,11 @@ namespace Easing {
*
* @see @ref cubicInOut(), @ref quarticInOut(), @ref quinticInOut()
*/
inline Float quadraticInOut(Float t) {
if(t < 0.5f) return 2.0f*t*t;
static T quadraticInOut(T t) {
if(t < T(0.5)) return T(2.0)*t*t;
const Float inv = 1.0f - t;
return 1.0f - 2.0f*inv*inv;
const T inv = T(1.0) - t;
return T(1.0) - T(2.0)*inv*inv;
}
/**
@ -469,7 +471,7 @@ namespace Easing {
*
* @see @ref quadraticIn(), @ref quarticIn(), @ref quinticIn()
*/
inline Float cubicIn(Float t) { return t*t*t; }
static T cubicIn(T t) { return t*t*t; }
/**
* @brief Cubic out
@ -486,9 +488,9 @@ namespace Easing {
*
* @see @ref quadraticOut(), @ref quarticOut(), @ref quinticOut()
*/
inline Float cubicOut(Float t) {
const Float inv = t - 1.0f;
return inv*inv*inv + 1.0f;
static T cubicOut(T t) {
const T inv = t - T(1.0);
return inv*inv*inv + T(1.0);
}
/**
@ -511,11 +513,11 @@ namespace Easing {
*
* @see @ref quadraticInOut(), @ref quarticInOut(), @ref quinticInOut()
*/
inline Float cubicInOut(Float t) {
if(t < 0.5f) return 4.0f*t*t*t;
static T cubicInOut(T t) {
if(t < T(0.5)) return T(4.0)*t*t*t;
const Float inv = 1.0f - t;
return 1.0f - 4.0f*inv*inv*inv;
const T inv = T(1.0) - t;
return T(1.0) - T(4.0)*inv*inv*inv;
}
/**
@ -529,11 +531,11 @@ namespace Easing {
*
* @see @ref quadraticIn(), @ref cubicIn(), @ref quinticIn()
*/
inline Float quarticIn(Float t) {
static T quarticIn(T t) {
/* Not just t*t*t*t, since compile can't optimize it on its own to just
two multiplication without breaking precision. So doing that
explicitly. */
const Float tt = t*t;
const T tt = t*t;
return tt*tt;
}
@ -548,11 +550,11 @@ namespace Easing {
*
* @see @ref quadraticOut(), @ref cubicOut(), @ref quinticOut()
*/
inline Float quarticOut(Float t) {
static T quarticOut(T t) {
/* Instead of t*t*t*t suggesting the optimization as described above */
const Float inv = 1.0f - t;
const Float quad = inv*inv;
return 1.0f - quad*quad;
const T inv = T(1.0) - t;
const T quad = inv*inv;
return T(1.0) - quad*quad;
}
/**
@ -571,17 +573,17 @@ namespace Easing {
*
* @see @ref quadraticInOut(), @ref cubicInOut(), @ref quinticInOut()
*/
inline Float quarticInOut(Float t) {
static T quarticInOut(T t) {
/* Instead of t*t*t*t suggesting the optimization as described above */
if(t < 0.5f) {
const Float tt = t*t;
if(t < T(0.5)) {
const T tt = t*t;
return 8*tt*tt;
}
const Float inv = 1.0f - t;
const Float quad = inv*inv;
return 1.0f - 8.0f*quad*quad;
const T inv = T(1.0) - t;
const T quad = inv*inv;
return T(1.0) - T(8.0)*quad*quad;
}
/**
@ -595,10 +597,10 @@ namespace Easing {
*
* @see @ref quadraticIn(), @ref cubicIn(), @ref quarticIn()
*/
inline Float quinticIn(Float t) {
static T quinticIn(T t) {
/* Instead of t*t*t*t*t suggesting the optimization as described
above */
const Float tt = t*t;
const T tt = t*t;
return tt*t*tt;
}
@ -613,12 +615,12 @@ namespace Easing {
*
* @see @ref quadraticOut(), @ref cubicOut(), @ref quarticOut()
*/
inline Float quinticOut(Float t) {
static T quinticOut(T t) {
/* Instead of t*t*t*t*t suggesting the optimization as described
above */
const Float inv = t - 1.0f;
const Float quad = inv*inv;
return 1.0f + quad*inv*quad;
const T inv = t - T(1.0);
const T quad = inv*inv;
return T(1.0) + quad*inv*quad;
}
/**
@ -637,18 +639,18 @@ namespace Easing {
*
* @see @ref quadraticInOut(), @ref cubicInOut(), @ref quarticInOut()
*/
inline Float quinticInOut(Float t) {
static T quinticInOut(T t) {
/* Instead of t*t*t*t*t suggesting the optimization as described
above */
if(t < 0.5f) {
const Float tt = t*t;
return 16.0f*tt*t*tt;
if(t < T(0.5)) {
const T tt = t*t;
return T(16.0)*tt*t*tt;
}
const Float inv = 1.0f - t;
const Float quad = inv*inv;
return 1.0f - 16.0f*quad*inv*quad;
const T inv = T(1.0) - t;
const T quad = inv*inv;
return T(1.0) - T(16.0)*quad*inv*quad;
}
/**
@ -662,8 +664,8 @@ namespace Easing {
*
* @see @ref circularIn()
*/
inline Float sineIn(Float t) {
return 1.0f + std::sin(Constants::piHalf()*(t - 1.0f));
static T sineIn(T t) {
return T(1.0) + std::sin(Math::Constants<T>::piHalf()*(t - T(1.0)));
}
/**
@ -677,8 +679,8 @@ namespace Easing {
*
* @see @ref circularOut()
*/
inline Float sineOut(Float t) {
return std::sin(Constants::piHalf()*t);
static T sineOut(T t) {
return std::sin(Math::Constants<T>::piHalf()*t);
}
/**
@ -694,8 +696,8 @@ namespace Easing {
*
* @see @ref circularInOut()
*/
inline Float sineInOut(Float t) {
return 0.5f*(1.0f - std::cos(t*Constants::pi()));
static T sineInOut(T t) {
return T(0.5)*(T(1.0) - std::cos(t*Math::Constants<T>::pi()));
}
/**
@ -709,8 +711,8 @@ namespace Easing {
*
* @see @ref sineIn()
*/
inline Float circularIn(Float t) {
return 1.0f - std::sqrt(1.0f - t*t);
static T circularIn(T t) {
return T(1.0) - std::sqrt(T(1.0) - t*t);
}
/**
@ -724,8 +726,8 @@ namespace Easing {
*
* @see @ref sineOut()
*/
inline Float circularOut(Float t) {
return std::sqrt((2.0f - t)*t);
static T circularOut(T t) {
return std::sqrt((T(2.0) - t)*t);
}
/**
@ -744,9 +746,9 @@ namespace Easing {
*
* @see @ref sineInOut()
*/
inline Float circularInOut(Float t) {
if(t < 0.5f) return 0.5f*(1.0f - std::sqrt(1.0f - 4.0f*t*t));
return 0.5f*(1.0f + std::sqrt(-4.0f*t*t + 8.0f*t - 3.0f));
static T circularInOut(T t) {
if(t < T(0.5)) return T(0.5)*(T(1.0) - std::sqrt(T(1.0) - T(4.0)*t*t));
return T(0.5)*(T(1.0) + std::sqrt(T(-4.0)*t*t + T(8.0)*t - T(3.0)));
}
/**
@ -768,8 +770,8 @@ namespace Easing {
* @todo could be done better like with the sRGB curve, but should I
* bother?
*/
inline Float exponentialIn(Float t) {
return t <= 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
static T exponentialIn(T t) {
return t <= T(0.0) ? T(0.0) : std::pow(T(2.0), T(10.0)*(t - T(1.0)));
}
/**
@ -791,8 +793,8 @@ namespace Easing {
* @todo could be done better like with the sRGB curve, but should I
* bother?
*/
inline Float exponentialOut(Float t) {
return t >= 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f*t);
static T exponentialOut(T t) {
return t >= T(1.0) ? T(1.0) : T(1.0) - std::pow(T(2.0), -T(10.0)*t);
}
/**
@ -814,11 +816,11 @@ namespace Easing {
* \end{cases}
* @f]
*/
inline Float exponentialInOut(Float t) {
if(t <= 0.0f) return 0.0f;
if(t < 0.5f) return 0.5f*std::pow(2.0f, 20.0f*t - 10.0f);
if(t < 1.0f) return 1.0f - 0.5f*std::pow(2.0f, 10.0f - 20.0f*t);
return 1.0f;
static T exponentialInOut(T t) {
if(t <= T(0.0)) return T(0.0);
if(t < T(0.5)) return T(0.5)*std::pow(T(2.0), T(20.0)*t - T(10.0));
if(t < T(1.0)) return T(1.0) - T(0.5)*std::pow(T(2.0), T(10.0) -T(20.0)*t);
return T(1.0);
}
/**
@ -832,8 +834,8 @@ namespace Easing {
* y = 2^{10 (p - 1)} \sin(13 \frac{\pi}{2} x)
* @f]
*/
inline Float elasticIn(Float t) {
return std::pow(2.0f, 10.0f*(t - 1.0f))*std::sin(13.0f*Constants::piHalf()*t);
static T elasticIn(T t) {
return std::pow(T(2.0), T(10.0)*(t - T(1.0)))*std::sin(T(13.0)*Math::Constants<T>::piHalf()*t);
}
/**
@ -847,8 +849,8 @@ namespace Easing {
* y = 1 - 2^{-10 x} \sin(13 \frac{\pi}{2} (x + 1))
* @f]
*/
inline Float elasticOut(Float t) {
return 1.0f - std::pow(2.0f, -10.0f*t)*std::sin(13.0f*Constants::piHalf()*(t + 1.0f));
static T elasticOut(T t) {
return T(1.0) - std::pow(T(2.0), T(-10.0)*t)*std::sin(T(13.0)*Math::Constants<T>::piHalf()*(t + T(1.0)));
}
/**
@ -866,10 +868,10 @@ namespace Easing {
* \end{cases}
* @f]
*/
inline Float elasticInOut(Float t) {
if(t < 0.5f)
return 0.5f*std::pow(2.0f, 10.0f*(2.0f*t - 1.0f))*std::sin(13.0f*Constants::pi()*t);
return 1.0f - 0.5f*std::pow(2.0f, 10.0f*(1.0f - 2.0f*t))*std::sin(13.0f*Constants::pi()*t);
static T elasticInOut(T t) {
if(t < T(0.5))
return T(0.5)*std::pow(T(2.0), T(10.0)*(T(2.0)*t - T(1.0)))*std::sin(T(13.0)*Math::Constants<T>::pi()*t);
return T(1.0) - T(0.5)*std::pow(T(2.0), T(10.0)*(T(1.0) - T(2.0)*t))*std::sin(T(13.0)*Math::Constants<T>::pi()*t);
}
/**
@ -881,8 +883,8 @@ namespace Easing {
* y = x^3 - x \sin(\pi x)
* @f]
*/
inline Float backIn(Float t) {
return t*(t*t - std::sin(Constants::pi()*t));
static T backIn(T t) {
return t*(t*t - std::sin(Math::Constants<T>::pi()*t));
}
/**
@ -894,9 +896,9 @@ namespace Easing {
* y = 1 - ((1 - x)^3 - (1 - x) \sin(\pi (1 - x)))
* @f]
*/
inline Float backOut(Float t) {
const Float inv = 1.0f - t;
return 1 - inv*(inv*inv - std::sin(Constants::pi()*inv));
static T backOut(T t) {
const T inv = T(1.0) - t;
return 1 - inv*(inv*inv - std::sin(Math::Constants<T>::pi()*inv));
}
/**
@ -915,27 +917,23 @@ namespace Easing {
* \end{cases}
* @f]
*/
inline Float backInOut(Float t) {
if(t < 0.5f) {
const Float t2 = 2.0f*t;
return 0.5f*t2*(t2*t2 - std::sin(Constants::pi()*t2));
static T backInOut(T t) {
if(t < T(0.5)) {
const T t2 = T(2.0)*t;
return T(0.5)*t2*(t2*t2 - std::sin(Math::Constants<T>::pi()*t2));
}
const Float inv = 2.0f - 2.0f*t;
return 1.0f - 0.5f*inv*(inv*inv - std::sin(Constants::pi()*inv));
const T inv = T(2.0) - T(2.0)*t;
return T(1.0) - T(0.5)*inv*(inv*inv - std::sin(Math::Constants<T>::pi()*inv));
}
#ifndef DOXYGEN_GENERATING_OUTPUT
Float bounceOut(Float);
#endif
/**
* @brief Bounce in
*
* @htmlinclude easings-bouncein.svg
*/
inline Float bounceIn(Float t) {
return 1.0f - bounceOut(1.0f - t);
static T bounceIn(T t) {
return T(1.0) - bounceOut(T(1.0) - t);
}
/**
@ -943,11 +941,11 @@ namespace Easing {
*
* @htmlinclude easings-bounceout.svg
*/
inline Float bounceOut(Float t) {
if(t < 4.0f/11.0f) return (121.0f*t*t)/16.0f;
if(t < 8.0f/11.0f) return 363.0f/40.0f*t*t - 99.0f/10.0f*t + 17.0f/5.0f;
if(t < 9.0f/10.0f) return 4356.0f/361.0f*t*t - 35442.0f/1805.0f*t + 16061.0f/1805.0f;
return 54.0f/5.0f*t*t - 513.0f/25.0f*t + 268.0f/25.0f;
static T bounceOut(T t) {
if(t < T(4.0)/T(11.0)) return (T(121.0)*t*t)/T(16.0);
if(t < T(8.0)/T(11.0)) return T(363.0)/T(40.0)*t*t - T(99.0)/T(10.0)*t + T(17.0)/T(5.0);
if(t < T(9.0)/T(10.0)) return T(4356.0)/T(361.0)*t*t - T(35442.0)/T(1805.0)*t + T(16061.0)/T(1805.0);
return T(54.0)/T(5.0)*t*t - T(513.0)/T(25.0)*t + T(268.0)/T(25.0);
}
/**
@ -957,11 +955,26 @@ namespace Easing {
*
* @htmlinclude easings-bounceinout.svg
*/
inline Float bounceInOut(Float t) {
if(t < 0.5f) return 0.5f*bounceIn(2.0f*t);
return 0.5f*bounceOut(2.0f*t - 1.0f) + 0.5f;
static T bounceInOut(T t) {
if(t < T(0.5)) return T(0.5)*bounceIn(T(2.0)*t);
return T(0.5)*bounceOut(T(2.0)*t - T(1.0)) + T(0.5);
}
}
};
/**
@brief Float easing functions
@see @ref Easingd
*/
typedef BasicEasing<Float> Easing;
/**
@brief Double easing functions
@m_since_latest
@see @ref Easing
*/
typedef BasicEasing<Double> Easingd;
}}

135
src/Magnum/Animation/Test/EasingTest.cpp

@ -30,24 +30,26 @@
#include <Corrade/Utility/Format.h>
#include "Magnum/Animation/Easing.h"
#include "Magnum/Math/TypeTraits.h"
namespace Magnum { namespace Animation { namespace Test { namespace {
struct EasingTest: TestSuite::Tester {
explicit EasingTest();
void bounds();
void monotonicity();
void symmetry();
void values();
template<class T> void bounds();
template<class T> void monotonicity();
template<class T> void symmetry();
template<class T> void values();
void benchmark();
template<class T> void benchmark();
};
#define _c(name) #name, Easing::name
constexpr struct {
#define _c(name) #name, Easing::name, Easingd::name
constexpr struct Bounds {
const char* name;
Float(*function)(Float);
Double(*functiond)(Double);
} BoundsData[] {
{_c(linear)},
{_c(step)},
@ -80,9 +82,10 @@ constexpr struct {
{_c(bounceInOut)}
};
constexpr struct {
constexpr struct Monotonicity {
const char* name;
Float(*function)(Float);
Double(*functiond)(Double);
} MonotonicityData[] {
{_c(linear)},
{_c(step)},
@ -112,11 +115,13 @@ constexpr struct {
/* elastic, back and bounce are not monotonic */
};
constexpr struct {
constexpr struct Symmetry {
const char* name;
Float(*function)(Float);
Double(*functiond)(Double);
const char* symmetricName;
Float(*symmetric)(Float);
Double(*symmetricd)(Double);
} SymmetryData[] {
{_c(linear), _c(linear)},
{_c(step), _c(step)},
@ -144,9 +149,10 @@ constexpr struct {
{_c(bounceInOut), _c(bounceInOut)}
};
constexpr struct {
constexpr struct Value {
const char* name;
Float(*function)(Float);
Double(*functiond)(Double);
Float values[3];
} ValueData[] {
{_c(linear), {0.25f, 0.5f, 0.75f}},
@ -186,91 +192,142 @@ constexpr struct {
};
#undef _c
template<class T> struct FunctionFor;
template<> struct FunctionFor<Float> {
static auto function(const Bounds& s) -> Float(*)(Float) {
return s.function;
}
static auto function(const Monotonicity& s) -> Float(*)(Float) {
return s.function;
}
static auto function(const Symmetry& s) -> Float(*)(Float) {
return s.function;
}
static auto symmetric(const Symmetry& s) -> Float(*)(Float) {
return s.symmetric;
}
static auto function(const Value& s) -> Float(*)(Float) {
return s.function;
}
};
template<> struct FunctionFor<Double> {
static auto function(const Bounds& s) -> Double(*)(Double) {
return s.functiond;
}
static auto function(const Monotonicity& s) -> Double(*)(Double) {
return s.functiond;
}
static auto function(const Symmetry& s) -> Double(*)(Double) {
return s.functiond;
}
static auto symmetric(const Symmetry& s) -> Double(*)(Double) {
return s.symmetricd;
}
static auto function(const Value& s) -> Double(*)(Double) {
return s.functiond;
}
};
EasingTest::EasingTest() {
addInstancedTests({&EasingTest::bounds},
addInstancedTests<EasingTest>({
&EasingTest::bounds<Float>,
&EasingTest::bounds<Double>},
Containers::arraySize(BoundsData));
addInstancedTests({&EasingTest::monotonicity},
addInstancedTests<EasingTest>({
&EasingTest::monotonicity<Float>,
&EasingTest::monotonicity<Double>},
Containers::arraySize(MonotonicityData));
addInstancedTests({&EasingTest::symmetry},
addInstancedTests<EasingTest>({
&EasingTest::symmetry<Float>,
&EasingTest::symmetry<Double>},
Containers::arraySize(SymmetryData));
addInstancedTests({&EasingTest::values},
addInstancedTests<EasingTest>({
&EasingTest::values<Float>,
&EasingTest::values<Double>},
Containers::arraySize(ValueData));
addInstancedBenchmarks({&EasingTest::benchmark}, 100,
addInstancedBenchmarks<EasingTest>({
&EasingTest::benchmark<Float>,
&EasingTest::benchmark<Double>}, 100,
Containers::arraySize(ValueData));
}
enum: std::size_t { PropertyVerificationStepCount = 50 };
void EasingTest::bounds() {
template<class T> void EasingTest::bounds() {
auto&& data = BoundsData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
Float scale = 1.0f/Float(PropertyVerificationStepCount - 1);
T scale = T(1.0)/T(PropertyVerificationStepCount - 1);
for(std::size_t i = 0; i != PropertyVerificationStepCount; ++i) {
Float t = i*scale;
CORRADE_COMPARE_AS(data.function(t), 0.0f, TestSuite::Compare::GreaterOrEqual);
CORRADE_COMPARE_AS(data.function(t), 1.0f, TestSuite::Compare::LessOrEqual);
T t = i*scale;
CORRADE_COMPARE_AS(FunctionFor<T>::function(data)(t), T(0.0), TestSuite::Compare::GreaterOrEqual);
CORRADE_COMPARE_AS(FunctionFor<T>::function(data)(t), T(1.0), TestSuite::Compare::LessOrEqual);
}
}
void EasingTest::monotonicity() {
template<class T> void EasingTest::monotonicity() {
auto&& data = MonotonicityData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
Float scale = 1.0f/Float(PropertyVerificationStepCount - 1);
Float prev = data.function(0);
T scale = T(1.0)/T(PropertyVerificationStepCount - 1);
T prev = FunctionFor<T>::function(data)(0);
for(std::size_t i = 1; i != PropertyVerificationStepCount; ++i) {
Float cur = data.function(i*scale);
T cur = FunctionFor<T>::function(data)(i*scale);
CORRADE_COMPARE_AS(cur, prev, TestSuite::Compare::GreaterOrEqual);
prev = cur;
}
}
void EasingTest::symmetry() {
template<class T> void EasingTest::symmetry() {
auto&& data = SymmetryData[testCaseInstanceId()];
if(data.name == Containers::StringView{data.symmetricName})
setTestCaseDescription(data.name);
else
setTestCaseDescription(Utility::format("{} : {}", data.name, data.symmetricName));
setTestCaseTemplateName(Math::TypeTraits<T>::name());
/* Not testing the edges, as these are tested in values() anyway (and are
problematic in functions that have explicit handling for them) */
Float scale = 1.0f/Float(PropertyVerificationStepCount + 1);
std::size_t max = PropertyVerificationStepCount/(data.function == data.symmetric ? 2 : 1);
T scale = T(1.0)/T(PropertyVerificationStepCount + 1);
std::size_t max = PropertyVerificationStepCount/(FunctionFor<T>::function(data) == FunctionFor<T>::symmetric(data) ? 2 : 1);
for(std::size_t i = 1; i != max; ++i) {
Float t = i*scale;
CORRADE_COMPARE(data.function(t), 1.0f - data.symmetric(1.0f - t));
T t = i*scale;
CORRADE_COMPARE(FunctionFor<T>::function(data)(t), T(1.0) - FunctionFor<T>::symmetric(data)(T(1.0) - t));
}
}
void EasingTest::values() {
template<class T> void EasingTest::values() {
auto&& data = ValueData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
CORRADE_COMPARE(data.function(0.0f), 0.0f);
CORRADE_COMPARE(data.function(1.0f), 1.0f);
CORRADE_COMPARE(data.function(0.25f), data.values[0]);
CORRADE_COMPARE(data.function(0.50f), data.values[1]);
CORRADE_COMPARE(data.function(0.75f), data.values[2]);
CORRADE_COMPARE(FunctionFor<T>::function(data)(T(0.0)), T(0.0));
CORRADE_COMPARE(FunctionFor<T>::function(data)(T(1.0)), T(1.0));
CORRADE_COMPARE(FunctionFor<T>::function(data)(T(0.25)), data.values[0]);
CORRADE_COMPARE(FunctionFor<T>::function(data)(T(0.50)), data.values[1]);
CORRADE_COMPARE(FunctionFor<T>::function(data)(T(0.75)), data.values[2]);
}
enum: Int { BenchmarkStepCount = 5000 };
void EasingTest::benchmark() {
template<class T> void EasingTest::benchmark() {
auto&& data = ValueData[testCaseInstanceId()];
setTestCaseDescription(data.name);
setTestCaseTemplateName(Math::TypeTraits<T>::name());
/* Skip edges because the cumulated number may exceed 1 (on asm.js
Emscripten), producing NaN on some functions and failing the test */
Float scale = 1.0f/Float(BenchmarkStepCount + 1);
Float result = 0.0f;
T scale = T(1.0)/T(BenchmarkStepCount + 1);
T result = T(0.0);
std::size_t i = 0;
CORRADE_BENCHMARK(BenchmarkStepCount)
result += data.function(++i*scale);
result += FunctionFor<T>::function(data)(++i*scale);
/* backIn() has -340 */
CORRADE_COMPARE_AS(result, -350.0f, TestSuite::Compare::Greater);

Loading…
Cancel
Save