SpringStopEngine.java

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.core.motion.utils;

/**
 * This contains the class to provide the logic for an animation to come to a stop using a spring
 * model.
 *
 * @suppress
 */
public class SpringStopEngine implements StopEngine {
    double mDamping = 0.5f;
    private static final double UNSET = Double.MAX_VALUE;
    private boolean mInitialized = false;
    private double mStiffness;
    private double mTargetPos;
    private double mLastVelocity;
    private float mLastTime;
    private float mPos;
    private float mV;
    private float mMass;
    private float mStopThreshold;
    private int mBoundaryMode = 0;

    @Override
    public String debug(String desc, float time) {
        return null;
    }

    void log(String str) {
        StackTraceElement s = new Throwable().getStackTrace()[1];
        String line = ".(" + s.getFileName() + ":" + s.getLineNumber() + ") " + s.getMethodName() + "() ";
        System.out.println(line + str);
    }

    public void springConfig(float currentPos, float target, float currentVelocity, float mass,
                             float stiffness, float damping, float stopThreshold, int boundaryMode) {
        mTargetPos = target;
        mDamping = damping;
        mInitialized = false;
        mPos = currentPos;
        mLastVelocity = currentVelocity;
        mStiffness = stiffness;
        mMass = mass;
        mStopThreshold = stopThreshold;
        mBoundaryMode = boundaryMode;
        mLastTime = 0;
    }

    @Override
    public float getVelocity(float t) {
        return (float) mV;
    }

    @Override
    public float getInterpolation(float time) {
        compute(time - mLastTime);
        mLastTime = time;
        return (float) (mPos);
    }

    public float getAcceleration() {
        double k = mStiffness;
        double c = mDamping;
        double x = (mPos - mTargetPos);
        return (float) (-k * x - c * mV) / mMass;
    }

    @Override
    public float getVelocity() {
        return 0;
    }

    @Override
    public boolean isStopped() {
        double x = (mPos - mTargetPos);
        double k = mStiffness;
        double v = mV;
        double m = mMass;
        double energy = v * v * m + k * x * x;
        double max_def = Math.sqrt(energy / k);
        return max_def <= mStopThreshold;
    }

    private void compute(double dt) {

        double k = mStiffness;
        double c = mDamping;
        // Estimate how many time we should over sample based on the frequency and current sampling
        int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4));
        dt /= overSample;

        for (int i = 0; i < overSample; i++) {
            double x = (mPos - mTargetPos);
            double a = (-k * x - c * mV) / mMass;
            // This refinement of a simple coding of the acceleration increases accuracy
            double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity
            double avgX = mPos + dt * (avgV) / 2 - mTargetPos;// pass 1 calculate the average pos
            a = (-avgX * k - avgV * c) / mMass; //  calculate acceleration over that average pos

            double dv = a * dt; //  calculate change in velocity
            avgV = mV + dv / 2; //  average  velocity is current + half change
            mV += dv;
            mPos += avgV * dt;
            if (mBoundaryMode > 0) {
                if (mPos < 0 && ((mBoundaryMode & 1) == 1)) {
                    mPos = -mPos;
                    mV = -mV;
                }
                if (mPos > 1 && ((mBoundaryMode & 2) == 2)) {
                    mPos = 2 - mPos;
                    mV = -mV;
                }
            }
        }
    }
    
}