mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
3b31e69e28
Summary: Changelog: [General] [Fixed] - License header cleanup Reviewed By: yungsters Differential Revision: D17952694 fbshipit-source-id: 17c87de7ebb271fa2ac8d00af72a4d1addef8bd0
206 lines
6.9 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|