diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index 523fdb599..7d1044408 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -40,6 +40,7 @@ endif() add_library(snippets-Magnum STATIC Magnum.cpp + MagnumAnimation.cpp MagnumMath.cpp) target_link_libraries(snippets-Magnum PRIVATE Magnum) set_target_properties(snippets-Magnum PROPERTIES FOLDER "Magnum/doc/snippets") diff --git a/doc/snippets/MagnumAnimation.cpp b/doc/snippets/MagnumAnimation.cpp new file mode 100644 index 000000000..e1761108d --- /dev/null +++ b/doc/snippets/MagnumAnimation.cpp @@ -0,0 +1,102 @@ +/* + 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 "Magnum/Math/Quaternion.h" +#include "Magnum/Animation/Track.h" + +using namespace Magnum; +using namespace Magnum::Math::Literals; + +int main() { +{ +/* [Track-usage] */ +const Animation::Track jump{{ + {0.0f, Vector2::yAxis(0.0f)}, + {1.0f, Vector2::yAxis(0.5f)}, + {2.0f, Vector2::yAxis(0.75f)}, + {3.0f, Vector2::yAxis(0.875f)}, + {4.0f, Vector2::yAxis(0.75f)}, + {5.0f, Vector2::yAxis(0.5f)}, + {6.0f, Vector2::yAxis(0.0f)} +}, Math::lerp, Animation::Extrapolation::Constant}; + +Vector2 position = jump.at(2.2f); // y = 0.775 +/* [Track-usage] */ + +{ +/* [Track-performance-hint] */ +std::size_t hint = 0; +Vector2 position = jump.at(2.2f, hint); // y = 0.775, hint = 2 +/* [Track-performance-hint] */ +static_cast(position); +} + +{ +/* [Track-performance-strict] */ +std::size_t hint = 0; +Vector2 position = jump.atStrict(2.2f, hint); // y = 0.775, hint = 2 +/* [Track-performance-strict] */ +static_cast(position); +} + +static_cast(position); +} + +{ +/* [Track-performance-cache] */ +struct Keyframe { + Float time; + Vector2 position; + Deg rotation; +}; +const Keyframe data[]{ + {0.0f, Vector2::yAxis(0.0f), 0.0_degf}, + {1.0f, Vector2::yAxis(0.5f), 60.0_degf}, + {2.0f, Vector2::yAxis(0.75f), 80.0_degf}, + {3.0f, Vector2::yAxis(0.875f), 90.0_degf}, + {4.0f, Vector2::yAxis(0.75f), 100.0_degf}, + {5.0f, Vector2::yAxis(0.5f), 120.0_degf}, + {6.0f, Vector2::yAxis(0.0f), 180.0_degf} +}; + +Animation::TrackView positions{ + {&data[0].time, Containers::arraySize(data), sizeof(Keyframe)}, + {&data[0].position, Containers::arraySize(data), sizeof(Keyframe)}, + Math::lerp}; +Animation::TrackView rotations{ + {&data[0].time, Containers::arraySize(data), sizeof(Keyframe)}, + {&data[0].rotation, Containers::arraySize(data), sizeof(Keyframe)}, + Math::lerp}; + +Float time = 2.2f; +std::size_t hint = 0; +Vector2 position = positions.atStrict(time, hint); // y = 0.775f +Deg rotation = rotations.atStrict(time, hint); // φ = 82° +/* [Track-performance-cache] */ +static_cast(position); +static_cast(rotation); +} + +} diff --git a/src/Magnum/Animation/Animation.h b/src/Magnum/Animation/Animation.h index cb4978be9..dd580cbb4 100644 --- a/src/Magnum/Animation/Animation.h +++ b/src/Magnum/Animation/Animation.h @@ -41,6 +41,9 @@ template using ResultOf = typename Implementation::TypeTraits::Resul enum class Extrapolation: UnsignedByte; +template class Track; +template class TrackView; + }} #endif diff --git a/src/Magnum/Animation/CMakeLists.txt b/src/Magnum/Animation/CMakeLists.txt index 364dbaa21..1bd1d535a 100644 --- a/src/Magnum/Animation/CMakeLists.txt +++ b/src/Magnum/Animation/CMakeLists.txt @@ -25,7 +25,8 @@ set(MagnumAnimation_HEADERS Animation.h - Interpolation.h) + Interpolation.h + Track.h) # Force IDEs to display all header files in project view add_custom_target(MagnumAnimation SOURCES ${MagnumAnimation_HEADERS}) diff --git a/src/Magnum/Animation/Interpolation.h b/src/Magnum/Animation/Interpolation.h index 7341f7cb2..d9f09345d 100644 --- a/src/Magnum/Animation/Interpolation.h +++ b/src/Magnum/Animation/Interpolation.h @@ -63,7 +63,8 @@ template using ResultOf = typename Implementation::TypeTraits::Resul Describes what value is returned for frames outside of keyframe range for given track (frame lower than first keyframe or frame larger or equal to last keyframe). -@see @ref interpolate() +@see @ref interpolate(), @ref Track::before(), @ref Track::after(), + @ref TrackView::before(), @ref TrackView::after() @experimental */ enum class Extrapolation: UnsignedByte { @@ -120,6 +121,10 @@ calculated interpolation factor, returning the interpolated value. The @p hint parameter hints where to start the linear search and is updated with keyframe index matching @p frame. If @p frame is earlier than @p hint, the search is restarted from the beginning. + +Used internally from @ref Track::at() / @ref TrackView::at(), see @ref Track +documentation for more information. + @see @ref interpolateStrict(), @ref Math::select(), @ref Math::lerp(), @ref Math::slerp(), @ref Math::sclerp() @experimental @@ -139,6 +144,10 @@ restarted from the beginning. This is a stricter but more performant version of @ref interpolate() with implicit @ref Extrapolation::Extrapolated behavior. Expects that there are always at least two keyframes. + +Used internally from @ref Track::atStrict() / @ref TrackView::atStrict(), see +@ref Track documentation for more information. + @see @ref Math::select(), @ref Math::lerp(), @ref Math::slerp(), @ref Math::sclerp() @experimental diff --git a/src/Magnum/Animation/Test/CMakeLists.txt b/src/Magnum/Animation/Test/CMakeLists.txt index 22d5235a8..7c5cc8dfe 100644 --- a/src/Magnum/Animation/Test/CMakeLists.txt +++ b/src/Magnum/Animation/Test/CMakeLists.txt @@ -24,6 +24,8 @@ # corrade_add_test(AnimationInterpolationTest InterpolationTest.cpp LIBRARIES Magnum) +corrade_add_test(AnimationTrackTest TrackTest.cpp LIBRARIES Magnum) +corrade_add_test(AnimationTrackViewTest TrackViewTest.cpp LIBRARIES Magnum) set_property(TARGET AnimationInterpolationTest @@ -31,4 +33,6 @@ set_property(TARGET set_target_properties( AnimationInterpolationTest + AnimationTrackTest + AnimationTrackViewTest PROPERTIES FOLDER "Magnum/Animation/Test") diff --git a/src/Magnum/Animation/Test/TrackTest.cpp b/src/Magnum/Animation/Test/TrackTest.cpp new file mode 100644 index 000000000..d511dcb8d --- /dev/null +++ b/src/Magnum/Animation/Test/TrackTest.cpp @@ -0,0 +1,223 @@ +/* + 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 "Magnum/Animation/Track.h" +#include "Magnum/Math/Vector3.h" + +namespace Magnum { namespace Animation { namespace Test { + +struct TrackTest: TestSuite::Tester { + explicit TrackTest(); + + void constructArray(); + void constructArrayDefaults(); + void constructInitializerList(); + void constructInitializerListDefaults(); + + void convertView(); + + void at(); + void atStrict(); +}; + +namespace { + +/* Reduced version from InterpolateTest, keep in sync with TrackViewTest */ +const struct { + const char* name; + Extrapolation extrapolationBefore; + Extrapolation extrapolationAfter; + Float time; + Float expectedValue, expectedValueStrict; + std::size_t expectedHint; +} AtData[] { + {"before default-constructed", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + -1.0f, 0.0f, 4.0f, 0}, + {"before constant", + Extrapolation::Constant, Extrapolation::Extrapolated, + -1.0f, 3.0f, 4.0f, 0}, + {"before extrapolated", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + -1.0f, 4.0f, 4.0f, 0}, + {"during first", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 1.5f, 1.5f, 1.5f, 0}, + {"during second", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 4.75f, 1.0f, 1.0f, 2}, + {"after default-constructed", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + 6.0f, 0.0f, -1.5f, 2}, + {"after constant", + Extrapolation::Extrapolated, Extrapolation::Constant, + 6.0f, 0.5f, -1.5f, 2}, + {"after extrapolated", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + 6.0f, -1.5f, -1.5f, 2} +}; + +} + +TrackTest::TrackTest() { + addTests({&TrackTest::constructArray, + &TrackTest::constructArrayDefaults, + &TrackTest::constructInitializerList, + &TrackTest::constructInitializerListDefaults, + + &TrackTest::convertView}); + + addInstancedTests({&TrackTest::at, + &TrackTest::atStrict}, Containers::arraySize(AtData)); +} + +using namespace Math::Literals; + +void TrackTest::constructArray() { + const Track a{ + Containers::Array>{Containers::InPlaceInit, + {{0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}}}}, + Math::select, Extrapolation::Extrapolated, Extrapolation::Constant}; + + CORRADE_COMPARE(a.interpolator(), Math::select); + CORRADE_COMPARE(a.before(), Extrapolation::Extrapolated); + CORRADE_COMPARE(a.after(), Extrapolation::Constant); + CORRADE_COMPARE(a.data().size(), 2); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); + CORRADE_COMPARE(a.data()[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); + CORRADE_COMPARE(a.keys()[1], 5.0f); + CORRADE_COMPARE(a.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); +} + +void TrackTest::constructArrayDefaults() { + const Track a{ + Containers::Array>{Containers::InPlaceInit, + {{0.0f, {3.0f, 1.0f, 0.1f}}}}, + Math::lerp, Extrapolation::DefaultConstructed}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(a.after(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(a.data().size(), 1); + CORRADE_COMPARE(a.keys().size(), 1); + CORRADE_COMPARE(a.values().size(), 1); + CORRADE_COMPARE(a[0], (std::pair{0.0f, {3.0f, 1.0f, 0.1f}})); + CORRADE_COMPARE(a.data()[0], (std::pair{0.0f, {3.0f, 1.0f, 0.1f}})); + CORRADE_COMPARE(a.keys()[0], 0.0f); + CORRADE_COMPARE(a.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); +} + +void TrackTest::constructInitializerList() { + const Track a{ + {{0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}}}, + Math::select, Extrapolation::Extrapolated, Extrapolation::DefaultConstructed}; + + CORRADE_COMPARE(a.interpolator(), Math::select); + CORRADE_COMPARE(a.before(), Extrapolation::Extrapolated); + CORRADE_COMPARE(a.after(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(a.data().size(), 2); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); + CORRADE_COMPARE(a.data()[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); + CORRADE_COMPARE(a.keys()[1], 5.0f); + CORRADE_COMPARE(a.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); +} + +void TrackTest::constructInitializerListDefaults() { + const Track a{{{0.0f, {3.0f, 1.0f, 0.1f}}}, + Math::lerp, Extrapolation::Constant}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::Constant); + CORRADE_COMPARE(a.after(), Extrapolation::Constant); + CORRADE_COMPARE(a.data().size(), 1); + CORRADE_COMPARE(a.keys().size(), 1); + CORRADE_COMPARE(a.values().size(), 1); + CORRADE_COMPARE(a[0], (std::pair{0.0f, {3.0f, 1.0f, 0.1f}})); + CORRADE_COMPARE(a.data()[0], (std::pair{0.0f, {3.0f, 1.0f, 0.1f}})); + CORRADE_COMPARE(a.keys()[0], 0.0f); + CORRADE_COMPARE(a.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); +} + +void TrackTest::convertView() { + const Track a{ + {{0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}}}, + Math::select, Extrapolation::Extrapolated, Extrapolation::DefaultConstructed}; + const TrackView av = a; + + CORRADE_COMPARE(av.interpolator(), Math::select); + CORRADE_COMPARE(av.before(), Extrapolation::Extrapolated); + CORRADE_COMPARE(av.after(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(av.keys().size(), 2); + CORRADE_COMPARE(av.values().size(), 2); + CORRADE_COMPARE(av[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); + CORRADE_COMPARE(av.keys()[1], 5.0f); + CORRADE_COMPARE(av.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); +} + +void TrackTest::at() { + const auto& data = AtData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + const Track a{ + {{0.0f, 3.0f}, + {2.0f, 1.0f}, + {4.0f, 2.5f}, + {5.0f, 0.5f}}, Math::lerp, + data.extrapolationBefore, data.extrapolationAfter}; + + std::size_t hint{}; + CORRADE_COMPARE(a.at(data.time, hint), data.expectedValue); + CORRADE_COMPARE(a.at(data.time), data.expectedValue); + CORRADE_COMPARE(hint, data.expectedHint); +} + +void TrackTest::atStrict() { + const auto& data = AtData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + const Track a{ + {{0.0f, 3.0f}, + {2.0f, 1.0f}, + {4.0f, 2.5f}, + {5.0f, 0.5f}}, Math::lerp, + data.extrapolationBefore, data.extrapolationAfter}; + + std::size_t hint{}; + CORRADE_COMPARE(a.atStrict(data.time, hint), data.expectedValueStrict); + CORRADE_COMPARE(hint, data.expectedHint); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Animation::Test::TrackTest) diff --git a/src/Magnum/Animation/Test/TrackViewTest.cpp b/src/Magnum/Animation/Test/TrackViewTest.cpp new file mode 100644 index 000000000..4c2de191d --- /dev/null +++ b/src/Magnum/Animation/Test/TrackViewTest.cpp @@ -0,0 +1,193 @@ +/* + 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 "Magnum/Animation/Track.h" +#include "Magnum/Math/Vector3.h" + +namespace Magnum { namespace Animation { namespace Test { + +struct TrackViewTest: TestSuite::Tester { + explicit TrackViewTest(); + + void construct(); + void constructDefaults(); + void constructSingleArray(); + void constructSingleArrayDefaults(); + + void at(); + void atStrict(); +}; + +namespace { + +/* Reduced version from InterpolateTest, keep in sync with TrackTest */ +const struct { + const char* name; + Extrapolation extrapolationBefore; + Extrapolation extrapolationAfter; + Float time; + Float expectedValue, expectedValueStrict; + std::size_t expectedHint; +} AtData[] { + {"before default-constructed", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + -1.0f, 0.0f, 4.0f, 0}, + {"before constant", + Extrapolation::Constant, Extrapolation::Extrapolated, + -1.0f, 3.0f, 4.0f, 0}, + {"before extrapolated", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + -1.0f, 4.0f, 4.0f, 0}, + {"during first", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 1.5f, 1.5f, 1.5f, 0}, + {"during second", + Extrapolation::DefaultConstructed, Extrapolation::DefaultConstructed, + 4.75f, 1.0f, 1.0f, 2}, + {"after default-constructed", + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed, + 6.0f, 0.0f, -1.5f, 2}, + {"after constant", + Extrapolation::Extrapolated, Extrapolation::Constant, + 6.0f, 0.5f, -1.5f, 2}, + {"after extrapolated", + Extrapolation::DefaultConstructed, Extrapolation::Extrapolated, + 6.0f, -1.5f, -1.5f, 2} +}; + +} + +TrackViewTest::TrackViewTest() { + addTests({&TrackViewTest::construct, + &TrackViewTest::constructDefaults, + &TrackViewTest::constructSingleArray, + &TrackViewTest::constructSingleArrayDefaults}); + + addInstancedTests({&TrackViewTest::at, + &TrackViewTest::atStrict}, Containers::arraySize(AtData)); +} + +using namespace Math::Literals; + +void TrackViewTest::construct() { + constexpr Float keys[]{0.0f, 5.0f}; + constexpr Vector3 values[]{{3.0f, 1.0f, 0.1f}, {0.3f, 0.6f, 1.0f}}; + + const TrackView a{keys, values, Math::lerp, + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::Extrapolated); + CORRADE_COMPARE(a.after(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); +} + +void TrackViewTest::constructDefaults() { + constexpr Float keys[]{0.0f, 5.0f}; + constexpr Vector3 values[]{{3.0f, 1.0f, 0.1f}, {0.3f, 0.6f, 1.0f}}; + + const TrackView a{keys, values, Math::lerp, + Extrapolation::Constant}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::Constant); + CORRADE_COMPARE(a.after(), Extrapolation::Constant); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); +} + +void TrackViewTest::constructSingleArray() { + const std::pair data[]{ + {0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}}}; + + const TrackView a{data, Math::lerp, + Extrapolation::Extrapolated, Extrapolation::DefaultConstructed}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::Extrapolated); + CORRADE_COMPARE(a.after(), Extrapolation::DefaultConstructed); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); +} + +void TrackViewTest::constructSingleArrayDefaults() { + const std::pair data[]{ + {0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}}}; + + const TrackView a{data, Math::lerp, + Extrapolation::Constant}; + + CORRADE_COMPARE(a.interpolator(), Math::lerp); + CORRADE_COMPARE(a.before(), Extrapolation::Constant); + CORRADE_COMPARE(a.after(), Extrapolation::Constant); + CORRADE_COMPARE(a.keys().size(), 2); + CORRADE_COMPARE(a.values().size(), 2); + CORRADE_COMPARE(a[1], (std::pair{5.0f, {0.3f, 0.6f, 1.0f}})); +} + +namespace { + const std::pair Keyframes[]{ + {0.0f, 3.0f}, + {2.0f, 1.0f}, + {4.0f, 2.5f}, + {5.0f, 0.5f}}; +} + +void TrackViewTest::at() { + const auto& data = AtData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + const TrackView a{Keyframes, Math::lerp, + data.extrapolationBefore, data.extrapolationAfter}; + + std::size_t hint{}; + CORRADE_COMPARE(a.at(data.time, hint), data.expectedValue); + CORRADE_COMPARE(a.at(data.time), data.expectedValue); + CORRADE_COMPARE(hint, data.expectedHint); +} + +void TrackViewTest::atStrict() { + const auto& data = AtData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + const TrackView a{Keyframes, Math::lerp, + data.extrapolationBefore, data.extrapolationAfter}; + + std::size_t hint{}; + CORRADE_COMPARE(a.atStrict(data.time, hint), data.expectedValueStrict); + CORRADE_COMPARE(hint, data.expectedHint); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Animation::Test::TrackViewTest) diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h new file mode 100644 index 000000000..2e9d6e197 --- /dev/null +++ b/src/Magnum/Animation/Track.h @@ -0,0 +1,390 @@ +#ifndef Magnum_Animation_Track_h +#define Magnum_Animation_Track_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Animation::Track, @ref Magnum::Animation::TrackView + */ + +#include + +#include "Magnum/Animation/Animation.h" +#include "Magnum/Animation/Interpolation.h" + +namespace Magnum { namespace Animation { + +/** +@brief Animation track +@tparam K Key type +@tparam V Value type + +Immutable storage of keyframe + value pairs. + +@section Animation-Track-usage Basic usage + +Animation track is defined by a list of keyframes (time+value pairs), +interpolator function and extrapolation behavior. + +@snippet MagnumAnimation.cpp Track-usage + +@section Animation-Track-interpolators Types and interpolators + +The track supports arbitrary types for keys, values and interpolators. These +are common combinations: + +@m_class{m-fullwidth} + +Interpolation type | Value type | Result type | Interpolator +------------------- | ----------------- | ------------- | ------------ +Constant | any `V` | `V` | @ref Math::select() +Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() +Linear | @ref Math::BoolVector | @ref Math::BoolVector | @ref Math::select() +Linear | any scalar `V` | `V` | @ref Math::lerp() +Linear | any vector `V` | `V` | @ref Math::lerp() +Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()" +Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()" +Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()" + +@section Animation-Track-performance Performance tuning + +The snippet shown above is convenience-oriented at a cost of sacrificing some +performance. You have the following options: + +@subsection Animation-Track-performance-hint Keyframe hinting + +The @ref Track and @ref TrackView classes are fully stateless and the +@ref at(K) const function performs a linear search for matching keyframe from +the beginning every time. You can use @ref at(K, std::size_t&) const to +remember last used keyframe index and pass it in the next iteration as a hint: + +@snippet MagnumAnimation.cpp Track-performance-hint + +@subsection Animation-Track-performance-strict Strict interpolation + +While it's possible to have different @ref Extrapolation modes for frames +outside of the track range with graceful handling of single- or zero-frame +animations, the additional checks have some impact. The @ref atStrict() has +implicit @ref Extrapolation::Extrapolated behavior and assumes there are always +at least two keyframes, resulting in more compact interpolation code. If your +animation data satisfy the prerequisites, simply use it in place of @ref at(): + +@snippet MagnumAnimation.cpp Track-performance-strict + +@subsection Animation-Track-performance-cache Cache-efficient data layout + +Usually multiple tracks (translation, rotation, scaling) are combined together +to form a single animation. In order to achieve better data layout, consider +interleaving the data and passing them using +@ref Corrade::Containers::StridedArrayView to multiple @ref TrackView +instead of having data duplicated scattered across disjoint allocations of +@ref Track instances: + +@snippet MagnumAnimation.cpp Track-performance-cache + +@experimental +*/ +template class Track { + public: + /** @brief Key type */ + typedef K KeyType; + + /** @brief Value type */ + typedef V ValueType; + + /** @brief Animation result type */ + typedef ResultOf ResultType; + + /** @brief Interpolation function */ + typedef ResultType(*Interpolator)(const ValueType&, const ValueType&, Float); + + /** + * @brief Constructor + * @param data Keyframe data + * @param interpolator Interpolator function + * @param before Extrapolation behavior + * @param after Extrapolation behavior after + * + * The keyframe data are assumed to be stored in sorted order. It's not + * an error to have two successive keyframes with the same frame value. + */ + explicit Track(Containers::Array>&& data, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: _data{std::move(data)}, _interpolator{interpolator}, _before{before}, _after{after} {} + + /** @overload */ + explicit Track(std::initializer_list> data, Interpolator interpolator, Extrapolation before, Extrapolation after): Track{Containers::Array>{Containers::InPlaceInit, data}, interpolator, before, after} {} + + /** @overload + * Equivalent to calling @ref Track(Containers::Array>&&, Interpolator, Extrapolation, Extrapolation) + * with both @p before and @p after set to @p extrapolation. + */ + explicit Track(Containers::Array>&& data, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant) noexcept: Track{std::move(data), interpolator, extrapolation, extrapolation} {} + + /** @overload */ + explicit Track(std::initializer_list> data, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant): Track{Containers::Array>{Containers::InPlaceInit, data}, interpolator, extrapolation} {} + + /** @brief Copying is not allowed */ + Track(const Track&) = delete; + + /** @brief Move constructor */ + Track(Track&&) = default; + + /** @brief Copying is not allowed */ + Track& operator=(const Track&) = delete; + + /** @brief Move constructor */ + Track& operator=(Track&&) = default; + + /** @brief Conversion to a view */ + operator TrackView() const noexcept { + return TrackView{_data, _interpolator, _before, _after}; + } + + /** @brief Interpolation function */ + Interpolator interpolator() const { return _interpolator; } + + /** + * @brief Extrapolation behavior before first keyframe + * + * @see @ref after(), @ref at() + */ + Extrapolation before() const { return _before; } + + /** + * @brief Extrapolation behavior after last keyframe + * + * @see @ref before(), @ref at() + */ + Extrapolation after() const { return _after; } + + /** + * @brief Keyframe data + * + * @see @ref keys(), @ref values(), @ref operator[]() + */ + Containers::ArrayView> data() const { return _data; } + + /** + * @brief Key data + * + * @see @ref data(), @ref values(), @ref operator[]() + */ + Containers::StridedArrayView keys() const { + return _data ? Containers::StridedArrayView{&_data[0].first, _data.size(), sizeof(std::pair)} : nullptr; + } + + /** + * @brief Value data + * + * @see @ref data(), @ref keys(), @ref operator[]() + */ + Containers::StridedArrayView values() const { + return _data ? Containers::StridedArrayView{&_data[0].second, _data.size(), sizeof(std::pair)} : nullptr; + } + + /** @brief Keyframe access */ + const std::pair& operator[](std::size_t i) const { return _data[i]; } + + /** + * @brief Animated value at a given time + * + * Calls @ref interpolate(), see its documentation for more + * information. Note that this function performs a linear search every + * time, use @ref at(K, std::size_t&) const to supply a search hint. + * @see @ref atStrict() + */ + ResultOf at(K frame) const { + std::size_t hint{}; + return at(frame, hint); + } + + /** + * @brief Animated value at a given time + * + * Calls @ref interpolate(), see its documentation for more + * information. + * @see @ref at(K) const, @ref atStrict(K, std::size_t&) const + */ + ResultOf at(K frame, std::size_t& hint) const { + return interpolate(keys(), values(), _before, _after, _interpolator, frame, hint); + } + + /** + * @brief Animated value at a given time + * + * A faster version of @ref at(K, std::size_t&) const with some + * restrictions. Calls @ref interpolateStrict(), see its documentation + * for more information. + */ + ResultOf atStrict(K frame, std::size_t& hint) const { + return interpolateStrict(keys(), values(), _interpolator, frame, hint); + } + + private: + Containers::Array> _data; + Interpolator _interpolator; + Extrapolation _before, _after; +}; + +/** +@brief Animation track view +@tparam K Key type +@tparam V Value type + +Unlike @ref Track this is a non-owning view onto keyframe + value pairs. See +its documentation for more information. +@experimental +*/ +template class TrackView { + public: + /** @brief Key type */ + typedef K KeyType; + + /** @brief Value type */ + typedef V ValueType; + + /** @brief Animation result type */ + typedef ResultOf ResultType; + + /** @brief Interpolation function */ + typedef ResultType(*Interpolator)(const ValueType&, const ValueType&, Float); + + /** + * @brief Constructor + * @param keys Frame keys + * @param values Frame values + * @param interpolator Interpolation function + * @param before Extrapolation behavior before + * @param after Extrapolation behavior after + * + * The keyframe data are assumed to be stored in sorted order. It's not + * an error to have two successive keyframes with the same frame value. + */ + constexpr explicit TrackView(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: _keys{keys}, _values{values}, _interpolator{interpolator}, _before{before}, _after{after} {} + + /** @overload + * Equivalent to calling @ref TrackView(const Containers::StridedArrayView&, const Containers::StridedArrayView&, Interpolator, Extrapolation, Extrapolation) + * with both @p before and @p after set to @p extrapolation. + */ + constexpr explicit TrackView(const Containers::StridedArrayView& keys, const Containers::StridedArrayView& values, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Extrapolated) noexcept: TrackView{keys, values, interpolator, extrapolation, extrapolation} {} + + /** + * @brief Construct from an interleaved array + * @param data Keyframe data + * @param interpolator Interpolation function + * @param before Extrapolation behavior before + * @param after Extrapolation behavior after + * + * Converts @p data to a pair of strided array views and calls + * @ref TrackView(const Containers::StridedArrayView&, const Containers::StridedArrayView&, Interpolator, Extrapolation, Extrapolation). + */ + constexpr explicit TrackView(Containers::ArrayView> data, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: _keys{data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, _values{data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, _interpolator{interpolator}, _before{before}, _after{after} {} + + /** @overload + * Equivalent to calling @ref TrackView(Containers::ArrayView>, Interpolator, Extrapolation, Extrapolation) + * with both @p before and @p after set to @p extrapolation. + */ + constexpr explicit TrackView(Containers::ArrayView> data, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Extrapolated) noexcept: TrackView{data, interpolator, extrapolation, extrapolation} {} + + /** + * @brief Extrapolation behavior before first keyframe + * + * @see @ref after(), @ref at(), @ref atStrict() + */ + Extrapolation before() const { return _before; } + + /** + * @brief Extrapolation behavior after last keyframe + * + * @see @ref before(), @ref at(), @ref atStrict() + */ + Extrapolation after() const { return _after; } + + /** @brief Interpolation function */ + Interpolator interpolator() const { return _interpolator; } + + /** + * @brief Key data + * + * @see @ref values(), @ref operator[]() + */ + Containers::StridedArrayView keys() const { return _keys; } + + /** + * @brief Value data + * + * @see @ref keys(), @ref operator[]() + */ + Containers::StridedArrayView values() const { return _values; } + + /** @brief Keyframe access */ + std::pair operator[](std::size_t i) const { + return {_keys[i], _values[i]}; + } + + /** + * @brief Animated value at a given time + * + * Calls @ref interpolate(), see its documentation for more + * information. Note that this function performs a linear search every + * time, use @ref at(K, std::size_t&) const to supply a search hint. + * @see @ref atStrict(K, std::size_t&) const + */ + ResultOf at(K frame) const { + std::size_t hint{}; + return at(frame, hint); + } + + /** + * @brief Animated value at a given time + * + * Calls @ref interpolate(), see its documentation for more + * information. + * @see @ref at(K) const, @ref atStrict(K, std::size_t&) const + */ + ResultOf at(K frame, std::size_t& hint) const { + return interpolate(_keys, _values, _before, _after, _interpolator, frame, hint); + } + + /** + * @brief Animated value at a given time + * + * A faster version of @ref at(K, std::size_t&) const with some + * restrictions. Calls @ref interpolateStrict(), see its documentation + * for more information. + */ + ResultOf atStrict(K frame, std::size_t& hint) const { + return interpolateStrict(_keys, _values, _interpolator, frame, hint); + } + + private: + Containers::StridedArrayView _keys; + Containers::StridedArrayView _values; + Interpolator _interpolator; + Extrapolation _before, _after; +}; + +}} + +#endif