-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: This change adds support for spring animations to be run off the JS thread on android. The implementation is based on the android spring implementation from Rebound (http://facebook.github.io/rebound/) but since only a small subset of the library is used the relevant parts are copied instead of making RN to import the whole library. **Test Plan** Run java tests: `buck test ReactAndroid/src/test/java/com/facebook/react/animated` Add `useNativeDriver: true` to spring animation in animated example app, run it on android Closes #8860 Differential Revision: D3676436 fbshipit-source-id: 3a4b1b006725a938562712989b93dd4090577c48
- Loading branch information
Showing
4 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
ReactAndroid/src/main/java/com/facebook/react/animated/SpringAnimation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
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 mSpringFriction; | ||
private double mSpringTension; | ||
private boolean mOvershootClampingEnabled; | ||
|
||
// all physics simulation objects are final and reused in each processing pass | ||
private final PhysicsState mCurrentState = new PhysicsState(); | ||
private final PhysicsState mPreviousState = new PhysicsState(); | ||
private final PhysicsState mTempState = 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 = 0; | ||
|
||
SpringAnimation(ReadableMap config) { | ||
mSpringFriction = config.getDouble("friction"); | ||
mSpringTension = config.getDouble("tension"); | ||
mCurrentState.velocity = config.getDouble("initialVelocity"); | ||
mEndValue = config.getDouble("toValue"); | ||
mRestSpeedThreshold = config.getDouble("restSpeedThreshold"); | ||
mDisplacementFromRestThreshold = config.getDouble("restDisplacementThreshold"); | ||
mOvershootClampingEnabled = config.getBoolean("overshootClamping"); | ||
} | ||
|
||
@Override | ||
public void runAnimationStep(long frameTimeNanos) { | ||
long frameTimeMillis = frameTimeNanos / 1000000; | ||
if (!mSpringStarted) { | ||
mStartValue = mCurrentState.position = mAnimatedValue.mValue; | ||
mLastTime = frameTimeMillis; | ||
mSpringStarted = true; | ||
} | ||
advance((frameTimeMillis - mLastTime) / 1000.0); | ||
mLastTime = frameTimeMillis; | ||
mAnimatedValue.mValue = mCurrentState.position; | ||
mHasFinished = isAtRest(); | ||
} | ||
|
||
/** | ||
* 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 || | ||
mSpringTension == 0); | ||
} | ||
|
||
/** | ||
* Check if the spring is overshooting beyond its target. | ||
* @return true if the spring is overshooting its target | ||
*/ | ||
private boolean isOvershooting() { | ||
return mSpringTension > 0 && | ||
((mStartValue < mEndValue && mCurrentState.position > mEndValue) || | ||
(mStartValue > mEndValue && mCurrentState.position < mEndValue)); | ||
} | ||
|
||
/** | ||
* linear interpolation between the previous and current physics state based on the amount of | ||
* timestep remaining after processing the rendering delta time in timestep sized chunks. | ||
* @param alpha from 0 to 1, where 0 is the previous state, 1 is the current state | ||
*/ | ||
private void interpolate(double alpha) { | ||
mCurrentState.position = mCurrentState.position * alpha + mPreviousState.position *(1-alpha); | ||
mCurrentState.velocity = mCurrentState.velocity * alpha + mPreviousState.velocity *(1-alpha); | ||
} | ||
|
||
/** | ||
* advance the physics simulation in SOLVER_TIMESTEP_SEC sized chunks to fulfill the required | ||
* realTimeDelta. | ||
* The math is inlined inside the loop since it made a huge performance impact when there are | ||
* several springs being advanced. | ||
* @param time clock time | ||
* @param realDeltaTime clock drift | ||
*/ | ||
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 tension = mSpringTension; | ||
double friction = mSpringFriction; | ||
|
||
double position = mCurrentState.position; | ||
double velocity = mCurrentState.velocity; | ||
double tempPosition = mTempState.position; | ||
double tempVelocity = mTempState.velocity; | ||
|
||
double aVelocity, aAcceleration; | ||
double bVelocity, bAcceleration; | ||
double cVelocity, cAcceleration; | ||
double dVelocity, dAcceleration; | ||
|
||
double dxdt, dvdt; | ||
|
||
// iterate over the true time | ||
while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) { | ||
/* begin debug | ||
iterations++; | ||
end debug */ | ||
mTimeAccumulator -= SOLVER_TIMESTEP_SEC; | ||
|
||
if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) { | ||
// This will be the last iteration. Remember the previous state in case we need to | ||
// interpolate | ||
mPreviousState.position = position; | ||
mPreviousState.velocity = velocity; | ||
} | ||
|
||
// Perform an RK4 integration to provide better detection of the acceleration curve via | ||
// sampling of Euler integrations at 4 intervals feeding each derivative into the calculation | ||
// of the next and taking a weighted sum of the 4 derivatives as the final output. | ||
|
||
// This math was inlined since it made for big performance improvements when advancing several | ||
// springs in one pass of the BaseSpringSystem. | ||
|
||
// The initial derivative is based on the current velocity and the calculated acceleration | ||
aVelocity = velocity; | ||
aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity; | ||
|
||
// Calculate the next derivatives starting with the last derivative and integrating over the | ||
// timestep | ||
tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5; | ||
tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5; | ||
bVelocity = tempVelocity; | ||
bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; | ||
|
||
tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5; | ||
tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5; | ||
cVelocity = tempVelocity; | ||
cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; | ||
|
||
tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC; | ||
tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC; | ||
dVelocity = tempVelocity; | ||
dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; | ||
|
||
// Take the weighted sum of the 4 derivatives as the final output. | ||
dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity); | ||
dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration); | ||
|
||
position += dxdt * SOLVER_TIMESTEP_SEC; | ||
velocity += dvdt * SOLVER_TIMESTEP_SEC; | ||
} | ||
|
||
mTempState.position = tempPosition; | ||
mTempState.velocity = tempVelocity; | ||
|
||
mCurrentState.position = position; | ||
mCurrentState.velocity = velocity; | ||
|
||
if (mTimeAccumulator > 0) { | ||
interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC); | ||
} | ||
|
||
// 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 (tension > 0) { | ||
mStartValue = mEndValue; | ||
mCurrentState.position = mEndValue; | ||
} else { | ||
mEndValue = mCurrentState.position; | ||
mStartValue = mEndValue; | ||
} | ||
mCurrentState.velocity = 0; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters