diff --git a/change/react-native-windows-2019-10-21-18-46-29-calculatedanimation.json b/change/react-native-windows-2019-10-21-18-46-29-calculatedanimation.json new file mode 100644 index 00000000000..1c6d1d86b0a --- /dev/null +++ b/change/react-native-windows-2019-10-21-18-46-29-calculatedanimation.json @@ -0,0 +1,9 @@ +{ + "type": "none", + "comment": "Create Calculated Animation which allows for animations that don't follow Bezier curves", + "packageName": "react-native-windows", + "email": "asklar@winse.microsoft.com", + "commit": "3785eabdd6fad1417602b4ad78f4f46ab6b4a985", + "date": "2019-10-22T01:46:28.972Z", + "file": "F:\\rnw\\change\\react-native-windows-2019-10-21-18-46-29-calculatedanimation.json" +} \ No newline at end of file diff --git a/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.cpp b/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.cpp new file mode 100644 index 00000000000..8dfc3d954eb --- /dev/null +++ b/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" + +#include +#include "CalculatedAnimationDriver.h" + +namespace react { +namespace uwp { + +std::tuple +CalculatedAnimationDriver::MakeAnimation(const folly::dynamic &config) { + const auto [scopedBatch, animation, easingFunction] = []() { + const auto compositor = winrt::Window::Current().Compositor(); + return std::make_tuple( + compositor.CreateScopedBatch( + winrt::CompositionBatchTypes::AllAnimations), + compositor.CreateScalarKeyFrameAnimation(), + compositor.CreateLinearEasingFunction()); + }(); + + m_startValue = GetAnimatedValue()->Value(); + std::vector keyFrames = [this]() { + std::vector keyFrames; + bool done = false; + double time = 0; + while (!done) { + time += 1.0f / 60.0f; + auto [currentValue, currentVelocity] = GetValueAndVelocityForTime(time); + keyFrames.push_back(currentValue); + if (IsAnimationDone(currentValue, currentVelocity)) { + done = true; + } + } + return keyFrames; + }(); + + std::chrono::milliseconds duration( + static_cast(keyFrames.size() / 60.0f * 1000.0f)); + animation.Duration(duration); + auto normalizedProgress = 0.0f; + // We are animating the values offset property which should start at 0. + animation.InsertKeyFrame(normalizedProgress, 0.0f, easingFunction); + for (const auto keyFrame : keyFrames) { + normalizedProgress = + std::min(normalizedProgress + 1.0f / keyFrames.size(), 1.0f); + animation.InsertKeyFrame( + normalizedProgress, + keyFrame - static_cast(m_startValue), + easingFunction); + } + + if (m_iterations == -1) { + animation.IterationBehavior(winrt::AnimationIterationBehavior::Forever); + } else { + animation.IterationCount(static_cast(m_iterations)); + animation.IterationBehavior(winrt::AnimationIterationBehavior::Count); + } + + return std::make_tuple(animation, scopedBatch); +} + +} // namespace uwp +} // namespace react diff --git a/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.h b/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.h new file mode 100644 index 00000000000..7d7dcbeeb61 --- /dev/null +++ b/vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include +#include +#include "AnimatedNode.h" +#include "AnimationDriver.h" + +namespace react { +namespace uwp { +class CalculatedAnimationDriver : public AnimationDriver { + public: + using AnimationDriver::AnimationDriver; + + std::tuple + MakeAnimation(const folly::dynamic &config) override; + + protected: + virtual std::tuple GetValueAndVelocityForTime(double time) = 0; + + virtual bool IsAnimationDone(double currentValue, double currentVelocity) = 0; + double m_startValue{0}; +}; +} // namespace uwp +} // namespace react diff --git a/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.cpp b/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.cpp index 700c43c0bc5..3bf82d9852e 100644 --- a/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.cpp +++ b/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.cpp @@ -14,51 +14,32 @@ DecayAnimationDriver::DecayAnimationDriver( const Callback &endCallback, const folly::dynamic &config, const std::shared_ptr &manager) - : AnimationDriver(id, animatedValueTag, endCallback, config, manager) { + : CalculatedAnimationDriver( + id, + animatedValueTag, + endCallback, + config, + manager) { m_deceleration = config.find(s_decelerationName).dereference().second.asDouble(); assert(m_deceleration > 0); m_velocity = config.find(s_velocityName).dereference().second.asDouble(); } -std::tuple -DecayAnimationDriver::MakeAnimation(const folly::dynamic &config) { - const auto [scopedBatch, animation] = []() { - const auto compositor = winrt::Window::Current().Compositor(); - return std::make_tuple( - compositor.CreateScopedBatch( - winrt::CompositionBatchTypes::AllAnimations), - compositor.CreateScalarKeyFrameAnimation()); - }(); - - std::chrono::milliseconds duration( - static_cast(m_velocity / -m_deceleration * 1000)); - animation.Duration(duration); - - const auto compositor = winrt::Window::Current().Compositor(); - animation.SetScalarParameter( - s_velocityParameterName, static_cast(m_velocity)); - animation.SetScalarParameter( - s_decelerationParameterName, static_cast(m_deceleration)); - animation.SetScalarParameter( - s_durationName, static_cast(m_velocity / -m_deceleration) * 1000); - // Offset = (Velocity*time) + (0.5*Acceleration*Time^2) - animation.InsertExpressionKeyFrame( - 1.0f, - static_cast(L"(") + s_durationName + L" * " + - s_velocityParameterName + L") + (0.5 * " + - s_decelerationParameterName + L" * " + s_durationName + L" * " + - s_durationName + L")", - compositor.CreateCubicBezierEasingFunction({0, 1}, {0, 1})); - - if (m_iterations == -1) { - animation.IterationBehavior(winrt::AnimationIterationBehavior::Forever); - } else { - animation.IterationCount(static_cast(m_iterations)); - animation.IterationBehavior(winrt::AnimationIterationBehavior::Count); - } +std::tuple DecayAnimationDriver::GetValueAndVelocityForTime( + double time) { + const auto value = m_startValue + + m_velocity / (1 - m_deceleration) * + (1 - std::exp(-(1 - m_deceleration) * (1000 * time))); + return std::make_tuple( + static_cast(value), + 42.0f); // we don't need the velocity, so set it to a dummy value +} - return std::make_tuple(animation, scopedBatch); +bool DecayAnimationDriver::IsAnimationDone( + double currentValue, + double currentVelocity) { + return (std::abs(ToValue() - currentValue) < 0.1); } double DecayAnimationDriver::ToValue() { @@ -72,9 +53,7 @@ double DecayAnimationDriver::ToValue() { return 0.0; }(); - auto const duration = m_velocity / -m_deceleration; - return startValue + m_velocity * duration + - (0.5 * -m_deceleration * duration * duration); + return m_startValue + m_velocity / (1 - m_deceleration); } } // namespace uwp diff --git a/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.h b/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.h index dea9cd6002b..f2a5d5ab75b 100644 --- a/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.h +++ b/vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.h @@ -4,11 +4,11 @@ #pragma once #include #include "AnimatedNode.h" -#include "AnimationDriver.h" +#include "CalculatedAnimationDriver.h" namespace react { namespace uwp { -class DecayAnimationDriver : public AnimationDriver { +class DecayAnimationDriver : public CalculatedAnimationDriver { public: DecayAnimationDriver( int64_t id, @@ -17,11 +17,12 @@ class DecayAnimationDriver : public AnimationDriver { const folly::dynamic &config, const std::shared_ptr &manager); - std::tuple - MakeAnimation(const folly::dynamic &config) override; - double ToValue() override; + protected: + std::tuple GetValueAndVelocityForTime(double time) override; + bool IsAnimationDone(double currentValue, double currentVelocity) override; + private: double m_velocity{0}; double m_deceleration{0}; diff --git a/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.cpp b/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.cpp index d446d401c16..d729105de5e 100644 --- a/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.cpp +++ b/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.cpp @@ -17,7 +17,12 @@ SpringAnimationDriver::SpringAnimationDriver( const std::shared_ptr &manager, const folly::dynamic &dynamicToValues) : m_dynamicToValues(dynamicToValues), - AnimationDriver(id, animatedValueTag, endCallback, config, manager) { + CalculatedAnimationDriver( + id, + animatedValueTag, + endCallback, + config, + manager) { m_springStiffness = config.find(s_springStiffnessParameterName) .dereference() .second.asDouble(); @@ -45,71 +50,22 @@ SpringAnimationDriver::SpringAnimationDriver( config.find(s_iterationsParameterName).dereference().second.asDouble()); } -std::tuple -SpringAnimationDriver::MakeAnimation(const folly::dynamic &config) { - const auto [scopedBatch, animation, easingFunction] = []() { - const auto compositor = winrt::Window::Current().Compositor(); - return std::make_tuple( - compositor.CreateScopedBatch( - winrt::CompositionBatchTypes::AllAnimations), - compositor.CreateScalarKeyFrameAnimation(), - compositor.CreateLinearEasingFunction()); - }(); - - const auto startValue = GetAnimatedValue()->Value(); - std::vector keyFrames = [this, startValue]() { - std::vector keyFrames; - bool done = false; - double time = 0; - while (!done) { - time += 1.0f / 60.0f; - auto [currentValue, currentVelocity] = - GetValueAndVelocityForTime(time, startValue); - keyFrames.push_back(currentValue); - if (IsAtRest(currentVelocity, currentValue, m_endValue) || - (m_overshootClampingEnabled && - IsOvershooting(currentValue, startValue))) { - done = true; - } - } - return keyFrames; - }(); - - std::chrono::milliseconds duration( - static_cast(keyFrames.size() / 60.0f * 1000.0f)); - animation.Duration(duration); - - auto normalizedProgress = 0.0f; - // We are animating the values offset property which should start at 0. - animation.InsertKeyFrame(normalizedProgress, 0.0f, easingFunction); - for (const auto keyFrame : keyFrames) { - normalizedProgress = - std::min(normalizedProgress + 1.0f / keyFrames.size(), 1.0f); - animation.InsertKeyFrame( - normalizedProgress, - keyFrame - static_cast(startValue), - easingFunction); - } - - if (m_iterations == -1) { - animation.IterationBehavior(winrt::AnimationIterationBehavior::Forever); - } else { - animation.IterationCount(static_cast(m_iterations)); - animation.IterationBehavior(winrt::AnimationIterationBehavior::Count); - } - - return std::make_tuple(animation, scopedBatch); +bool SpringAnimationDriver::IsAnimationDone( + double currentValue, + double currentVelocity) { + return ( + IsAtRest(currentVelocity, currentValue, m_endValue) || + (m_overshootClampingEnabled && IsOvershooting(currentValue))); } std::tuple SpringAnimationDriver::GetValueAndVelocityForTime( - double time, - double startValue) { - const auto toValue = [this, time, startValue]() { + double time) { + const auto toValue = [this, time]() { const auto frameFromTime = static_cast(time * 60.0); if (frameFromTime < static_cast(m_dynamicToValues.size())) { - return startValue + + return m_startValue + (m_dynamicToValues[frameFromTime].asDouble() * - (m_endValue - startValue)); + (m_endValue - m_startValue)); } return m_endValue; }(); @@ -121,7 +77,7 @@ std::tuple SpringAnimationDriver::GetValueAndVelocityForTime( const auto zeta = c / (2 * std::sqrt(k * m)); const auto omega0 = std::sqrt(k / m); const auto omega1 = omega0 * std::sqrt(1.0 - (zeta * zeta)); - const auto x0 = toValue - startValue; + const auto x0 = toValue - m_startValue; if (zeta < 1) { const auto envelope = std::exp(-zeta * omega0 * time); @@ -140,7 +96,7 @@ std::tuple SpringAnimationDriver::GetValueAndVelocityForTime( } else { const auto envelope = std::exp(-omega0 * time); const auto value = static_cast( - toValue * (v0 * (time * omega0 - 1) + time * x0 * (omega0 * omega0))); + m_endValue - envelope * (x0 + (v0 + omega0 * x0) * time)); const auto velocity = envelope * (v0 * (time * omega0 - 1) + time * x0 * (omega0 * omega0)); return std::make_tuple(value, velocity); @@ -156,12 +112,10 @@ bool SpringAnimationDriver::IsAtRest( m_springStiffness == 0); } -bool SpringAnimationDriver::IsOvershooting( - double currentValue, - double startValue) { +bool SpringAnimationDriver::IsOvershooting(double currentValue) { return m_springStiffness > 0 && - ((startValue < m_endValue && currentValue > m_endValue) || - (startValue > m_endValue && currentValue < m_endValue)); + ((m_startValue < m_endValue && currentValue > m_endValue) || + (m_startValue > m_endValue && currentValue < m_endValue)); } double SpringAnimationDriver::ToValue() { diff --git a/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.h b/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.h index dc07515a690..09d53853616 100644 --- a/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.h +++ b/vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.h @@ -5,11 +5,11 @@ #pragma once #include #include "AnimatedNode.h" -#include "AnimationDriver.h" +#include "CalculatedAnimationDriver.h" namespace react { namespace uwp { -class SpringAnimationDriver : public AnimationDriver { +class SpringAnimationDriver : public CalculatedAnimationDriver { public: SpringAnimationDriver( int64_t id, @@ -19,18 +19,16 @@ class SpringAnimationDriver : public AnimationDriver { const std::shared_ptr &manager, const folly::dynamic &dynamicToValues = folly::dynamic::array()); - std::tuple - MakeAnimation(const folly::dynamic &config) override; - double ToValue() override; + protected: + std::tuple GetValueAndVelocityForTime(double time) override; + bool IsAnimationDone(double currentValue, double currentVelocity) override; + private: - std::tuple GetValueAndVelocityForTime( - double time, - double startValue); bool IsAtRest(double currentVelocity, double currentPosition, double endValue); - bool IsOvershooting(double currentValue, double startValue); + bool IsOvershooting(double currentValue); double m_springStiffness{0}; double m_springDamping{0}; diff --git a/vnext/ReactUWP/ReactUWP.vcxproj b/vnext/ReactUWP/ReactUWP.vcxproj index 9df13685d41..5bfbd4d2a76 100644 --- a/vnext/ReactUWP/ReactUWP.vcxproj +++ b/vnext/ReactUWP/ReactUWP.vcxproj @@ -164,6 +164,7 @@ + @@ -271,6 +272,7 @@ + @@ -405,7 +407,7 @@ {a62d504a-16b8-41d2-9f19-e2e86019e5e4} - + {17DD1B17-3094-40DD-9373-AC2497932ECA}