diff --git a/doc/changelog.dox b/doc/changelog.dox index 074b70788..811235ec5 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -40,6 +40,15 @@ See also: @subsection changelog-latest-new New features +@subsubsection changelog-latest-new-animation Animation library + +- New @ref Animation::Easing namespace with a collection of well-documented + easing functions +- New @ref Animation::ease(), @ref Animation::easeClamped(), + @ref Animation::unpack(), @ref Animation::unpackEase() and + @ref Animation::unpackEaseClamped() utilities for combining interpolators + with easing functions and/or unpackers + @subsubsection changelog-latest-new-math Math library - Support for using the @ref Math::Deg, @ref Math::Rad, @ref Math::Half, diff --git a/doc/generated/CMakeLists.txt b/doc/generated/CMakeLists.txt index e4eaf8626..cc77c5f34 100644 --- a/doc/generated/CMakeLists.txt +++ b/doc/generated/CMakeLists.txt @@ -46,6 +46,10 @@ else() message(FATAL_ERROR "No windowless application available on this platform") endif() +add_executable(easings easings.cpp) +target_link_libraries(easings + Magnum::Magnum) + add_executable(shaders shaders.cpp) target_link_libraries(shaders Magnum::Magnum diff --git a/doc/generated/easings.cpp b/doc/generated/easings.cpp new file mode 100644 index 000000000..da9b0796a --- /dev/null +++ b/doc/generated/easings.cpp @@ -0,0 +1,390 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* + Generator for plots in the Easing namespace. Run the executable and it'll + generate a shitload of files in CWD. Copy these to doc/snippets/, replacing + the previous, and regenerate the docs using dox2html5.py. + + Note that the SVGs are crafted for inline HTML, so they won't get + recognized by vector editors. Add the XML preamble + + + + and the + + xmlns="http://www.w3.org/2000/svg" + + attribute to the element if you'd ever need that. +*/ + +#include +#include +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Math/Bezier.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Range.h" +#include "Magnum/Animation/Easing.h" + +using namespace Magnum; +using namespace Magnum::Math::Literals; + +namespace { + +constexpr Int Points = 96; +constexpr Int PointsOutside = 8; +constexpr Int EndMarkerSize = 12; +constexpr Int ExtraMargin = 32; +constexpr Vector2 Size{224, 128}; +constexpr Vector2 Border{16, 16}; +constexpr Int CircleRadius = 2; + +constexpr Int ThumbPoints = 64; +constexpr Int ThumbEndMarkerSize = 8; +constexpr Vector2 ThumbSize{128, 128}; +constexpr Vector2 ThumbBorder{0, 32}; + +void generateThumb(const std::string& file, Float(*function)(Float)) { + std::string out; + + Range2D viewBox{{}, ThumbSize}; + Range2D contentBox{ThumbBorder, ThumbSize - ThumbBorder}; + + Utility::formatInto(out, out.size(), R"( + + + +)"); + + Utility::Directory::writeString("easings-" + file + "-thumb.svg", out); +} + +void generate(const std::string& file, Float(*function)(Float), std::initializer_list related = {}, const CubicBezier2D& bezier = {}, const Color3& colorBefore = 0xcd3431_srgbf, const Color3 colorAfter = 0xcd3431_srgbf, bool extraMargin = false) { + std::string out; + + Vector2 size = Size; + Vector2 border = Border; + std::string extraStyle; + if(extraMargin) { + size.y() += 2*ExtraMargin; + border.y() += ExtraMargin; + extraStyle = " margin-top: -24px; margin-bottom: -8px;"; + } + + Range2D viewBox{{}, size}; + Range2D contentBox{border, size - border}; + + /* Background, extrapolated points */ + Utility::formatInto(out, out.size(), R"( + + + +)"); + + /* Related functions */ + auto print = [&](const Color3& color, Float strokeWidth, Float(*function)(Float)) { + Utility::formatInto(out, out.size(), R"( +)"); + }; + + for(auto fn: related) print(0x747474_srgbf, 1.0f, fn); + + /* Bezier representation, if any */ + if(!bezier[3].isZero()) { + CubicBezier2D transformed{bezier[0]*contentBox.size() + border, + bezier[1]*contentBox.size() + border, + bezier[2]*contentBox.size() + border, + bezier[3]*contentBox.size() + border}; + + /* Handle end is slightly cut to make the circle nice */ + const Vector2 end1 = transformed[1] - (transformed[1] - transformed[0]).resized(CircleRadius); + const Vector2 end2 = transformed[2] - (transformed[2] - transformed[3]).resized(CircleRadius); + + /* Handles, handle points and the curve, all smashed togetherrrr so + we can reuse the data*/ + Utility::formatInto(out, out.size(), R"( + + +)", + transformed[0][0], size.y() - transformed[0][1], + transformed[1][0], size.y() - transformed[1][1], + transformed[2][0], size.y() - transformed[2][1], + transformed[3][0], size.y() - transformed[3][1], + end1.x(), size.y() - end1.y(), + end2.x(), size.y() - end2.y()); + } + + print(0xdcdcdc_srgbf, 1.75f, function); + + Utility::formatInto(out, out.size(), R"( +)"); + + Utility::Directory::writeString("easings-" + file + ".svg", out); + + generateThumb(file, function); +} + +const Color3 danger = 0xcd3431_srgbf; +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(#name), name + generate(_c(linear), {}, +/* [linear] */ +CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f}, + Vector2{2.0f/3.0f}, Vector2{1.0f}} +/* [linear] */ + , success, success); + generate(_c(step), {}, {}, success, success); + generate(_c(smoothstep), {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}, +/* [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}, +/* [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}, +/* [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}, +/* [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}, +/* [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}, +/* [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}, {} +// , +// /* [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}, {} +// , +// /* [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} +// , +// /* [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}, {} +// , +// /* [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}, {} +// , +// /* [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} +// , +// /* [quinticInOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.86f, 0.0f}, +// Vector2{0.07f, 1.0f}, Vector2{1.0f}} +// /* [quinticInOut] */ + ); + generate(_c(sineIn), {circularIn} +// , +// /* [sineIn] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.47f, 0.0f}, +// Vector2{0.745f, 0.715f}, Vector2{1.0f}} +// /* [sineIn] */ + ); + generate(_c(sineOut), {circularOut} +// , +// /* [sineOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.39f, 0.575f}, +// Vector2{0.565f, 1.0f}, Vector2{1.0f}} +// /* [sineOut] */ + ); + generate(_c(sineInOut), {circularInOut} +// , +// /* [sineInOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.445f, 0.05f}, +// Vector2{0.55f, 0.95f}, Vector2{1.0f}} +// /* [sineInOut] */ + ); + generate(_c(circularIn), {sineIn} +// , +// /* [circularIn] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.6f, 0.04f}, +// Vector2{0.98f, 0.335f}, Vector2{1.0f}} +// /* [circularIn] */ + ); + generate(_c(circularOut), {sineOut} +// , +// /* [circularOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.075f, 0.085f}, +// Vector2{0.165f, 1.0f}, Vector2{1.0f}} +// /* [circularOut] */ + ); + generate(_c(circularInOut), {sineInOut} +// , +// /* [circularInOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.785f, 0.135f}, +// Vector2{0.15f, 0.86f}, Vector2{1.0f}} +// /* [circularInOut] */ + ); + generate(_c(exponentialIn), {}, {} +// , +// /* [exponentialIn] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.95f, 0.05f}, +// Vector2{0.795f, 0.035f}, Vector2{1.0f}} +// /* [exponentialIn] */ + , success, success); + generate(_c(exponentialOut), {}, {} +// , +// /* [exponentialOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.19f, 1.0f}, +// Vector2{0.22f, 1.0f}, Vector2{1.0f}} +// /* [exponentialOut] */ + , success, success); + generate(_c(exponentialInOut), {}, {} +// , +// /* [exponentialInOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{1.0f, 0.0f}, +// Vector2{0.0f, 1.0f}, Vector2{1.0f}} +// /* [exponentialInOut] */ + , success, success); + generate(_c(elasticIn), {}, {}, success, danger, true); + generate(_c(elasticOut), {}, {}, danger, success, true); + generate(_c(elasticInOut), {}, {}, success, success, true); + generate(_c(backIn), {}, {} +// , +// /* [backIn] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.6f, -0.28f}, +// Vector2{0.735f, 0.045f}, Vector2{1.0f}} +// /* [backIn] */ + , danger, danger, true); + generate(_c(backOut), {}, {} +// , +// /* [backOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.175f, 0.885f}, +// Vector2{0.32f, 1.275f}, Vector2{1.0f}} +// /* [backOut] */ + , danger, danger, true); + generate(_c(backInOut), {}, {} +// , +// /* [backInOut] */ +// CubicBezier2D{Vector2{0.0f}, Vector2{0.68f, -0.55f}, +// Vector2{0.265f, 1.55f}, Vector2{1.0f}} +// /* [backInOut] */ + , danger, danger, true); + generate(_c(bounceIn)); + generate(_c(bounceOut)); + generate(_c(bounceInOut)); + #undef _c +} diff --git a/doc/snippets/MagnumAnimation.cpp b/doc/snippets/MagnumAnimation.cpp index 49a5ded2c..b6f628171 100644 --- a/doc/snippets/MagnumAnimation.cpp +++ b/doc/snippets/MagnumAnimation.cpp @@ -24,7 +24,11 @@ */ #include "Magnum/Timeline.h" +#include "Magnum/Math/Bezier.h" +#include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Quaternion.h" +#include "Magnum/Math/Packing.h" +#include "Magnum/Animation/Easing.h" #include "Magnum/Animation/Player.h" using namespace Magnum; @@ -32,6 +36,101 @@ using namespace Magnum::Math::Literals; int main() { +{ +Vector3 a, b; +Float t{}; +{ +/* [ease] */ +auto lerpBounceIn = + Animation::ease(); + +Vector3 result1 = Math::lerp(a, b, Animation::Easing::bounceIn(t)); +Vector3 result2 = lerpBounceIn (a, b, t); +/* [ease] */ +static_cast(result1); +static_cast(result2); +} + +{ +/* [unpack] */ +UnsignedShort a, b; +auto lerpPacked = + Animation::unpack>(); + +Float result1 = Math::lerp(Math::unpack(a), Math::unpack(b), t); +Float result2 = lerpPacked(a, b, t); +/* [unpack] */ +static_cast(result1); +static_cast(result2); +} + +{ +/* [unpackEase] */ +UnsignedShort a, b; +auto lerpPackedBounceIn = Animation::unpackEase, Animation::Easing::bounceIn>(); + +Float result1 = Math::lerp(Math::unpack(a), Math::unpack(b), + Animation::Easing::bounceIn(t)); +Float result2 = lerpPackedBounceIn(a, b, t); +/* [unpackEase] */ +static_cast(result1); +static_cast(result2); +} +} + +{ +Vector3 a, b; +Float t{}; +{ +/* [Easing-factor] */ +Vector3 result = Math::lerp(a, b, Animation::Easing::quadraticInOut(t)); +/* [Easing-factor] */ +static_cast(result); +} + +{ +/* [Easing-ease] */ +auto lerpQuadraticInOut = + Animation::ease(); + +Vector3 result = lerpQuadraticInOut(a, b, t); +/* [Easing-ease] */ +static_cast(result); +} + +{ +/* [Easing-clamp] */ +auto lerpCircularOutClamped = Animation::easeClamped< + Vector3, Math::lerp, Animation::Easing::quadraticInOut>(); + +Vector3 result1 = Math::lerp(a, b, + Math::clamp(0.0f, 1.0f, Animation::Easing::circularOut(t))); +Vector3 result2 = lerpCircularOutClamped(a, b, t); +/* [Easing-clamp] */ +static_cast(result1); +static_cast(result2); +} + +{ +/* [Easing-bezier-transform] */ +Matrix3 transformation; +CubicBezier2D easing; +CubicBezier2D transformed{ + transformation.transformPoint(easing[0]), + transformation.transformPoint(easing[1]), + transformation.transformPoint(easing[2]), + transformation.transformPoint(easing[3])}; +/* [Easing-bezier-transform] */ +} + +{ +/* [Easing-smoothstep] */ +Math::lerp(a, b, Animation::Easing::smoothstep(t)); +/* [Easing-smoothstep] */ +} +} + { /* [Player-usage] */ const Animation::TrackView translation; diff --git a/doc/snippets/easings-backin-thumb.svg b/doc/snippets/easings-backin-thumb.svg new file mode 100644 index 000000000..46598cef6 --- /dev/null +++ b/doc/snippets/easings-backin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-backin.svg b/doc/snippets/easings-backin.svg new file mode 100644 index 000000000..8b96c5cec --- /dev/null +++ b/doc/snippets/easings-backin.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-backinout-thumb.svg b/doc/snippets/easings-backinout-thumb.svg new file mode 100644 index 000000000..f4bcfd17d --- /dev/null +++ b/doc/snippets/easings-backinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-backinout.svg b/doc/snippets/easings-backinout.svg new file mode 100644 index 000000000..e2eda5f6a --- /dev/null +++ b/doc/snippets/easings-backinout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-backout-thumb.svg b/doc/snippets/easings-backout-thumb.svg new file mode 100644 index 000000000..85e847544 --- /dev/null +++ b/doc/snippets/easings-backout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-backout.svg b/doc/snippets/easings-backout.svg new file mode 100644 index 000000000..537995dd4 --- /dev/null +++ b/doc/snippets/easings-backout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-bouncein-thumb.svg b/doc/snippets/easings-bouncein-thumb.svg new file mode 100644 index 000000000..0e96a151a --- /dev/null +++ b/doc/snippets/easings-bouncein-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-bouncein.svg b/doc/snippets/easings-bouncein.svg new file mode 100644 index 000000000..e5f3a3c4b --- /dev/null +++ b/doc/snippets/easings-bouncein.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-bounceinout-thumb.svg b/doc/snippets/easings-bounceinout-thumb.svg new file mode 100644 index 000000000..856fb819c --- /dev/null +++ b/doc/snippets/easings-bounceinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-bounceinout.svg b/doc/snippets/easings-bounceinout.svg new file mode 100644 index 000000000..aa3c84a67 --- /dev/null +++ b/doc/snippets/easings-bounceinout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-bounceout-thumb.svg b/doc/snippets/easings-bounceout-thumb.svg new file mode 100644 index 000000000..5c585e4fe --- /dev/null +++ b/doc/snippets/easings-bounceout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-bounceout.svg b/doc/snippets/easings-bounceout.svg new file mode 100644 index 000000000..3a4ce1713 --- /dev/null +++ b/doc/snippets/easings-bounceout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-circularin-thumb.svg b/doc/snippets/easings-circularin-thumb.svg new file mode 100644 index 000000000..1461ba3ea --- /dev/null +++ b/doc/snippets/easings-circularin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-circularin.svg b/doc/snippets/easings-circularin.svg new file mode 100644 index 000000000..205ad33f2 --- /dev/null +++ b/doc/snippets/easings-circularin.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-circularinout-thumb.svg b/doc/snippets/easings-circularinout-thumb.svg new file mode 100644 index 000000000..c80ff0852 --- /dev/null +++ b/doc/snippets/easings-circularinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-circularinout.svg b/doc/snippets/easings-circularinout.svg new file mode 100644 index 000000000..7633f6e5b --- /dev/null +++ b/doc/snippets/easings-circularinout.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-circularout-thumb.svg b/doc/snippets/easings-circularout-thumb.svg new file mode 100644 index 000000000..4a445b7c4 --- /dev/null +++ b/doc/snippets/easings-circularout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-circularout.svg b/doc/snippets/easings-circularout.svg new file mode 100644 index 000000000..6d2b7dddd --- /dev/null +++ b/doc/snippets/easings-circularout.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-cubicin-thumb.svg b/doc/snippets/easings-cubicin-thumb.svg new file mode 100644 index 000000000..61e26ea1e --- /dev/null +++ b/doc/snippets/easings-cubicin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-cubicin.svg b/doc/snippets/easings-cubicin.svg new file mode 100644 index 000000000..6096332b9 --- /dev/null +++ b/doc/snippets/easings-cubicin.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-cubicinout-thumb.svg b/doc/snippets/easings-cubicinout-thumb.svg new file mode 100644 index 000000000..5b5d0bed0 --- /dev/null +++ b/doc/snippets/easings-cubicinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-cubicinout.svg b/doc/snippets/easings-cubicinout.svg new file mode 100644 index 000000000..dd86f73df --- /dev/null +++ b/doc/snippets/easings-cubicinout.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-cubicout-thumb.svg b/doc/snippets/easings-cubicout-thumb.svg new file mode 100644 index 000000000..030f1cae4 --- /dev/null +++ b/doc/snippets/easings-cubicout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-cubicout.svg b/doc/snippets/easings-cubicout.svg new file mode 100644 index 000000000..346294522 --- /dev/null +++ b/doc/snippets/easings-cubicout.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-elasticin-thumb.svg b/doc/snippets/easings-elasticin-thumb.svg new file mode 100644 index 000000000..5fd0bd157 --- /dev/null +++ b/doc/snippets/easings-elasticin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-elasticin.svg b/doc/snippets/easings-elasticin.svg new file mode 100644 index 000000000..3771c0d2a --- /dev/null +++ b/doc/snippets/easings-elasticin.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-elasticinout-thumb.svg b/doc/snippets/easings-elasticinout-thumb.svg new file mode 100644 index 000000000..6a407d4ac --- /dev/null +++ b/doc/snippets/easings-elasticinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-elasticinout.svg b/doc/snippets/easings-elasticinout.svg new file mode 100644 index 000000000..d1ea1d5a3 --- /dev/null +++ b/doc/snippets/easings-elasticinout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-elasticout-thumb.svg b/doc/snippets/easings-elasticout-thumb.svg new file mode 100644 index 000000000..f0151a4b8 --- /dev/null +++ b/doc/snippets/easings-elasticout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-elasticout.svg b/doc/snippets/easings-elasticout.svg new file mode 100644 index 000000000..10a7f8f08 --- /dev/null +++ b/doc/snippets/easings-elasticout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-exponentialin-thumb.svg b/doc/snippets/easings-exponentialin-thumb.svg new file mode 100644 index 000000000..0cbc1e213 --- /dev/null +++ b/doc/snippets/easings-exponentialin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-exponentialin.svg b/doc/snippets/easings-exponentialin.svg new file mode 100644 index 000000000..d33bcdde3 --- /dev/null +++ b/doc/snippets/easings-exponentialin.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-exponentialinout-thumb.svg b/doc/snippets/easings-exponentialinout-thumb.svg new file mode 100644 index 000000000..3676e97b9 --- /dev/null +++ b/doc/snippets/easings-exponentialinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-exponentialinout.svg b/doc/snippets/easings-exponentialinout.svg new file mode 100644 index 000000000..495a191d8 --- /dev/null +++ b/doc/snippets/easings-exponentialinout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-exponentialout-thumb.svg b/doc/snippets/easings-exponentialout-thumb.svg new file mode 100644 index 000000000..ded52340a --- /dev/null +++ b/doc/snippets/easings-exponentialout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-exponentialout.svg b/doc/snippets/easings-exponentialout.svg new file mode 100644 index 000000000..c3e6c3cb5 --- /dev/null +++ b/doc/snippets/easings-exponentialout.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/doc/snippets/easings-linear-thumb.svg b/doc/snippets/easings-linear-thumb.svg new file mode 100644 index 000000000..8b60c12bd --- /dev/null +++ b/doc/snippets/easings-linear-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-linear.svg b/doc/snippets/easings-linear.svg new file mode 100644 index 000000000..cf5d8a2f4 --- /dev/null +++ b/doc/snippets/easings-linear.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quadraticin-thumb.svg b/doc/snippets/easings-quadraticin-thumb.svg new file mode 100644 index 000000000..f16e04d42 --- /dev/null +++ b/doc/snippets/easings-quadraticin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quadraticin.svg b/doc/snippets/easings-quadraticin.svg new file mode 100644 index 000000000..0155f0fe3 --- /dev/null +++ b/doc/snippets/easings-quadraticin.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-quadraticinout-thumb.svg b/doc/snippets/easings-quadraticinout-thumb.svg new file mode 100644 index 000000000..e405dcfbc --- /dev/null +++ b/doc/snippets/easings-quadraticinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quadraticinout.svg b/doc/snippets/easings-quadraticinout.svg new file mode 100644 index 000000000..5a2d428c8 --- /dev/null +++ b/doc/snippets/easings-quadraticinout.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-quadraticout-thumb.svg b/doc/snippets/easings-quadraticout-thumb.svg new file mode 100644 index 000000000..ed43578d7 --- /dev/null +++ b/doc/snippets/easings-quadraticout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quadraticout.svg b/doc/snippets/easings-quadraticout.svg new file mode 100644 index 000000000..da4bb05e3 --- /dev/null +++ b/doc/snippets/easings-quadraticout.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/doc/snippets/easings-quarticin-thumb.svg b/doc/snippets/easings-quarticin-thumb.svg new file mode 100644 index 000000000..96d4aa673 --- /dev/null +++ b/doc/snippets/easings-quarticin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quarticin.svg b/doc/snippets/easings-quarticin.svg new file mode 100644 index 000000000..c8c35fa7e --- /dev/null +++ b/doc/snippets/easings-quarticin.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quarticinout-thumb.svg b/doc/snippets/easings-quarticinout-thumb.svg new file mode 100644 index 000000000..879c4b823 --- /dev/null +++ b/doc/snippets/easings-quarticinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quarticinout.svg b/doc/snippets/easings-quarticinout.svg new file mode 100644 index 000000000..407bca004 --- /dev/null +++ b/doc/snippets/easings-quarticinout.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quarticout-thumb.svg b/doc/snippets/easings-quarticout-thumb.svg new file mode 100644 index 000000000..2d2d3f097 --- /dev/null +++ b/doc/snippets/easings-quarticout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quarticout.svg b/doc/snippets/easings-quarticout.svg new file mode 100644 index 000000000..8d0a8142e --- /dev/null +++ b/doc/snippets/easings-quarticout.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quinticin-thumb.svg b/doc/snippets/easings-quinticin-thumb.svg new file mode 100644 index 000000000..9fe9445a8 --- /dev/null +++ b/doc/snippets/easings-quinticin-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quinticin.svg b/doc/snippets/easings-quinticin.svg new file mode 100644 index 000000000..6e2f87688 --- /dev/null +++ b/doc/snippets/easings-quinticin.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quinticinout-thumb.svg b/doc/snippets/easings-quinticinout-thumb.svg new file mode 100644 index 000000000..2ea5e1c48 --- /dev/null +++ b/doc/snippets/easings-quinticinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quinticinout.svg b/doc/snippets/easings-quinticinout.svg new file mode 100644 index 000000000..ef164f823 --- /dev/null +++ b/doc/snippets/easings-quinticinout.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-quinticout-thumb.svg b/doc/snippets/easings-quinticout-thumb.svg new file mode 100644 index 000000000..f54196085 --- /dev/null +++ b/doc/snippets/easings-quinticout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-quinticout.svg b/doc/snippets/easings-quinticout.svg new file mode 100644 index 000000000..f31584013 --- /dev/null +++ b/doc/snippets/easings-quinticout.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/doc/snippets/easings-sinein-thumb.svg b/doc/snippets/easings-sinein-thumb.svg new file mode 100644 index 000000000..904aff8be --- /dev/null +++ b/doc/snippets/easings-sinein-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-sinein.svg b/doc/snippets/easings-sinein.svg new file mode 100644 index 000000000..e94049848 --- /dev/null +++ b/doc/snippets/easings-sinein.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-sineinout-thumb.svg b/doc/snippets/easings-sineinout-thumb.svg new file mode 100644 index 000000000..bf4210483 --- /dev/null +++ b/doc/snippets/easings-sineinout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-sineinout.svg b/doc/snippets/easings-sineinout.svg new file mode 100644 index 000000000..7597259d2 --- /dev/null +++ b/doc/snippets/easings-sineinout.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-sineout-thumb.svg b/doc/snippets/easings-sineout-thumb.svg new file mode 100644 index 000000000..933f7ab76 --- /dev/null +++ b/doc/snippets/easings-sineout-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-sineout.svg b/doc/snippets/easings-sineout.svg new file mode 100644 index 000000000..9170b3d04 --- /dev/null +++ b/doc/snippets/easings-sineout.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-smootherstep-thumb.svg b/doc/snippets/easings-smootherstep-thumb.svg new file mode 100644 index 000000000..8b5aa0ab0 --- /dev/null +++ b/doc/snippets/easings-smootherstep-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-smootherstep.svg b/doc/snippets/easings-smootherstep.svg new file mode 100644 index 000000000..6e7f00c3a --- /dev/null +++ b/doc/snippets/easings-smootherstep.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/doc/snippets/easings-smoothstep-thumb.svg b/doc/snippets/easings-smoothstep-thumb.svg new file mode 100644 index 000000000..dec0219ae --- /dev/null +++ b/doc/snippets/easings-smoothstep-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-smoothstep.svg b/doc/snippets/easings-smoothstep.svg new file mode 100644 index 000000000..44bffc51b --- /dev/null +++ b/doc/snippets/easings-smoothstep.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/doc/snippets/easings-step-thumb.svg b/doc/snippets/easings-step-thumb.svg new file mode 100644 index 000000000..d087cbf19 --- /dev/null +++ b/doc/snippets/easings-step-thumb.svg @@ -0,0 +1,6 @@ + + + + diff --git a/doc/snippets/easings-step.svg b/doc/snippets/easings-step.svg new file mode 100644 index 000000000..bcd39355c --- /dev/null +++ b/doc/snippets/easings-step.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/Magnum/Animation/CMakeLists.txt b/src/Magnum/Animation/CMakeLists.txt index 0b8a316d6..92c80d69d 100644 --- a/src/Magnum/Animation/CMakeLists.txt +++ b/src/Magnum/Animation/CMakeLists.txt @@ -25,6 +25,7 @@ set(MagnumAnimation_HEADERS Animation.h + Easing.h Interpolation.h Player.h Player.hpp diff --git a/src/Magnum/Animation/Easing.h b/src/Magnum/Animation/Easing.h new file mode 100644 index 000000000..d96939639 --- /dev/null +++ b/src/Magnum/Animation/Easing.h @@ -0,0 +1,947 @@ +#ifndef Magnum_Animation_Easing_h +#define Magnum_Animation_Easing_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Namespace @ref Magnum::Animation::Easing + */ + +#include "Magnum/Magnum.h" +#include "Magnum/Math/Constants.h" +#include "Magnum/Animation/Animation.h" + +namespace Magnum { namespace Animation { + +/** +@brief Easing functions + +A collection of predefined [easing / tweening](https://en.wikipedia.org/wiki/Inbetweening) +functions for adding life to animation interpolation. + +@m_class{m-row m-container-inflate} + +@parblock + +@m_div{m-col-l-2 m-push-l-2 m-col-m-3 m-col-t-6 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-linear-thumb.svg +@ref linear() @m_class{m-label m-primary} **B** @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-push-l-2 m-col-m-3 m-col-t-6 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-step-thumb.svg +@ref step() @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-push-l-2 m-col-m-3 m-col-t-6 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-smoothstep-thumb.svg +@ref smoothstep() @m_class{m-label m-primary} **B** @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-push-l-2 m-col-m-3 m-col-t-6 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-smootherstep-thumb.svg +@ref smootherstep() @m_class{m-label m-success} **E** +@m_enddiv + +@endparblock + +@m_class{m-row m-container-inflate} + +@parblock + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quadraticin-thumb.svg +@ref quadraticIn() @m_class{m-label m-primary} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quadraticout-thumb.svg +@ref quadraticOut() @m_class{m-label m-primary} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quadraticinout-thumb.svg +@ref quadraticInOut() @m_class{m-label m-info} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-cubicin-thumb.svg +@ref cubicIn() @m_class{m-label m-primary} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-cubicout-thumb.svg +@ref cubicOut() @m_class{m-label m-primary} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-cubicinout-thumb.svg +@ref cubicInOut() @m_class{m-label m-info} **B** @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quarticin-thumb.svg +@ref quarticIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quarticout-thumb.svg +@ref quarticOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quarticinout-thumb.svg +@ref quarticInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quinticin-thumb.svg +@ref quinticIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quinticout-thumb.svg +@ref quinticOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-quinticinout-thumb.svg +@ref quinticInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-sinein-thumb.svg +@ref sineIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-sineout-thumb.svg +@ref sineOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-sineinout-thumb.svg +@ref sineInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-circularin-thumb.svg +@ref circularIn() +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-circularout-thumb.svg +@ref circularOut() +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-circularinout-thumb.svg +@ref circularInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-exponentialin-thumb.svg +@ref exponentialIn() @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-exponentialout-thumb.svg +@ref exponentialOut() @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-exponentialinout-thumb.svg +@ref exponentialInOut() @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-elasticin-thumb.svg +@ref elasticIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-elasticout-thumb.svg +@ref elasticOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-elasticinout-thumb.svg +@ref elasticInOut() @m_class{m-label m-success} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-backin-thumb.svg +@ref backIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-backout-thumb.svg +@ref backOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-backinout-thumb.svg +@ref backInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-bouncein-thumb.svg +@ref bounceIn() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-bounceout-thumb.svg +@ref bounceOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@m_div{m-col-l-2 m-col-t-4 m-text-center m-nopadt m-nopadx} +@htmlinclude easings-bounceinout-thumb.svg +@ref bounceInOut() @m_class{m-label m-danger} **E** +@m_enddiv + +@endparblock + +The easing function is meant to be used to modify the interpolation factor, such as: + +@snippet MagnumAnimation.cpp Easing-factor + +The @ref Animation library also provides the @ref ease() utility that combines +the interpolator together with the easing function: + +@snippet MagnumAnimation.cpp Easing-ease + +@section Animation-Easing-equations Equations + +Every function documentation shows a plot of its behavior, together with a +direction in which it extrapolates. Green color means the extrapolation goes in +a reasonable monotonic direction outside of the range (also denoted by +@m_class{m-label m-success} **E** in the above list), red color means the +extrapolation is defined, but behaves in a probably unwanted or non-monotonic +way (denoted by @m_class{m-label m-danger} **E** in the above list). If neither +is present, it means the function is not defined outside of the @f$ [ 0; 1 ] @f$ +range and produces a NaN. You may want to ensure the factor stays in bounds, +using either @ref Math::clamp() or the @ref easeClamped() function --- the +following two expressions are equivalent: + +@snippet MagnumAnimation.cpp Easing-clamp + +Out-function @f$ f_\text{out} @f$ for a corresponding in-function @f$ f_\text{in} @f$ +is defined as the following, the equations in the docs usually just show the +final derived form. Similarly goes for combined in-/out-function +@f$ f_\text{inout} @f$: @f[ + \begin{array}{rcl} + f_\text{out}(x) & = & 1 - f_\text{in}(1 - x) \\[5pt] + f_\text{inout}(x) & = & \left. \begin{cases} + \frac{1}{2} f_\text{in}(2x), & x < 0.5 \\ + \frac{1}{2} (1 + f_\text{out}(2x - 1)), & x \ge 0.5 + \end{cases} \right\} = \begin{cases} + \frac{1}{2} f_\text{in}(2x), & x < 0.5 \\ + 1 - \frac{1}{2} f_\text{in}(2 - 2x), & x \ge 0.5 + \end{cases} + \end{array} +@f] + +Easing functions defined by simple polynomials can have an *exact* (denoted +by @m_class{m-label m-primary} **B** in the above list) or approximate (denoted +by @m_class{m-label m-info} **B** in the above list) cubic @ref Math::Bezier +curve representation (and thus, in turn, convertible to Cubic Hermite splines +using @ref Math::CubicHermite::fromBezier()). If that's the case, given +function documentation also lists the corresponding Bézier representation and +plots it with a thin blue line. The curve is always normalized to go from +@f$ (0, 0)^T @f$ to @f$ (1, 1)^T @f$, apply arbitrary transformation to each +point as needed: + +@snippet MagnumAnimation.cpp Easing-bezier-transform + +@section Animation-Easing-references References + +Functions follow the common naming from Robert Penner's Easing functions, +http://robertpenner.com/easing/. Implementation based on and inspired by +https://easings.net/, +[https://github.com/warrenm/AHEasing](https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c), +[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 { + +/** +@brief Linear + +@htmlinclude easings-linear.svg + +@f[ + y = x +@f] + +One possible *exact* Bézier representation: + +@snippet easings.cpp linear + +*/ +inline Float linear(Float 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. + +@htmlinclude easings-step.svg + +@f[ + y = \begin{cases} + 0, & x < 0.5 \\ + 1, & x \ge 0.5 + \end{cases} +@f] + +@m_keyword{step(),GLSL step(),} +@see @ref smoothstep(), @ref smootherstep() +*/ +inline Float step(Float t) { return t < 0.5f ? 0.0f : 1.0f; } + +/** +@brief [Smoothstep](https://en.wikipedia.org/wiki/Smoothstep). + +Implementation matching the GLSL @glsl smoothstep() @ce function. Combine with +@ref Math::lerp() to get the equivalent result: + +@snippet MagnumAnimation.cpp Easing-smoothstep + +@htmlinclude easings-smoothstep.svg + +@f[ + y = \begin{cases} + 0, & x < 0 \\ + 3x^2 - 2x^3, & x \in [0, 1] \\ + 1, & x > 1 + \end{cases} +@f] + +* *Exact* Bézier representation: + +@snippet easings.cpp smoothstep + +@m_keyword{smoothstep(),GLSL smoothstep(),} +@see @ref smootherstep(), @ref Math::clamp() +*/ +inline Float smoothstep(Float 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; +} + +/** +@brief [Smootherstep](https://en.wikipedia.org/wiki/Smoothstep#Variations). + +Improved version of @ref smoothstep() by [Ken Perlin](https://en.wikipedia.org/wiki/Ken_Perlin). + +@htmlinclude easings-smootherstep.svg + +@f[ + y = \begin{cases} + 0, & x < 0 \\ + 6x^5 - 15x^4 + 10x^3, & x \in [0, 1] \\ + 1, & x > 1 + \end{cases} +@f] + +@see @ref Math::clamp() +*/ +inline Float smootherstep(Float 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); +} + +/** +@brief Quadratic in + +@htmlinclude easings-quadraticin.svg + +@f[ + y = x^2 +@f] + +* *Exact* Bézier representation: + +@snippet easings.cpp quadraticIn + +@see @ref cubicIn(), @ref quarticIn(), @ref quinticIn() +*/ +inline Float quadraticIn(Float t) { return t*t; } + +/** +@brief Quadratic out + +@htmlinclude easings-quadraticout.svg + +@f[ + y = 1 - (1 - x)^2 = (2 - x) x +@f] + +* *Exact* Bézier representation: + +@snippet easings.cpp quadraticOut + +@see @ref cubicOut(), @ref quarticOut(), @ref quinticOut() +*/ +inline Float quadraticOut(Float t) { return -t*(t - 2.0f); } + +/** +@brief Quadratic in and out + +Combination of @ref quadraticIn() and @ref quadraticOut(). + +@htmlinclude easings-quadraticinout.svg + +@f[ + y = \begin{cases} + 2 x^2, & x < 0.5 \\ + 1 - 2 (1 - x)^2, & x \ge 0.5 + \end{cases} +@f] + +Approximate Bézier representation: + +@snippet easings.cpp quadraticInOut + +@see @ref cubicInOut(), @ref quarticInOut(), @ref quinticInOut() +*/ +inline Float quadraticInOut(Float t) { + if(t < 0.5f) return 2.0f*t*t; + + const Float inv = 1.0f - t; + return 1.0f - 2.0f*inv*inv; +} + +/** +@brief Cubic in + +@htmlinclude easings-cubicin.svg + +@f[ + y = x^3 +@f] + +* *Exact* Bézier representation: + +@snippet easings.cpp cubicIn + +@see @ref quadraticIn(), @ref quarticIn(), @ref quinticIn() +*/ +inline Float cubicIn(Float t) { return t*t*t; } + +/** +@brief Cubic out + +@htmlinclude easings-cubicout.svg + +@f[ + y = 1 - (1 - x)^3 +@f] + +* *Exact* Bézier representation: + +@snippet easings.cpp cubicOut + +@see @ref quadraticOut(), @ref quarticOut(), @ref quinticOut() +*/ +inline Float cubicOut(Float t) { + const Float inv = t - 1.0f; + return inv*inv*inv + 1.0f; +} + +/** +@brief Cubic in and out + +Combination of @ref cubicIn() and @ref cubicOut(). + +@htmlinclude easings-cubicinout.svg + +@f[ + y = \begin{cases} + 4 x^3, & x < 0.5 \\ + 1 - 4 (1 - x)^3, & x \ge 0.5 + \end{cases} +@f] + +Approximate Bézier representation: + +@snippet easings.cpp cubicInOut + +@see @ref quadraticInOut(), @ref quarticInOut(), @ref quinticInOut() +*/ +inline Float cubicInOut(Float t) { + if(t < 0.5f) return 4.0f*t*t*t; + + const Float inv = 1.0f - t; + return 1.0f - 4.0f*inv*inv*inv; +} + +/** +@brief Quartic in + +@htmlinclude easings-quarticin.svg + +@f[ + y = x^4 +@f] + +@see @ref quadraticIn(), @ref cubicIn(), @ref quinticIn() +*/ +inline Float quarticIn(Float 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; + return tt*tt; +} + +/** +@brief Quartic out + +@htmlinclude easings-quarticout.svg + +@f[ + y = 1 - (1 - x)^4 +@f] + +@see @ref quadraticOut(), @ref cubicOut(), @ref quinticOut() +*/ +inline Float quarticOut(Float 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; +} + +/** +@brief Quartic in and out + +Combination of @ref quarticIn() and @ref quarticOut(). + +@htmlinclude easings-quarticinout.svg + +@f[ + y = \begin{cases} + 8 x^4, & x < 0.5 \\ + 1 - 8 (1 - x)^4, & x \ge 0.5 + \end{cases} +@f] + +@see @ref quadraticInOut(), @ref cubicInOut(), @ref quinticInOut() +*/ +inline Float quarticInOut(Float t) { + /* Instead of t*t*t*t suggesting the optimization as described above */ + + if(t < 0.5f) { + const Float 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; +} + +/** +@brief Quintic in + +@htmlinclude easings-quinticin.svg + +@f[ + y = x^5 +@f] + +@see @ref quadraticIn(), @ref cubicIn(), @ref quarticIn() +*/ +inline Float quinticIn(Float t) { + /* Instead of t*t*t*t*t suggesting the optimization as described above */ + const Float tt = t*t; + return tt*t*tt; +} + +/** +@brief Quintic out + +@htmlinclude easings-quinticout.svg + +@f[ + y = 1 - (1 - x)^5 +@f] + +@see @ref quadraticOut(), @ref cubicOut(), @ref quarticOut() +*/ +inline Float quinticOut(Float 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; +} + +/** +@brief Quintic in and out + +Combination of @ref quinticIn() and @ref quinticOut(). + +@htmlinclude easings-quinticinout.svg + +@f[ + y = \begin{cases} + 16 x^5, & x < 0.5 \\ + 1 - 16 (1 - x)^5, & x \ge 0.5 + \end{cases} +@f] + +@see @ref quadraticInOut(), @ref cubicInOut(), @ref quarticInOut() +*/ +inline Float quinticInOut(Float 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; + } + + const Float inv = 1.0f - t; + const Float quad = inv*inv; + return 1.0f - 16.0f*quad*inv*quad; +} + +/** +@brief Sine in + +@htmlinclude easings-sinein.svg + +@f[ + y = 1 + \sin(\frac{\pi}{2} (x - 1)) +@f] + +@see @ref circularIn() +*/ +inline Float sineIn(Float t) { + return 1.0f + std::sin(Constants::piHalf()*(t - 1.0f)); +} + +/** +@brief Sine out + +@htmlinclude easings-sineout.svg + +@f[ + y = \sin(\frac{\pi}{2} x) +@f] + +@see @ref circularOut() +*/ +inline Float sineOut(Float t) { + return std::sin(Constants::piHalf()*t); +} + +/** +@brief Sine in and out + +Combination of @ref sineIn() and @ref sineOut(). + +@htmlinclude easings-sineinout.svg + +@f[ + y = \frac{1}{2} (1 - \cos(\pi x)) +@f] + +@see @ref circularInOut() +*/ +inline Float sineInOut(Float t) { + return 0.5f*(1.0f - std::cos(t*Constants::pi())); +} + +/** +@brief Circular in + +@htmlinclude easings-circularin.svg + +@f[ + y = 1 - \sqrt{1 - x^2} +@f] + +@see @ref sineIn() +*/ +inline Float circularIn(Float t) { + return 1.0f - std::sqrt(1.0f - t*t); +} + +/** +@brief Circular out + +@htmlinclude easings-circularout.svg + +@f[ + y = \sqrt{(2 - x) x} +@f] + +@see @ref sineOut() +*/ +inline Float circularOut(Float t) { + return std::sqrt((2.0f - t)*t); +} + +/** +@brief Circular in and out + +Combination of @ref circularIn() and @ref circularOut(). + +@htmlinclude easings-circularinout.svg + +@f[ + y = \begin{cases} + \frac{1}{2} (1 - \sqrt{1 - (2x)^2}), & x < 0.5 \\ + \frac{1}{2} (1 + \sqrt{1 - (2x - 2)^2}), & x \ge 0.5 + \end{cases} +@f] + +@see @ref sineInOut() +*/ +inline Float circularInOut(Float t) { + if(t < 0.5f) return 0.5f*(1.0f - std::sqrt(1.0f - 4*t*t)); + return 0.5f*(1.0f + std::sqrt(-4.0f*t*t + 8.0f*t - 3.0f)); +} + +/** +@brief Exponential in + +Contrary to Robert Penner's book but consistently with other implementations +has a special case for @f$ x \le 0 @f$, because @f$ 2^{-10} = 0.0009765625 @f$ +otherwise. + +@htmlinclude easings-exponentialin.svg + +@f[ + y = \begin{cases} + 0, & x \le 0 \\ + 2^{10(x - 1)}, & x \ne 0 + \end{cases} +@f] + +@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)); +} + +/** +@brief Exponential out + +Contrary to Robert Penner's book but consistently with other implementations +has a special case for @f$ x \ge 1 @f$, because @f$ 2^{-10} = 0.0009765625 @f$ +otherwise. + +@htmlinclude easings-exponentialout.svg + +@f[ + y = \begin{cases} + 2^{-10 x}, & x < 1 \\ + 1, & x \ge 1 + \end{cases} +@f] + +@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); +} + +/** +@brief Exponential in and out + +Combination of @ref exponentialIn() and @ref exponentialOut(). Contrary to +Robert Penner's book but consistently with other implementations has a special +case for @f$ x \notin \{0, 1\} @f$, because @f$ 2^{-10} = 0.0009765625 @f$ +otherwise. + +@htmlinclude easings-exponentialinout.svg + +@f[ + y = \begin{cases} + 0, & x \le 0 \\ + \frac{1}{2} 2^{20 x - 10}, & x \in (0, 0.5) \\ + 1 - \frac{1}{2} 2^{10 - 20 x}, & x \in [0.5, 1) \\ + 1, & x \ge 1 + \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; +} + +/** +@brief Elastic in + +Combines @ref sineIn() and @ref exponentialIn(). + +@htmlinclude easings-elasticin.svg + +@f[ + 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); +} + +/** +@brief Elastic out + +Combines @ref sineOut() and @ref exponentialOut(). + +@htmlinclude easings-elasticout.svg + +@f[ + 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)); +} + +/** +@brief Elastic in and out + +Combination of @ref elasticIn() and @ref elasticOut() (or @ref sineInOut() and +@ref exponentialInOut()). + +@htmlinclude easings-elasticinout.svg + +@f[ + y = \begin{cases} + \frac{1}{2} 2^{10 (2x - 1)} \sin(13 \pi x), & x < 0.5 \\ + 1 - \frac{1}{2} 2^{10 (1 - 2x)} \sin(13 \pi x), & x \ge 0.5 + \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); +} + +/** +@brief Back in + +@htmlinclude easings-backin.svg + +@f[ + y = x^3 - x \sin(\pi x) +@f] +*/ +inline Float backIn(Float t) { + return t*(t*t - std::sin(Constants::pi()*t)); +} + +/** +@brief Back out + +@htmlinclude easings-backout.svg + +@f[ + 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)); +} + +/** +@brief Back in and out + +Combination of @ref backIn() and @ref backOut(). + +@htmlinclude easings-backinout.svg + +@f[ + y = \begin{cases} + \frac{1}{2} ((2x)^3 - 2x \sin(2 \pi x)), & x < 0.5 \\ + 1 - \frac{1}{2} ((2 - 2x)^3 - (2 - 2x) \sin(\pi (2 - 2x)), & x \ge 0.5 + \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)); + } + + const Float inv = 2.0f - 2.0f*t; + return 1.0f - 0.5f*inv*(inv*inv - std::sin(Constants::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); +} + +/** +@brief Bounce out + +@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; +} + +/** +@brief Bounce in and out + +Combination of @ref bounceIn() and @ref bounceOut(). + +@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; +} + +}}} + +#endif diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index d93195717..7bd440cd2 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Alias @ref Magnum::Animation::ResultOf, enum @ref Magnum::Animation::Extrapolation, function @ref Magnum::Animation::interpolate(), @ref Magnum::Animation::interpolateStrict() + * @brief Alias @ref Magnum::Animation::ResultOf, enum @ref Magnum::Animation::Interpolation. @ref Magnum::Animation::Extrapolation, function @ref Magnum::Animation::interpolatorFor(), @ref Magnum::Animation::interpolate(), @ref Magnum::Animation::interpolateStrict(), @ref Magnum::Animation::ease(), @ref Magnum::Animation::easeClamped() @ref Magnum::Animation::unpack(), @ref Magnum::Animation::unpackEase(), @ref Magnum::Animation::unpackEaseClamped() */ #include @@ -223,6 +223,72 @@ Used internally from @ref Track::atStrict() / @ref TrackView::atStrict(), see */ template> R interpolateStrict(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, R(*interpolator)(const V&, const V&, Float), K frame, std::size_t& hint); +/** +@brief Combine easing function and an interpolator + +Useful to create a new function out of one of the interpolators from +@ref transformations-interpolation and an easing function from @ref Easing. For +example, the following two expressions give the same result: + +@snippet MagnumAnimation.cpp ease + +@see @ref unpack(), @ref unpackEase() +*/ +template(*interpolator)(const V&, const V&, Float), Float(*easer)(Float)> constexpr auto ease() -> ResultOf(*)(const V&, const V&, Float) { + return [](const V& a, const V& b, Float t) { return interpolator(a, b, easer(t)); }; +} + +/** +@brief Combine easing function and an interpolator + +In addition to @ref ease() clamps value coming to @p easer to range +@f$ [0 ; 1] @f$. Useful when extrapolating using @ref Easing functions that +have bad behavior outside of this range. +*/ +template(*interpolator)(const V&, const V&, Float), Float(*easer)(Float)> constexpr auto easeClamped() -> ResultOf(*)(const V&, const V&, Float) { + return [](const V& a, const V& b, Float t) { return interpolator(a, b, easer(Math::clamp(t, 0.0f, 1.0f))); }; +} + +/** +@brief Combine unpacking function and an interpolator + +Similar to @ref ease(), but for adding an unpacker function to interpolator +inputs instead of modifying the interpolator phase. The following two +expressions give the same result: + +@snippet MagnumAnimation.cpp unpack + +@see @ref unpackEase() +*/ +template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&)> constexpr auto unpack() -> ResultOf(*)(const V&, const V&, Float) { + return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), t); }; +} + +/** +@brief Combine unpacking and easing functions with an interpolator + +Combination of @ref ease() and @ref unpack(), creating a function that first +unpack the interpolator inputs, then modifies the interpolator phase and +finally passes that to the interpolator function. The following two expressions +give the same result: + +@snippet MagnumAnimation.cpp unpackEase +*/ +template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&), Float(*easer)(Float)> constexpr auto unpackEase() -> ResultOf(*)(const V&, const V&, Float) { + return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), easer(t)); }; +} + +/** +@brief Combine easing function and an interpolator + +In addition to @ref unpackEase() clamps value coming to @p easer to range +@f$ [0 ; 1] @f$. Useful when extrapolating with @ref Easing functions that have +bad behavior outside of this range. +*/ +template(*interpolator)(const V&, const V&, Float), V(*unpacker)(const T&), Float(*easer)(Float)> constexpr auto unpackEaseClamped() -> ResultOf(*)(const V&, const V&, Float) { + return [](const V& a, const V& b, Float t) { return interpolator(unpacker(a), unpacker(b), easer(Math::clamp(t, 0.0f, 1.0f))); }; +} + namespace Implementation { /* Generic types where result type is the same as value type */ diff --git a/src/Magnum/Animation/Test/CMakeLists.txt b/src/Magnum/Animation/Test/CMakeLists.txt index d6c58d1a1..689a50e06 100644 --- a/src/Magnum/Animation/Test/CMakeLists.txt +++ b/src/Magnum/Animation/Test/CMakeLists.txt @@ -24,6 +24,7 @@ # corrade_add_test(AnimationBenchmark Benchmark.cpp LIBRARIES Magnum) +corrade_add_test(AnimationEasingTest EasingTest.cpp LIBRARIES Magnum) corrade_add_test(AnimationInterpolationTest InterpolationTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(AnimationPlayerTest PlayerTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(AnimationPlayerCustomTest PlayerCustomTest.cpp LIBRARIES MagnumTestLib) diff --git a/src/Magnum/Animation/Test/EasingTest.cpp b/src/Magnum/Animation/Test/EasingTest.cpp new file mode 100644 index 000000000..44007266f --- /dev/null +++ b/src/Magnum/Animation/Test/EasingTest.cpp @@ -0,0 +1,289 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#include "Magnum/Animation/Easing.h" + +namespace Magnum { namespace Animation { namespace Test { + +struct EasingTest: TestSuite::Tester { + explicit EasingTest(); + + void bounds(); + void monotonicity(); + void symmetry(); + void values(); + + void benchmark(); +}; + +namespace { + +#define _c(name) #name, Easing::name +constexpr struct { + const char* name; + Float(*function)(Float); +} BoundsData[] { + {_c(linear)}, + {_c(step)}, + {_c(smoothstep)}, + {_c(smootherstep)}, + {_c(quadraticIn)}, + {_c(quadraticOut)}, + {_c(quadraticInOut)}, + {_c(cubicIn)}, + {_c(cubicOut)}, + {_c(cubicInOut)}, + {_c(quarticIn)}, + {_c(quarticOut)}, + {_c(quarticInOut)}, + {_c(quinticIn)}, + {_c(quinticOut)}, + {_c(quinticInOut)}, + {_c(sineIn)}, + {_c(sineOut)}, + {_c(sineInOut)}, + {_c(circularIn)}, + {_c(circularOut)}, + {_c(circularInOut)}, + {_c(exponentialIn)}, + {_c(exponentialOut)}, + {_c(exponentialInOut)}, + /* elastic and back are out of [0, 1] bounds */ + {_c(bounceIn)}, + {_c(bounceOut)}, + {_c(bounceInOut)} +}; + +constexpr struct { + const char* name; + Float(*function)(Float); +} MonotonicityData[] { + {_c(linear)}, + {_c(step)}, + {_c(smoothstep)}, + {_c(smootherstep)}, + {_c(quadraticIn)}, + {_c(quadraticOut)}, + {_c(quadraticInOut)}, + {_c(cubicIn)}, + {_c(cubicOut)}, + {_c(cubicInOut)}, + {_c(quarticIn)}, + {_c(quarticOut)}, + {_c(quarticInOut)}, + {_c(quinticIn)}, + {_c(quinticOut)}, + {_c(quinticInOut)}, + {_c(sineIn)}, + {_c(sineOut)}, + {_c(sineInOut)}, + {_c(circularIn)}, + {_c(circularOut)}, + {_c(circularInOut)}, + {_c(exponentialIn)}, + {_c(exponentialOut)}, + {_c(exponentialInOut)} + /* elastic, back and bounce are not monotonic */ +}; + +constexpr struct { + const char* name; + Float(*function)(Float); + const char* symmetricName; + Float(*symmetric)(Float); +} SymmetryData[] { + {_c(linear), _c(linear)}, + {_c(step), _c(step)}, + {_c(smoothstep), _c(smoothstep)}, + {_c(smootherstep), _c(smootherstep)}, + {_c(quadraticIn), _c(quadraticOut)}, + {_c(quadraticInOut), _c(quadraticInOut)}, + {_c(cubicIn), _c(cubicOut)}, + {_c(cubicInOut), _c(cubicInOut)}, + {_c(quarticIn), _c(quarticOut)}, + {_c(quarticInOut), _c(quarticInOut)}, + {_c(quinticIn), _c(quinticOut)}, + {_c(quinticInOut), _c(quinticInOut)}, + {_c(sineIn), _c(sineOut)}, + {_c(sineInOut), _c(sineInOut)}, + {_c(circularIn), _c(circularOut)}, + {_c(circularInOut), _c(circularInOut)}, + {_c(exponentialIn), _c(exponentialOut)}, + {_c(exponentialInOut), _c(exponentialInOut)}, + {_c(elasticIn), _c(elasticOut)}, + {_c(elasticInOut), _c(elasticInOut)}, + {_c(backIn), _c(backOut)}, + {_c(backInOut), _c(backInOut)}, + {_c(bounceIn), _c(bounceOut)}, + {_c(bounceInOut), _c(bounceInOut)} +}; + +constexpr struct { + const char* name; + Float(*function)(Float); + Float values[3]; +} ValueData[] { + {_c(linear), {0.25f, 0.5f, 0.75f}}, + {_c(step), {0.0f, 1.0f, 1.0f}}, + {_c(smoothstep), {0.15625f, 0.5f, 0.84375f}}, + {_c(smootherstep), {0.103516f, 0.5f, 0.896484f}}, + {_c(quadraticIn), {0.0625f, 0.25f, 0.5625f}}, + {_c(quadraticOut), {0.4375f, 0.75f, 0.9375f}}, + {_c(quadraticInOut), {0.125f, 0.5f, 0.875f}}, + {_c(cubicIn), {0.015625f, 0.125f, 0.421875f}}, + {_c(cubicOut), {0.578125f, 0.875f, 0.984375f}}, + {_c(cubicInOut), {0.0625f, 0.5f, 0.9375f}}, + {_c(quarticIn), {0.00390625f, 0.0625f, 0.316406f}}, + {_c(quarticOut), {0.683594f, 0.9375f, 0.996094f}}, + {_c(quarticInOut), {0.03125f, 0.5f, 0.96875f}}, + {_c(quinticIn), {0.000976562f, 0.03125f, 0.237305f}}, + {_c(quinticOut), {0.762695f, 0.96875f, 0.999023f}}, + {_c(quinticInOut), {0.015625f, 0.5f, 0.984375f}}, + {_c(sineIn), {0.0761205f, 0.292893f, 0.617317f}}, + {_c(sineOut), {0.382683f, 0.707107f, 0.92388f}}, + {_c(sineInOut), {0.146447f, 0.5f, 0.853553f}}, + {_c(circularIn), {0.0317541f, 0.133975f, 0.338562f}}, + {_c(circularOut), {0.661438f, 0.866025f, 0.968246f}}, + {_c(circularInOut), {0.0669873f, 0.5f, 0.933013f}}, + {_c(exponentialIn), {0.00552427f, 0.03125f, 0.176777f}}, + {_c(exponentialOut), {0.823223f, 0.96875f, 0.994476f}}, + {_c(exponentialInOut), {0.015625f, 0.5f, 0.984375f}}, + {_c(elasticIn), {-0.00510376f, -0.0220971f, 0.0676494f}}, + {_c(elasticOut), {0.932351f, 1.022097f, 1.005104f}}, + {_c(elasticInOut), {-0.0110485f, 0.5f, 1.01105f}}, + {_c(backIn), {-0.161152f, -0.375f, -0.108455f}}, + {_c(backOut), {1.108455f, 1.375f, 1.161152f}}, + {_c(backInOut), {-0.1875f, 0.5f, 1.1875f}}, + {_c(bounceIn), {0.0411367f, 0.28125f, 0.527344f}}, + {_c(bounceOut), {0.472656f, 0.71875f, 0.958863f}}, + {_c(bounceInOut), {0.140625f, 0.5f, 0.859375f}} +}; +#undef _c + +} + +EasingTest::EasingTest() { + addInstancedTests({&EasingTest::bounds}, + Containers::arraySize(BoundsData)); + + addInstancedTests({&EasingTest::monotonicity}, + Containers::arraySize(MonotonicityData)); + + addInstancedTests({&EasingTest::symmetry}, + Containers::arraySize(SymmetryData)); + + addInstancedTests({&EasingTest::values}, + Containers::arraySize(ValueData)); + + addInstancedBenchmarks({&EasingTest::benchmark}, 100, + Containers::arraySize(ValueData)); +} + +namespace { + constexpr std::size_t PropertyVerificationStepCount = 50; +} + +void EasingTest::bounds() { + auto&& data = BoundsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Float scale = 1.0f/Float(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); + } +} + +void EasingTest::monotonicity() { + auto&& data = MonotonicityData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Float scale = 1.0f/Float(PropertyVerificationStepCount - 1); + Float prev = data.function(0); + for(std::size_t i = 1; i != PropertyVerificationStepCount; ++i) { + Float cur = data.function(i*scale); + CORRADE_COMPARE_AS(cur, prev, TestSuite::Compare::GreaterOrEqual); + prev = cur; + } +} + +void EasingTest::symmetry() { + auto&& data = SymmetryData[testCaseInstanceId()]; + if(std::strcmp(data.name, data.symmetricName) == 0) + setTestCaseDescription(data.name); + else + setTestCaseDescription(Utility::formatString("{} : {}", data.name, data.symmetricName)); + + /* 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); + 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)); + } +} + +void EasingTest::values() { + auto&& data = ValueData[testCaseInstanceId()]; + setTestCaseDescription(data.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]); +} + +namespace { + constexpr Int BenchmarkStepCount = 5000; +} + +void EasingTest::benchmark() { + auto&& data = ValueData[testCaseInstanceId()]; + setTestCaseDescription(data.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; + std::size_t i = 0; + CORRADE_BENCHMARK(BenchmarkStepCount) + result += data.function(++i*scale); + + /* backIn() has -340 */ + CORRADE_COMPARE_AS(result, -350.0f, TestSuite::Compare::Greater); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Animation::Test::EasingTest) diff --git a/src/Magnum/Animation/Test/InterpolationTest.cpp b/src/Magnum/Animation/Test/InterpolationTest.cpp index b9406ad15..5b7d8f5e9 100644 --- a/src/Magnum/Animation/Test/InterpolationTest.cpp +++ b/src/Magnum/Animation/Test/InterpolationTest.cpp @@ -26,6 +26,7 @@ #include #include +#include "Magnum/Animation/Easing.h" #include "Magnum/Animation/Interpolation.h" #include "Magnum/Math/Complex.h" #include "Magnum/Math/CubicHermite.h" @@ -65,6 +66,12 @@ struct InterpolationTest: TestSuite::Tester { void interpolateIntegerKey(); void interpolateStrictIntegerKey(); + void ease(); + void easeClamped(); + void unpack(); + void unpackEase(); + void unpackEaseClamped(); + void debugInterpolation(); void debugExtrapolation(); }; @@ -175,6 +182,12 @@ InterpolationTest::InterpolationTest() { &InterpolationTest::interpolateIntegerKey, &InterpolationTest::interpolateStrictIntegerKey, + &InterpolationTest::ease, + &InterpolationTest::easeClamped, + &InterpolationTest::unpack, + &InterpolationTest::unpackEase, + &InterpolationTest::unpackEaseClamped, + &InterpolationTest::debugInterpolation, &InterpolationTest::debugExtrapolation}); } @@ -506,6 +519,54 @@ void InterpolationTest::interpolateStrictError() { "Animation::interpolateStrict(): keys and values don't have the same size\n"); } +void InterpolationTest::ease() { + auto lerpQuadratic = Animation::ease(); + + CORRADE_COMPARE(Math::lerp(0.5f, 0.95f, Easing::quadraticIn(0.3f)), 0.5405f); + CORRADE_COMPARE(lerpQuadratic(0.5f, 0.95f, 0.3f), 0.5405f); +} + +void InterpolationTest::easeClamped() { + auto lerpBackInClamped = Animation::easeClamped(); + + /* Verify it doesn't return garbage outside the range */ + CORRADE_COMPARE(lerpBackInClamped(0.5f, 0.95f, -0.3f), 0.5f); + CORRADE_COMPARE(lerpBackInClamped(0.5f, 0.95f, 1.3f), 0.95f); + + /* Verify it doesn't clamp the easer output (should be less than 0.5) */ + CORRADE_COMPARE(Math::lerp(0.5f, 0.95f, Easing::backIn(0.3f)), 0.402933f); + CORRADE_COMPARE(lerpBackInClamped(0.5f, 0.95f, 0.3f), 0.402933f); +} + +void InterpolationTest::unpack() { + auto lerpPacked = Animation::unpack>(); + + CORRADE_COMPARE(Math::lerp(Math::unpack(32767), Math::unpack(62258), 0.3f), 0.634994f); + CORRADE_COMPARE(lerpPacked(32767, 62258, 0.3f), 0.634994f); +} + +void InterpolationTest::unpackEase() { + auto lerpPackedQuadratic = Animation::unpackEase, Easing::quadraticIn>(); + + /* Some minor imprecision compared to ease() due to lossy packing */ + CORRADE_COMPARE(Math::lerp(Math::unpack(32767), Math::unpack(62258), Easing::quadraticIn(0.3f)), 0.540493f); + CORRADE_COMPARE(lerpPackedQuadratic(32767, 62258, 0.3f), 0.540493f); +} + +void InterpolationTest::unpackEaseClamped() { + auto lerpPackedBackInClamped = Animation::unpackEaseClamped, Easing::backIn>(); + + /* Some minor imprecision compared to easeClamped() due to lossy packing */ + + /* Verify it doesn't return garbage outside the range */ + CORRADE_COMPARE(lerpPackedBackInClamped(32767, 62258, -0.3f), 0.499992f); + CORRADE_COMPARE(lerpPackedBackInClamped(32767, 62258, 1.3f), 0.949996f); + + /* Verify it doesn't clamp the easer output (should be less than 0.5) */ + CORRADE_COMPARE(Math::lerp(Math::unpack(32767), Math::unpack(62258), Easing::backIn(0.3f)), 0.402924f); + CORRADE_COMPARE(lerpPackedBackInClamped(32767, 62258, 0.3f), 0.402924f); +} + void InterpolationTest::debugInterpolation() { std::ostringstream out; diff --git a/src/Magnum/Math/Packing.h b/src/Magnum/Math/Packing.h index e3db308c1..7bb1f9811 100644 --- a/src/Magnum/Math/Packing.h +++ b/src/Magnum/Math/Packing.h @@ -84,14 +84,14 @@ Float b = Math::unpack(8191); // 1.0f */ template inline FloatingPoint unpack(const Integral& value); #else -template inline typename std::enable_if::value && std::is_unsigned::value, FloatingPoint>::type unpack(Integral value) { +template inline typename std::enable_if::value && std::is_unsigned::value, FloatingPoint>::type unpack(const Integral& value) { static_assert(std::is_floating_point::value && std::is_integral::value, "unpacking must be done from integral to floating-point type"); static_assert(bits <= sizeof(Integral)*8, "bit count larger than size of the integral type"); return value/FloatingPoint(Implementation::bitMax()); } -template inline typename std::enable_if::value && std::is_signed::value, FloatingPoint>::type unpack(Integral value) { +template inline typename std::enable_if::value && std::is_signed::value, FloatingPoint>::type unpack(const Integral& value) { static_assert(std::is_floating_point::value && std::is_integral::value, "unpacking must be done from integral to floating-point type"); static_assert(bits <= sizeof(Integral)*8,