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.touch; 17 18 /* 19 * Copyright (C) 2024 The Android Open Source Project 20 * 21 * Licensed under the Apache License, Version 2.0 (the "License"); 22 * you may not use this file except in compliance with the License. 23 * You may obtain a copy of the License at 24 * 25 * http://www.apache.org/licenses/LICENSE-2.0 26 * 27 * Unless required by applicable law or agreed to in writing, software 28 * distributed under the License is distributed on an "AS IS" BASIS, 29 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 * See the License for the specific language governing permissions and 31 * limitations under the License. 32 */ 33 34 /** 35 * This computes an form of easing such that the values constrained to be consistent in velocity The 36 * easing function is also constrained by the configure To have: a maximum time to stop, a maximum 37 * velocity, a maximum acceleration 38 */ 39 public class VelocityEasing { 40 private float mStartPos = 0; 41 private float mStartV = 0; 42 private float mEndPos = 0; 43 private float mDuration = 0; 44 45 private Stage[] mStage = {new Stage(1), new Stage(2), new Stage(3)}; 46 private int mNumberOfStages = 0; 47 private Easing mEasing; 48 private double mEasingAdapterDistance = 0; 49 private double mEasingAdapterA = 0; 50 private double mEasingAdapterB = 0; 51 private boolean mOneDimension = true; 52 private float mTotalEasingDuration = 0; 53 54 /** 55 * get the duration the easing will take 56 * 57 * @return the duration for the easing 58 */ getDuration()59 public float getDuration() { 60 if (mEasing != null) { 61 return mTotalEasingDuration; 62 } 63 return mDuration; 64 } 65 66 /** 67 * Get the velocity at time t 68 * 69 * @param t time in seconds 70 * @return the velocity units/second 71 */ getV(float t)72 public float getV(float t) { 73 if (mEasing == null) { 74 for (int i = 0; i < mNumberOfStages; i++) { 75 if (mStage[i].mEndTime > t) { 76 return mStage[i].getVel(t); 77 } 78 } 79 return 0f; 80 } 81 int lastStages = mNumberOfStages - 1; 82 for (int i = 0; i < lastStages; i++) { 83 if (mStage[i].mEndTime > t) { 84 return mStage[i].getVel(t); 85 } 86 } 87 return (float) getEasingDiff((t - mStage[lastStages].mStartTime)); 88 } 89 90 /** 91 * Get the position t seconds after the configure 92 * 93 * @param t time in seconds 94 * @return the position at time t 95 */ getPos(float t)96 public float getPos(float t) { 97 if (mEasing == null) { 98 for (int i = 0; i < mNumberOfStages; i++) { 99 if (mStage[i].mEndTime > t) { 100 return mStage[i].getPos(t); 101 } 102 } 103 return mEndPos; 104 } 105 int lastStages = mNumberOfStages - 1; 106 for (int i = 0; i < lastStages; i++) { 107 if (mStage[i].mEndTime > t) { 108 return mStage[i].getPos(t); 109 } 110 } 111 float ret = (float) getEasing((t - mStage[lastStages].mStartTime)); 112 ret += mStage[lastStages].mStartPos; 113 return ret; 114 } 115 116 @Override toString()117 public String toString() { 118 String s = " "; 119 for (int i = 0; i < mNumberOfStages; i++) { 120 Stage stage = mStage[i]; 121 s += " $i $stage"; 122 } 123 return s; 124 } 125 126 /** 127 * Configure the Velocity easing curve The system is in arbitrary units 128 * 129 * @param currentPos the current position 130 * @param destination the destination 131 * @param currentVelocity the current velocity units/seconds 132 * @param maxTime the max time to achieve position 133 * @param maxAcceleration the max acceleration units/s^2 134 * @param maxVelocity the maximum velocity 135 * @param easing End in using this easing curve 136 */ config( float currentPos, float destination, float currentVelocity, float maxTime, float maxAcceleration, float maxVelocity, Easing easing)137 public void config( 138 float currentPos, 139 float destination, 140 float currentVelocity, 141 float maxTime, 142 float maxAcceleration, 143 float maxVelocity, 144 Easing easing) { 145 float pos = currentPos; 146 float velocity = currentVelocity; 147 if (pos == destination) { 148 pos += 1f; 149 } 150 mStartPos = pos; 151 mEndPos = destination; 152 if (easing != null) { 153 this.mEasing = easing.clone(); 154 } 155 float dir = Math.signum(destination - pos); 156 float maxV = maxVelocity * dir; 157 float maxA = maxAcceleration * dir; 158 if (velocity == 0.0) { 159 velocity = 0.0001f * dir; 160 } 161 mStartV = velocity; 162 if (!rampDown(pos, destination, velocity, maxTime)) { 163 if (!(mOneDimension 164 && cruseThenRampDown(pos, destination, velocity, maxTime, maxA, maxV))) { 165 if (!rampUpRampDown(pos, destination, velocity, maxA, maxV, maxTime)) { 166 rampUpCruseRampDown(pos, destination, velocity, maxA, maxV, maxTime); 167 } 168 } 169 } 170 if (mOneDimension) { 171 configureEasingAdapter(); 172 } 173 } 174 rampDown( float currentPos, float destination, float currentVelocity, float maxTime)175 private boolean rampDown( 176 float currentPos, float destination, float currentVelocity, float maxTime) { 177 float timeToDestination = 2 * ((destination - currentPos) / currentVelocity); 178 if (timeToDestination > 0 && timeToDestination <= maxTime) { // hit the brakes 179 mNumberOfStages = 1; 180 mStage[0].setUp(currentVelocity, currentPos, 0f, 0f, destination, timeToDestination); 181 mDuration = timeToDestination; 182 return true; 183 } 184 return false; 185 } 186 cruseThenRampDown( float currentPos, float destination, float currentVelocity, float maxTime, float maxA, float maxV)187 private boolean cruseThenRampDown( 188 float currentPos, 189 float destination, 190 float currentVelocity, 191 float maxTime, 192 float maxA, 193 float maxV) { 194 float timeToBreak = currentVelocity / maxA; 195 float brakeDist = currentVelocity * timeToBreak / 2; 196 float cruseDist = destination - currentPos - brakeDist; 197 float cruseTime = cruseDist / currentVelocity; 198 float totalTime = cruseTime + timeToBreak; 199 if (totalTime > 0 && totalTime < maxTime) { 200 mNumberOfStages = 2; 201 mStage[0].setUp(currentVelocity, currentPos, 0f, currentVelocity, cruseDist, cruseTime); 202 mStage[1].setUp( 203 currentVelocity, 204 currentPos + cruseDist, 205 cruseTime, 206 0f, 207 destination, 208 cruseTime + timeToBreak); 209 mDuration = cruseTime + timeToBreak; 210 return true; 211 } 212 return false; 213 } 214 rampUpRampDown( float currentPos, float destination, float currentVelocity, float maxA, float maxVelocity, float maxTime)215 private boolean rampUpRampDown( 216 float currentPos, 217 float destination, 218 float currentVelocity, 219 float maxA, 220 float maxVelocity, 221 float maxTime) { 222 float peak_v = 223 Math.signum(maxA) 224 * (float) 225 Math.sqrt( 226 (maxA * (destination - currentPos) 227 + currentVelocity * currentVelocity / 2)); 228 if (maxVelocity / peak_v > 1) { 229 float t1 = (peak_v - currentVelocity) / maxA; 230 float d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos; 231 float t2 = peak_v / maxA; 232 mNumberOfStages = 2; 233 mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1); 234 mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1); 235 mDuration = t2 + t1; 236 if (mDuration > maxTime) { 237 return false; 238 } 239 if (mDuration < maxTime / 2) { 240 t1 = mDuration / 2; 241 t2 = t1; 242 peak_v = (2 * (destination - currentPos) / t1 - currentVelocity) / 2; 243 d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos; 244 mNumberOfStages = 2; 245 mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1); 246 mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1); 247 mDuration = t2 + t1; 248 if (mDuration > maxTime) { 249 return false; 250 } 251 } 252 return true; 253 } 254 return false; 255 } 256 rampUpCruseRampDown( float currentPos, float destination, float currentVelocity, float maxA, float maxV, float maxTime)257 private void rampUpCruseRampDown( 258 float currentPos, 259 float destination, 260 float currentVelocity, 261 float maxA, 262 float maxV, 263 float maxTime) { 264 float t1 = maxTime / 3; 265 float t2 = t1 * 2; 266 float distance = destination - currentPos; 267 float dt2 = t2 - t1; 268 float dt3 = maxTime - t2; 269 float v1 = (2 * distance - currentVelocity * t1) / (t1 + 2 * dt2 + dt3); 270 mDuration = maxTime; 271 float d1 = (currentVelocity + v1) * t1 / 2; 272 float d2 = (v1 + v1) * (t2 - t1) / 2; 273 mNumberOfStages = 3; 274 float acc = (v1 - currentVelocity) / t1; 275 float dec = v1 / dt3; 276 mStage[0].setUp(currentVelocity, currentPos, 0f, v1, currentPos + d1, t1); 277 mStage[1].setUp(v1, currentPos + d1, t1, v1, currentPos + d1 + d2, t2); 278 mStage[2].setUp(v1, currentPos + d1 + d2, t2, 0f, destination, maxTime); 279 mDuration = maxTime; 280 } 281 getEasing(double t)282 double getEasing(double t) { 283 double gx = t * t * mEasingAdapterA + t * mEasingAdapterB; 284 if (gx > 1) { 285 return mEasingAdapterDistance; 286 } else { 287 return mEasing.get(gx) * mEasingAdapterDistance; 288 } 289 } 290 getEasingDiff(double t)291 private double getEasingDiff(double t) { 292 double gx = t * t * mEasingAdapterA + t * mEasingAdapterB; 293 if (gx > 1) { 294 return 0.0; 295 } else { 296 return mEasing.getDiff(gx) 297 * mEasingAdapterDistance 298 * (t * mEasingAdapterA + mEasingAdapterB); 299 } 300 } 301 configureEasingAdapter()302 protected void configureEasingAdapter() { 303 if (mEasing == null) { 304 return; 305 } 306 int last = mNumberOfStages - 1; 307 float initialVelocity = mStage[last].mStartV; 308 float distance = mStage[last].mEndPos - mStage[last].mStartPos; 309 float duration = mStage[last].mEndTime - mStage[last].mStartTime; 310 double baseVel = mEasing.getDiff(0.0); 311 mEasingAdapterB = initialVelocity / (baseVel * distance); 312 mEasingAdapterA = 1 - mEasingAdapterB; 313 mEasingAdapterDistance = distance; 314 double easingDuration = 315 (Math.sqrt(4 * mEasingAdapterA + mEasingAdapterB * mEasingAdapterB) 316 - mEasingAdapterB) 317 / (2 * mEasingAdapterA); 318 mTotalEasingDuration = (float) (easingDuration + mStage[last].mStartTime); 319 } 320 321 interface Easing { get(double t)322 double get(double t); 323 getDiff(double t)324 double getDiff(double t); 325 clone()326 Easing clone(); 327 } 328 329 class Stage { 330 private float mStartV = 0; 331 private float mStartPos = 0; 332 private float mStartTime = 0; 333 private float mEndV = 0; 334 private float mEndPos = 0; 335 private float mEndTime = 0; 336 private float mDeltaV = 0; 337 private float mDeltaT = 0; 338 final int mStage; 339 Stage(int n)340 Stage(int n) { 341 mStage = n; 342 } 343 setUp( float startV, float startPos, float startTime, float endV, float endPos, float endTime)344 void setUp( 345 float startV, 346 float startPos, 347 float startTime, 348 float endV, 349 float endPos, 350 float endTime) { 351 this.mStartV = startV; 352 this.mStartPos = startPos; 353 this.mStartTime = startTime; 354 this.mEndV = endV; 355 this.mEndTime = endTime; 356 this.mEndPos = endPos; 357 mDeltaV = this.mEndV - this.mStartV; 358 mDeltaT = this.mEndTime - this.mStartTime; 359 } 360 getPos(float t)361 float getPos(float t) { 362 float dt = t - mStartTime; 363 float pt = dt / mDeltaT; 364 float v = mStartV + mDeltaV * pt; 365 return dt * (mStartV + v) / 2 + mStartPos; 366 } 367 getVel(float t)368 float getVel(float t) { 369 float dt = t - mStartTime; 370 float pt = dt / (mEndTime - mStartTime); 371 return mStartV + mDeltaV * pt; 372 } 373 } 374 } 375