• 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.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