Files
react-native/ReactAndroid/src/main/java/com/facebook/react/animated/SpringAnimation.java
T
Andres Suarez 3b31e69e28 Tidy up license headers [2/n]
Summary: Changelog: [General] [Fixed] - License header cleanup

Reviewed By: yungsters

Differential Revision: D17952694

fbshipit-source-id: 17c87de7ebb271fa2ac8d00af72a4d1addef8bd0
2019-10-16 10:06:34 -07:00

206 lines
6.9 KiB
Java

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.animated;
import com.facebook.react.bridge.ReadableMap;
/**
* Implementation of {@link AnimationDriver} providing support for spring animations. The
* implementation has been copied from android implementation of Rebound library (see <a
* href="http://facebook.github.io/rebound/">http://facebook.github.io/rebound/</a>)
*/
/*package*/ class SpringAnimation extends AnimationDriver {
// maximum amount of time to simulate per physics iteration in seconds (4 frames at 60 FPS)
private static final double MAX_DELTA_TIME_SEC = 0.064;
// fixed timestep to use in the physics solver in seconds
private static final double SOLVER_TIMESTEP_SEC = 0.001;
// storage for the current and prior physics state while integration is occurring
private static class PhysicsState {
double position;
double velocity;
}
private long mLastTime;
private boolean mSpringStarted;
// configuration
private double mSpringStiffness;
private double mSpringDamping;
private double mSpringMass;
private double mInitialVelocity;
private boolean mOvershootClampingEnabled;
// all physics simulation objects are final and reused in each processing pass
private final PhysicsState mCurrentState = new PhysicsState();
private double mStartValue;
private double mEndValue;
// thresholds for determining when the spring is at rest
private double mRestSpeedThreshold;
private double mDisplacementFromRestThreshold;
private double mTimeAccumulator;
// for controlling loop
private int mIterations;
private int mCurrentLoop;
private double mOriginalValue;
SpringAnimation(ReadableMap config) {
mCurrentState.velocity = config.getDouble("initialVelocity");
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
mSpringStiffness = config.getDouble("stiffness");
mSpringDamping = config.getDouble("damping");
mSpringMass = config.getDouble("mass");
mInitialVelocity = mCurrentState.velocity;
mEndValue = config.getDouble("toValue");
mRestSpeedThreshold = config.getDouble("restSpeedThreshold");
mDisplacementFromRestThreshold = config.getDouble("restDisplacementThreshold");
mOvershootClampingEnabled = config.getBoolean("overshootClamping");
mIterations = config.hasKey("iterations") ? config.getInt("iterations") : 1;
mHasFinished = mIterations == 0;
mCurrentLoop = 0;
mTimeAccumulator = 0;
mSpringStarted = false;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
long frameTimeMillis = frameTimeNanos / 1000000;
if (!mSpringStarted) {
if (mCurrentLoop == 0) {
mOriginalValue = mAnimatedValue.mValue;
mCurrentLoop = 1;
}
mStartValue = mCurrentState.position = mAnimatedValue.mValue;
mLastTime = frameTimeMillis;
mTimeAccumulator = 0.0;
mSpringStarted = true;
}
advance((frameTimeMillis - mLastTime) / 1000.0);
mLastTime = frameTimeMillis;
mAnimatedValue.mValue = mCurrentState.position;
if (isAtRest()) {
if (mIterations == -1 || mCurrentLoop < mIterations) { // looping animation, return to start
mSpringStarted = false;
mAnimatedValue.mValue = mOriginalValue;
mCurrentLoop++;
} else { // animation has completed
mHasFinished = true;
}
}
}
/**
* get the displacement from rest for a given physics state
*
* @param state the state to measure from
* @return the distance displaced by
*/
private double getDisplacementDistanceForState(PhysicsState state) {
return Math.abs(mEndValue - state.position);
}
/**
* check if the current state is at rest
*
* @return is the spring at rest
*/
private boolean isAtRest() {
return Math.abs(mCurrentState.velocity) <= mRestSpeedThreshold
&& (getDisplacementDistanceForState(mCurrentState) <= mDisplacementFromRestThreshold
|| mSpringStiffness == 0);
}
/**
* Check if the spring is overshooting beyond its target.
*
* @return true if the spring is overshooting its target
*/
private boolean isOvershooting() {
return mSpringStiffness > 0
&& ((mStartValue < mEndValue && mCurrentState.position > mEndValue)
|| (mStartValue > mEndValue && mCurrentState.position < mEndValue));
}
private void advance(double realDeltaTime) {
if (isAtRest()) {
return;
}
// clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able
// to catch up in a subsequent advance if necessary.
double adjustedDeltaTime = realDeltaTime;
if (realDeltaTime > MAX_DELTA_TIME_SEC) {
adjustedDeltaTime = MAX_DELTA_TIME_SEC;
}
mTimeAccumulator += adjustedDeltaTime;
double c = mSpringDamping;
double m = mSpringMass;
double k = mSpringStiffness;
double v0 = -mInitialVelocity;
double zeta = c / (2 * Math.sqrt(k * m));
double omega0 = Math.sqrt(k / m);
double omega1 = omega0 * Math.sqrt(1.0 - (zeta * zeta));
double x0 = mEndValue - mStartValue;
double velocity;
double position;
double t = mTimeAccumulator;
if (zeta < 1) {
// Under damped
double envelope = Math.exp(-zeta * omega0 * t);
position =
mEndValue
- envelope
* ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t)
+ x0 * Math.cos(omega1 * t));
// This looks crazy -- it's actually just the derivative of the
// oscillation function
velocity =
zeta
* omega0
* envelope
* (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1
+ x0 * Math.cos(omega1 * t))
- envelope
* (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0)
- omega1 * x0 * Math.sin(omega1 * t));
} else {
// Critically damped spring
double envelope = Math.exp(-omega0 * t);
position = mEndValue - envelope * (x0 + (v0 + omega0 * x0) * t);
velocity = envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
}
mCurrentState.position = position;
mCurrentState.velocity = velocity;
// End the spring immediately if it is overshooting and overshoot clamping is enabled.
// Also make sure that if the spring was considered within a resting threshold that it's now
// snapped to its end value.
if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) {
// Don't call setCurrentValue because that forces a call to onSpringUpdate
if (mSpringStiffness > 0) {
mStartValue = mEndValue;
mCurrentState.position = mEndValue;
} else {
mEndValue = mCurrentState.position;
mStartValue = mEndValue;
}
mCurrentState.velocity = 0;
}
}
}