• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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