Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
65 changes: 65 additions & 0 deletions vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"

#include <math.h>
#include "CalculatedAnimationDriver.h"

namespace react {
namespace uwp {

std::tuple<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
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<float> keyFrames = [this]() {
std::vector<float> 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<int>(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<float>(m_startValue),
easingFunction);
}

if (m_iterations == -1) {
animation.IterationBehavior(winrt::AnimationIterationBehavior::Forever);
} else {
animation.IterationCount(static_cast<int32_t>(m_iterations));
animation.IterationBehavior(winrt::AnimationIterationBehavior::Count);
}

return std::make_tuple(animation, scopedBatch);
}

} // namespace uwp
} // namespace react
26 changes: 26 additions & 0 deletions vnext/ReactUWP/Modules/Animated/CalculatedAnimationDriver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once
#include <folly/dynamic.h>
#include <utility>
#include "AnimatedNode.h"
#include "AnimationDriver.h"

namespace react {
namespace uwp {
class CalculatedAnimationDriver : public AnimationDriver {
public:
using AnimationDriver::AnimationDriver;

std::tuple<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
MakeAnimation(const folly::dynamic &config) override;

protected:
virtual std::tuple<float, double> GetValueAndVelocityForTime(double time) = 0;

virtual bool IsAnimationDone(double currentValue, double currentVelocity) = 0;
double m_startValue{0};
};
} // namespace uwp
} // namespace react
61 changes: 20 additions & 41 deletions vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,32 @@ DecayAnimationDriver::DecayAnimationDriver(
const Callback &endCallback,
const folly::dynamic &config,
const std::shared_ptr<NativeAnimatedNodeManager> &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<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
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<int>(m_velocity / -m_deceleration * 1000));
animation.Duration(duration);

const auto compositor = winrt::Window::Current().Compositor();
animation.SetScalarParameter(
s_velocityParameterName, static_cast<float>(m_velocity));
animation.SetScalarParameter(
s_decelerationParameterName, static_cast<float>(m_deceleration));
animation.SetScalarParameter(
s_durationName, static_cast<float>(m_velocity / -m_deceleration) * 1000);
// Offset = (Velocity*time) + (0.5*Acceleration*Time^2)
animation.InsertExpressionKeyFrame(
1.0f,
static_cast<winrt::hstring>(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<int32_t>(m_iterations));
animation.IterationBehavior(winrt::AnimationIterationBehavior::Count);
}
std::tuple<float, double> 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<float>(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() {
Expand All @@ -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
Expand Down
11 changes: 6 additions & 5 deletions vnext/ReactUWP/Modules/Animated/DecayAnimationDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
#pragma once
#include <folly/dynamic.h>
#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,
Expand All @@ -17,11 +17,12 @@ class DecayAnimationDriver : public AnimationDriver {
const folly::dynamic &config,
const std::shared_ptr<NativeAnimatedNodeManager> &manager);

std::tuple<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
MakeAnimation(const folly::dynamic &config) override;

double ToValue() override;

protected:
std::tuple<float, double> GetValueAndVelocityForTime(double time) override;
bool IsAnimationDone(double currentValue, double currentVelocity) override;

private:
double m_velocity{0};
double m_deceleration{0};
Expand Down
88 changes: 21 additions & 67 deletions vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ SpringAnimationDriver::SpringAnimationDriver(
const std::shared_ptr<NativeAnimatedNodeManager> &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();
Expand Down Expand Up @@ -45,71 +50,22 @@ SpringAnimationDriver::SpringAnimationDriver(
config.find(s_iterationsParameterName).dereference().second.asDouble());
}

std::tuple<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
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<float> keyFrames = [this, startValue]() {
std::vector<float> 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<int>(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<float>(startValue),
easingFunction);
}

if (m_iterations == -1) {
animation.IterationBehavior(winrt::AnimationIterationBehavior::Forever);
} else {
animation.IterationCount(static_cast<int32_t>(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<float, double> SpringAnimationDriver::GetValueAndVelocityForTime(
double time,
double startValue) {
const auto toValue = [this, time, startValue]() {
double time) {
const auto toValue = [this, time]() {
const auto frameFromTime = static_cast<int>(time * 60.0);
if (frameFromTime < static_cast<int>(m_dynamicToValues.size())) {
return startValue +
return m_startValue +
(m_dynamicToValues[frameFromTime].asDouble() *
(m_endValue - startValue));
(m_endValue - m_startValue));
}
return m_endValue;
}();
Expand All @@ -121,7 +77,7 @@ std::tuple<float, double> 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);
Expand All @@ -140,7 +96,7 @@ std::tuple<float, double> SpringAnimationDriver::GetValueAndVelocityForTime(
} else {
const auto envelope = std::exp(-omega0 * time);
const auto value = static_cast<float>(
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);
Expand All @@ -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() {
Expand Down
16 changes: 7 additions & 9 deletions vnext/ReactUWP/Modules/Animated/SpringAnimationDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
#pragma once
#include <folly/dynamic.h>
#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,
Expand All @@ -19,18 +19,16 @@ class SpringAnimationDriver : public AnimationDriver {
const std::shared_ptr<NativeAnimatedNodeManager> &manager,
const folly::dynamic &dynamicToValues = folly::dynamic::array());

std::tuple<winrt::CompositionAnimation, winrt::CompositionScopedBatch>
MakeAnimation(const folly::dynamic &config) override;

double ToValue() override;

protected:
std::tuple<float, double> GetValueAndVelocityForTime(double time) override;
bool IsAnimationDone(double currentValue, double currentVelocity) override;

private:
std::tuple<float, double> 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};
Expand Down
Loading