1 /*
2  * Copyright (C) 2021 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 using a spring
21  * model.
22  *
23  *
24  */
25 public class SpringStopEngine implements StopEngine {
26     double mDamping = 0.5f;
27     @SuppressWarnings("unused") private static final double UNSET = Double.MAX_VALUE;
28     @SuppressWarnings("unused") private boolean mInitialized = false;
29     private double mStiffness;
30     private double mTargetPos;
31     @SuppressWarnings("unused") private double mLastVelocity;
32     private float mLastTime;
33     private float mPos;
34     private float mV;
35     private float mMass;
36     private float mStopThreshold;
37     private int mBoundaryMode = 0;
38 
39     @Override
debug(String desc, float time)40     public String debug(String desc, float time) {
41         return null;
42     }
43 
log(String str)44     void log(String str) {
45         StackTraceElement s = new Throwable().getStackTrace()[1];
46         String line = ".(" + s.getFileName() + ":"
47                 + s.getLineNumber() + ") " + s.getMethodName() + "() ";
48         System.out.println(line + str);
49     }
50 
51     // @TODO: add description
springConfig(float currentPos, float target, float currentVelocity, float mass, float stiffness, float damping, float stopThreshold, int boundaryMode)52     public void springConfig(float currentPos,
53             float target,
54             float currentVelocity,
55             float mass,
56             float stiffness,
57             float damping,
58             float stopThreshold,
59             int boundaryMode) {
60         mTargetPos = target;
61         mDamping = damping;
62         mInitialized = false;
63         mPos = currentPos;
64         mLastVelocity = currentVelocity;
65         mStiffness = stiffness;
66         mMass = mass;
67         mStopThreshold = stopThreshold;
68         mBoundaryMode = boundaryMode;
69         mLastTime = 0;
70     }
71 
72     @Override
getVelocity(float time)73     public float getVelocity(float time) {
74         return (float) mV;
75     }
76 
77     @Override
getInterpolation(float time)78     public float getInterpolation(float time) {
79         compute(time - mLastTime);
80         mLastTime = time;
81         if (isStopped()) {
82             mPos = (float) mTargetPos;
83         }
84         return (float) mPos;
85     }
86 
87     // @TODO: add description
getAcceleration()88     public float getAcceleration() {
89         double k = mStiffness;
90         double c = mDamping;
91         double x = (mPos - mTargetPos);
92         return (float) (-k * x - c * mV) / mMass;
93     }
94 
95     @Override
getVelocity()96     public float getVelocity() {
97         return 0;
98     }
99 
100     @Override
isStopped()101     public boolean isStopped() {
102         double x = (mPos - mTargetPos);
103         double k = mStiffness;
104         double v = mV;
105         double m = mMass;
106         double energy = v * v * m + k * x * x;
107         double max_def = Math.sqrt(energy / k);
108         return max_def <= mStopThreshold;
109     }
110 
compute(double dt)111     private void compute(double dt) {
112         if (dt <= 0) {
113             // Nothing to compute if there's no time difference
114             return;
115         }
116 
117         double k = mStiffness;
118         double c = mDamping;
119         // Estimate how many time we should over sample based on the frequency and current sampling
120         int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4));
121         dt /= overSample;
122 
123         for (int i = 0; i < overSample; i++) {
124             double x = (mPos - mTargetPos);
125             double a = (-k * x - c * mV) / mMass;
126             // This refinement of a simple coding of the acceleration increases accuracy
127             double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity
128             double avgX = mPos + dt * avgV / 2 - mTargetPos; // pass 1 calculate the average pos
129             a = (-avgX * k - avgV * c) / mMass; //  calculate acceleration over that average pos
130 
131             double dv = a * dt; //  calculate change in velocity
132             avgV = mV + dv / 2; //  average  velocity is current + half change
133             mV += (float) dv;
134             mPos += (float) (avgV * dt);
135             if (mBoundaryMode > 0) {
136                 if (mPos < 0 && ((mBoundaryMode & 1) == 1)) {
137                     mPos = -mPos;
138                     mV = -mV;
139                 }
140                 if (mPos > 1 && ((mBoundaryMode & 2) == 2)) {
141                     mPos = 2 - mPos;
142                     mV = -mV;
143                 }
144             }
145         }
146     }
147 }
148