1 /* 2 * Copyright (C) 2020 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. 21 * The setup defines a series of velocity gradients that gets to the desired position 22 * ending at 0 velocity. 23 * The path is computed such that the velocities are continuous 24 * 25 * 26 */ 27 public class StopLogicEngine implements StopEngine { 28 // the velocity at the start of each period 29 private float mStage1Velocity, mStage2Velocity, mStage3Velocity; 30 private float mStage1Duration, mStage2Duration, mStage3Duration; // the time for each period 31 private float mStage1EndPosition, mStage2EndPosition, mStage3EndPosition; // ending position 32 private int mNumberOfStages; 33 private String mType; 34 private boolean mBackwards = false; 35 private float mStartPosition; 36 private float mLastPosition; 37 private float mLastTime; 38 @SuppressWarnings("unused") 39 private boolean mDone = false; 40 private static final float EPSILON = 0.00001f; 41 42 /** 43 * Debugging logic to log the state. 44 * 45 * @param desc Description to pre append 46 * @param time Time during animation 47 * @return string useful for debugging the state of the StopLogic 48 */ 49 @Override debug(String desc, float time)50 public String debug(String desc, float time) { 51 String ret = desc + " ===== " + mType + "\n"; 52 ret += desc + (mBackwards ? "backwards" : "forward ") 53 + " time = " + time + " stages " + mNumberOfStages + "\n"; 54 ret += desc + " dur " + mStage1Duration + " vel " 55 + mStage1Velocity + " pos " + mStage1EndPosition + "\n"; 56 57 if (mNumberOfStages > 1) { 58 ret += desc + " dur " + mStage2Duration + " vel " 59 + mStage2Velocity + " pos " + mStage2EndPosition + "\n"; 60 61 } 62 if (mNumberOfStages > 2) { 63 ret += desc + " dur " + mStage3Duration + " vel " 64 + mStage3Velocity + " pos " + mStage3EndPosition + "\n"; 65 } 66 67 if (time <= mStage1Duration) { 68 ret += desc + "stage 0" + "\n"; 69 return ret; 70 } 71 if (mNumberOfStages == 1) { 72 ret += desc + "end stage 0" + "\n"; 73 return ret; 74 } 75 time -= mStage1Duration; 76 if (time < mStage2Duration) { 77 78 ret += desc + " stage 1" + "\n"; 79 return ret; 80 } 81 if (mNumberOfStages == 2) { 82 ret += desc + "end stage 1" + "\n"; 83 return ret; 84 } 85 time -= mStage2Duration; 86 if (time < mStage3Duration) { 87 88 ret += desc + " stage 2" + "\n"; 89 return ret; 90 } 91 ret += desc + " end stage 2" + "\n"; 92 return ret; 93 } 94 95 // @TODO: add description 96 @Override getVelocity(float x)97 public float getVelocity(float x) { 98 if (x <= mStage1Duration) { 99 return mStage1Velocity + (mStage2Velocity - mStage1Velocity) * x / mStage1Duration; 100 } 101 if (mNumberOfStages == 1) { 102 return 0; 103 } 104 x -= mStage1Duration; 105 if (x < mStage2Duration) { 106 107 return mStage2Velocity + (mStage3Velocity - mStage2Velocity) * x / mStage2Duration; 108 } 109 if (mNumberOfStages == 2) { 110 return 0; 111 } 112 x -= mStage2Duration; 113 if (x < mStage3Duration) { 114 115 return mStage3Velocity - mStage3Velocity * x / mStage3Duration; 116 } 117 return 0; 118 } 119 calcY(float time)120 private float calcY(float time) { 121 mDone = false; 122 if (time <= mStage1Duration) { 123 return mStage1Velocity * time + (mStage2Velocity - mStage1Velocity) 124 * time * time / (2 * mStage1Duration); 125 } 126 if (mNumberOfStages == 1) { 127 return mStage1EndPosition; 128 } 129 time -= mStage1Duration; 130 if (time < mStage2Duration) { 131 132 return mStage1EndPosition + mStage2Velocity * time 133 + (mStage3Velocity - mStage2Velocity) * time * time / (2 * mStage2Duration); 134 } 135 if (mNumberOfStages == 2) { 136 return mStage2EndPosition; 137 } 138 time -= mStage2Duration; 139 if (time <= mStage3Duration) { 140 141 return mStage2EndPosition + mStage3Velocity 142 * time - mStage3Velocity * time * time / (2 * mStage3Duration); 143 } 144 mDone = true; 145 return mStage3EndPosition; 146 } 147 148 // @TODO: add description config(float currentPos, float destination, float currentVelocity, float maxTime, float maxAcceleration, float maxVelocity)149 public void config(float currentPos, float destination, float currentVelocity, 150 float maxTime, float maxAcceleration, float maxVelocity) { 151 mDone = false; 152 mStartPosition = currentPos; 153 mBackwards = (currentPos > destination); 154 if (mBackwards) { 155 setup(-currentVelocity, currentPos - destination, 156 maxAcceleration, maxVelocity, maxTime); 157 } else { 158 setup(currentVelocity, destination - currentPos, maxAcceleration, maxVelocity, maxTime); 159 } 160 } 161 162 // @TODO: add description 163 @Override getInterpolation(float v)164 public float getInterpolation(float v) { 165 float y = calcY(v); 166 mLastPosition = y; 167 mLastTime = v; 168 return mBackwards ? mStartPosition - y : mStartPosition + y; 169 } 170 171 @Override getVelocity()172 public float getVelocity() { 173 return mBackwards ? -getVelocity(mLastTime) : getVelocity(mLastTime); 174 } 175 176 @Override isStopped()177 public boolean isStopped() { 178 return getVelocity() < EPSILON && Math.abs(mStage3EndPosition - mLastPosition) < EPSILON; 179 } 180 setup(float velocity, float distance, float maxAcceleration, float maxVelocity, float maxTime)181 private void setup(float velocity, float distance, float maxAcceleration, float maxVelocity, 182 float maxTime) { 183 mDone = false; 184 mStage3EndPosition = distance; 185 if (velocity == 0) { 186 velocity = 0.0001f; 187 } 188 float min_time_to_stop = velocity / maxAcceleration; 189 float stopDistance = min_time_to_stop * velocity / 2; 190 191 if (velocity < 0) { // backward 192 float timeToZeroVelocity = -velocity / maxAcceleration; 193 float reversDistanceTraveled = timeToZeroVelocity * velocity / 2; 194 float totalDistance = distance - reversDistanceTraveled; 195 float peak_v = (float) Math.sqrt(maxAcceleration * totalDistance); 196 if (peak_v < maxVelocity) { // accelerate then decelerate 197 mType = "backward accelerate, decelerate"; 198 this.mNumberOfStages = 2; 199 this.mStage1Velocity = velocity; 200 this.mStage2Velocity = peak_v; 201 this.mStage3Velocity = 0; 202 this.mStage1Duration = (peak_v - velocity) / maxAcceleration; 203 this.mStage2Duration = peak_v / maxAcceleration; 204 this.mStage1EndPosition = (velocity + peak_v) * this.mStage1Duration / 2; 205 this.mStage2EndPosition = distance; 206 this.mStage3EndPosition = distance; 207 return; 208 } 209 mType = "backward accelerate cruse decelerate"; 210 this.mNumberOfStages = 3; 211 this.mStage1Velocity = velocity; 212 this.mStage2Velocity = maxVelocity; 213 this.mStage3Velocity = maxVelocity; 214 215 this.mStage1Duration = (maxVelocity - velocity) / maxAcceleration; 216 this.mStage3Duration = maxVelocity / maxAcceleration; 217 float accDist = (velocity + maxVelocity) * this.mStage1Duration / 2; 218 float decDist = (maxVelocity * this.mStage3Duration) / 2; 219 this.mStage2Duration = (distance - accDist - decDist) / maxVelocity; 220 this.mStage1EndPosition = accDist; 221 this.mStage2EndPosition = (distance - decDist); 222 this.mStage3EndPosition = distance; 223 return; 224 } 225 226 if (stopDistance >= distance) { // we cannot make it hit the breaks. 227 // we do a force hard stop 228 mType = "hard stop"; 229 float time = 2 * distance / velocity; 230 this.mNumberOfStages = 1; 231 this.mStage1Velocity = velocity; 232 this.mStage2Velocity = 0; 233 this.mStage1EndPosition = distance; 234 this.mStage1Duration = time; 235 return; 236 } 237 238 float distance_before_break = distance - stopDistance; 239 float cruseTime = distance_before_break / velocity; // do we just Cruse then stop? 240 if (cruseTime + min_time_to_stop < maxTime) { // close enough maintain v then break 241 mType = "cruse decelerate"; 242 this.mNumberOfStages = 2; 243 this.mStage1Velocity = velocity; 244 this.mStage2Velocity = velocity; 245 this.mStage3Velocity = 0; 246 this.mStage1EndPosition = distance_before_break; 247 this.mStage2EndPosition = distance; 248 this.mStage1Duration = cruseTime; 249 this.mStage2Duration = velocity / maxAcceleration; 250 return; 251 } 252 253 float peak_v = (float) Math.sqrt(maxAcceleration * distance + velocity * velocity / 2); 254 this.mStage1Duration = (peak_v - velocity) / maxAcceleration; 255 this.mStage2Duration = peak_v / maxAcceleration; 256 if (peak_v < maxVelocity) { // accelerate then decelerate 257 mType = "accelerate decelerate"; 258 this.mNumberOfStages = 2; 259 this.mStage1Velocity = velocity; 260 this.mStage2Velocity = peak_v; 261 this.mStage3Velocity = 0; 262 this.mStage1Duration = (peak_v - velocity) / maxAcceleration; 263 this.mStage2Duration = peak_v / maxAcceleration; 264 this.mStage1EndPosition = (velocity + peak_v) * this.mStage1Duration / 2; 265 this.mStage2EndPosition = distance; 266 267 return; 268 } 269 mType = "accelerate cruse decelerate"; 270 // accelerate, cruse then decelerate 271 this.mNumberOfStages = 3; 272 this.mStage1Velocity = velocity; 273 this.mStage2Velocity = maxVelocity; 274 this.mStage3Velocity = maxVelocity; 275 276 this.mStage1Duration = (maxVelocity - velocity) / maxAcceleration; 277 this.mStage3Duration = maxVelocity / maxAcceleration; 278 float accDist = (velocity + maxVelocity) * this.mStage1Duration / 2; 279 float decDist = (maxVelocity * this.mStage3Duration) / 2; 280 281 this.mStage2Duration = (distance - accDist - decDist) / maxVelocity; 282 this.mStage1EndPosition = accDist; 283 this.mStage2EndPosition = (distance - decDist); 284 this.mStage3EndPosition = distance; 285 } 286 287 // Support the simple Decelerate use case 288 public static class Decelerate implements StopEngine { 289 private float mDestination; 290 private float mInitialVelocity; 291 private float mAcceleration; 292 private float mLastVelocity; 293 private float mDuration; 294 private float mInitialPos; 295 private boolean mDone = false; 296 297 @Override debug(String desc, float time)298 public String debug(String desc, float time) { 299 return mDuration + " " + mLastVelocity; 300 } 301 302 @Override getVelocity(float time)303 public float getVelocity(float time) { 304 if (time > mDuration) { 305 return 0; 306 } 307 return mLastVelocity = mInitialVelocity + mAcceleration * time; 308 } 309 310 @Override getInterpolation(float time)311 public float getInterpolation(float time) { 312 if (time > mDuration) { 313 mDone = true; 314 return mDestination; 315 } 316 getVelocity(time); 317 return mInitialPos + (mInitialVelocity + mAcceleration * time / 2) * time; 318 } 319 320 @Override getVelocity()321 public float getVelocity() { 322 return mLastVelocity; 323 } 324 325 @Override isStopped()326 public boolean isStopped() { 327 return mDone; 328 } 329 330 /** 331 * Configure simple deceleration controller 332 * 333 * @param currentPos the current position 334 * @param destination the destination position 335 * @param currentVelocity the currentVelocity change in pos / second 336 */ config(float currentPos, float destination, float currentVelocity)337 public void config(float currentPos, float destination, float currentVelocity) { 338 mDone = false; 339 mDestination = destination; 340 mInitialVelocity = currentVelocity; 341 mInitialPos = currentPos; 342 float distance = mDestination - currentPos; 343 mDuration = distance / (currentVelocity / 2); 344 mAcceleration = -currentVelocity / mDuration; 345 } 346 } 347 } 348