1 /* 2 * Copyright (C) 2024 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 package com.android.internal.widget.remotecompose.core.operations.utilities.easing; 17 18 /** 19 * This contains the class to provide the logic for an animation to come to a stop using a spring 20 * model. String debug(String desc, float time); float getVelocity(float time); float 21 * getInterpolation(float time); float getVelocity(); boolean isStopped(); 22 */ 23 public class SpringStopEngine { 24 double mDamping = 0.5f; 25 26 @SuppressWarnings("unused") 27 private static final double UNSET = Double.MAX_VALUE; 28 29 @SuppressWarnings("unused") 30 private boolean mInitialized = false; 31 32 private double mStiffness; 33 private double mTargetPos; 34 35 @SuppressWarnings("unused") 36 private double mLastVelocity; 37 38 private float mLastTime; 39 private float mPos; 40 private float mV; 41 private float mMass; 42 private float mStopThreshold; 43 private int mBoundaryMode = 0; 44 45 // public String debug(String desc, float time) { 46 // return null; 47 // } 48 log(String str)49 void log(String str) { 50 StackTraceElement s = new Throwable().getStackTrace()[1]; 51 String line = 52 ".(" + s.getFileName() + ":" + s.getLineNumber() + ") " + s.getMethodName() + "() "; 53 System.out.println(line + str); 54 } 55 56 /** */ SpringStopEngine()57 public SpringStopEngine() {} 58 59 /** 60 * get the value the sping is pulling towards 61 * 62 * @return the value the sping is pulling towards 63 */ getTargetValue()64 public float getTargetValue() { 65 return (float) mTargetPos; 66 } 67 68 /** 69 * get the value the sping is starting from 70 * 71 * @param v the value the sping is starting from 72 */ setInitialValue(float v)73 public void setInitialValue(float v) { 74 mPos = v; 75 } 76 77 /** 78 * set the value the sping is pulling towards 79 * 80 * @param v the value the sping is pulling towards 81 */ setTargetValue(float v)82 public void setTargetValue(float v) { 83 mTargetPos = v; 84 } 85 86 /** 87 * Create a sping engine with the parameters encoded as an array of floats 88 * 89 * @param parameters the parameters to use 90 */ SpringStopEngine(float[] parameters)91 public SpringStopEngine(float[] parameters) { 92 if (parameters[0] != 0) { 93 throw new RuntimeException(" parameter[0] should be 0"); 94 } 95 96 springParameters( 97 1, 98 parameters[1], 99 parameters[2], 100 parameters[3], 101 Float.floatToRawIntBits(parameters[4])); 102 } 103 104 /** 105 * Config the spring starting conditions 106 * 107 * @param currentPos the current position of the spring 108 * @param target the target position of the spring 109 * @param currentVelocity the current velocity of the spring 110 */ springStart(float currentPos, float target, float currentVelocity)111 public void springStart(float currentPos, float target, float currentVelocity) { 112 mTargetPos = target; 113 mInitialized = false; 114 mPos = currentPos; 115 mLastVelocity = currentVelocity; 116 mLastTime = 0; 117 } 118 119 /** 120 * Config the spring parameters 121 * 122 * @param mass The mass of the spring 123 * @param stiffness The stiffness of the spring 124 * @param damping The dampening factor 125 * @param stopThreshold how low energy must you be to stop 126 * @param boundaryMode The boundary behaviour 127 */ springParameters( float mass, float stiffness, float damping, float stopThreshold, int boundaryMode)128 public void springParameters( 129 float mass, float stiffness, float damping, float stopThreshold, int boundaryMode) { 130 mDamping = damping; 131 mInitialized = false; 132 mStiffness = stiffness; 133 mMass = mass; 134 mStopThreshold = stopThreshold; 135 mBoundaryMode = boundaryMode; 136 mLastTime = 0; 137 } 138 139 /** 140 * get the velocity of the spring at a time 141 * 142 * @param time the time to get the velocity at 143 * @return the velocity of the spring at a time 144 */ getVelocity(float time)145 public float getVelocity(float time) { 146 return (float) mV; 147 } 148 149 /** 150 * get the position of the spring at a time 151 * 152 * @param time the time to get the position at 153 * @return the position of the spring at a time 154 */ get(float time)155 public float get(float time) { 156 compute(time - mLastTime); 157 mLastTime = time; 158 if (isStopped()) { 159 mPos = (float) mTargetPos; 160 } 161 return (float) mPos; 162 } 163 164 /** 165 * get the acceleration of the spring 166 * 167 * @return the acceleration of the spring 168 */ getAcceleration()169 public float getAcceleration() { 170 double k = mStiffness; 171 double c = mDamping; 172 double x = (mPos - mTargetPos); 173 return (float) (-k * x - c * mV) / mMass; 174 } 175 176 /** 177 * get the velocity of the spring 178 * 179 * @return the velocity of the spring 180 */ getVelocity()181 public float getVelocity() { 182 return 0; 183 } 184 185 /** 186 * is the spring stopped 187 * 188 * @return true if the spring is stopped 189 */ isStopped()190 public boolean isStopped() { 191 double x = (mPos - mTargetPos); 192 double k = mStiffness; 193 double v = mV; 194 double m = mMass; 195 double energy = v * v * m + k * x * x; 196 double max_def = Math.sqrt(energy / k); 197 return max_def <= mStopThreshold; 198 } 199 200 /** 201 * increment the spring position over time dt 202 * 203 * @param dt the time to increment the spring position over 204 */ compute(double dt)205 private void compute(double dt) { 206 if (dt <= 0) { 207 // Nothing to compute if there's no time difference 208 return; 209 } 210 211 double k = mStiffness; 212 double c = mDamping; 213 // Estimate how many time we should over sample based on the frequency and current sampling 214 int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4)); 215 dt /= overSample; 216 217 for (int i = 0; i < overSample; i++) { 218 double x = (mPos - mTargetPos); 219 double a = (-k * x - c * mV) / mMass; 220 // This refinement of a simple coding of the acceleration increases accuracy 221 double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity 222 double avgX = mPos + dt * avgV / 2 - mTargetPos; // pass 1 calculate the average pos 223 a = (-avgX * k - avgV * c) / mMass; // calculate acceleration over that average pos 224 225 double dv = a * dt; // calculate change in velocity 226 avgV = mV + dv / 2; // average velocity is current + half change 227 mV += (float) dv; 228 mPos += (float) (avgV * dt); 229 if (mBoundaryMode > 0) { 230 if (mPos < 0 && ((mBoundaryMode & 1) == 1)) { 231 mPos = -mPos; 232 mV = -mV; 233 } 234 if (mPos > 1 && ((mBoundaryMode & 2) == 2)) { 235 mPos = 2 - mPos; 236 mV = -mV; 237 } 238 } 239 } 240 } 241 } 242