1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.constraintlayout.core.motion.utils; 18 19 /** 20 * This contains the class to provide the logic for an animation to come to a stop using a spring 21 * model. 22 * 23 * 24 */ 25 public class SpringStopEngine implements StopEngine { 26 double mDamping = 0.5f; 27 @SuppressWarnings("unused") private static final double UNSET = Double.MAX_VALUE; 28 @SuppressWarnings("unused") private boolean mInitialized = false; 29 private double mStiffness; 30 private double mTargetPos; 31 @SuppressWarnings("unused") private double mLastVelocity; 32 private float mLastTime; 33 private float mPos; 34 private float mV; 35 private float mMass; 36 private float mStopThreshold; 37 private int mBoundaryMode = 0; 38 39 @Override debug(String desc, float time)40 public String debug(String desc, float time) { 41 return null; 42 } 43 log(String str)44 void log(String str) { 45 StackTraceElement s = new Throwable().getStackTrace()[1]; 46 String line = ".(" + s.getFileName() + ":" 47 + s.getLineNumber() + ") " + s.getMethodName() + "() "; 48 System.out.println(line + str); 49 } 50 51 // @TODO: add description springConfig(float currentPos, float target, float currentVelocity, float mass, float stiffness, float damping, float stopThreshold, int boundaryMode)52 public void springConfig(float currentPos, 53 float target, 54 float currentVelocity, 55 float mass, 56 float stiffness, 57 float damping, 58 float stopThreshold, 59 int boundaryMode) { 60 mTargetPos = target; 61 mDamping = damping; 62 mInitialized = false; 63 mPos = currentPos; 64 mLastVelocity = currentVelocity; 65 mStiffness = stiffness; 66 mMass = mass; 67 mStopThreshold = stopThreshold; 68 mBoundaryMode = boundaryMode; 69 mLastTime = 0; 70 } 71 72 @Override getVelocity(float time)73 public float getVelocity(float time) { 74 return (float) mV; 75 } 76 77 @Override getInterpolation(float time)78 public float getInterpolation(float time) { 79 compute(time - mLastTime); 80 mLastTime = time; 81 if (isStopped()) { 82 mPos = (float) mTargetPos; 83 } 84 return (float) mPos; 85 } 86 87 // @TODO: add description getAcceleration()88 public float getAcceleration() { 89 double k = mStiffness; 90 double c = mDamping; 91 double x = (mPos - mTargetPos); 92 return (float) (-k * x - c * mV) / mMass; 93 } 94 95 @Override getVelocity()96 public float getVelocity() { 97 return 0; 98 } 99 100 @Override isStopped()101 public boolean isStopped() { 102 double x = (mPos - mTargetPos); 103 double k = mStiffness; 104 double v = mV; 105 double m = mMass; 106 double energy = v * v * m + k * x * x; 107 double max_def = Math.sqrt(energy / k); 108 return max_def <= mStopThreshold; 109 } 110 compute(double dt)111 private void compute(double dt) { 112 if (dt <= 0) { 113 // Nothing to compute if there's no time difference 114 return; 115 } 116 117 double k = mStiffness; 118 double c = mDamping; 119 // Estimate how many time we should over sample based on the frequency and current sampling 120 int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4)); 121 dt /= overSample; 122 123 for (int i = 0; i < overSample; i++) { 124 double x = (mPos - mTargetPos); 125 double a = (-k * x - c * mV) / mMass; 126 // This refinement of a simple coding of the acceleration increases accuracy 127 double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity 128 double avgX = mPos + dt * avgV / 2 - mTargetPos; // pass 1 calculate the average pos 129 a = (-avgX * k - avgV * c) / mMass; // calculate acceleration over that average pos 130 131 double dv = a * dt; // calculate change in velocity 132 avgV = mV + dv / 2; // average velocity is current + half change 133 mV += (float) dv; 134 mPos += (float) (avgV * dt); 135 if (mBoundaryMode > 0) { 136 if (mPos < 0 && ((mBoundaryMode & 1) == 1)) { 137 mPos = -mPos; 138 mV = -mV; 139 } 140 if (mPos > 1 && ((mBoundaryMode & 2) == 2)) { 141 mPos = 2 - mPos; 142 mV = -mV; 143 } 144 } 145 } 146 } 147 } 148