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 import java.util.Arrays;
20 
21 /**
22  * Provide the engine for cubic spline easing
23  *
24  *
25  */
26 public class Easing {
27     static Easing sDefault = new Easing();
28     String mStr = "identity";
29     private static final String STANDARD = "cubic(0.4, 0.0, 0.2, 1)";
30     private static final String ACCELERATE = "cubic(0.4, 0.05, 0.8, 0.7)";
31     private static final String DECELERATE = "cubic(0.0, 0.0, 0.2, 0.95)";
32     private static final String LINEAR = "cubic(1, 1, 0, 0)";
33     private static final String ANTICIPATE = "cubic(0.36, 0, 0.66, -0.56)";
34     private static final String OVERSHOOT = "cubic(0.34, 1.56, 0.64, 1)";
35 
36     private static final String DECELERATE_NAME = "decelerate";
37     private static final String ACCELERATE_NAME = "accelerate";
38     private static final String STANDARD_NAME = "standard";
39     private static final String LINEAR_NAME = "linear";
40     private static final String ANTICIPATE_NAME = "anticipate";
41     private static final String OVERSHOOT_NAME = "overshoot";
42 
43     public static String[] NAMED_EASING =
44             {STANDARD_NAME, ACCELERATE_NAME, DECELERATE_NAME, LINEAR_NAME};
45 
46     // @TODO: add description
getInterpolator(String configString)47     public static Easing getInterpolator(String configString) {
48         if (configString == null) {
49             return null;
50         }
51         if (configString.startsWith("cubic")) {
52             return new CubicEasing(configString);
53         } else if (configString.startsWith("spline")) {
54             return new StepCurve(configString);
55         } else if (configString.startsWith("Schlick")) {
56             return new Schlick(configString);
57         } else {
58             switch (configString) {
59                 case STANDARD_NAME:
60                     return new CubicEasing(STANDARD);
61                 case ACCELERATE_NAME:
62                     return new CubicEasing(ACCELERATE);
63                 case DECELERATE_NAME:
64                     return new CubicEasing(DECELERATE);
65                 case LINEAR_NAME:
66                     return new CubicEasing(LINEAR);
67                 case ANTICIPATE_NAME:
68                     return new CubicEasing(ANTICIPATE);
69                 case OVERSHOOT_NAME:
70                     return new CubicEasing(OVERSHOOT);
71                 default:
72                     System.err.println("transitionEasing syntax error syntax:"
73                             + "transitionEasing=\"cubic(1.0,0.5,0.0,0.6)\" or "
74                             + Arrays.toString(NAMED_EASING));
75             }
76 
77         }
78         return sDefault;
79     }
80 
81     // @TODO: add description
get(double x)82     public double get(double x) {
83         return x;
84     }
85 
86     // @TODO: add description
87     @Override
toString()88     public String toString() {
89         return mStr;
90     }
91 
92     // @TODO: add description
getDiff(double x)93     public double getDiff(double x) {
94         return 1;
95     }
96 
97     static class CubicEasing extends Easing {
98 
99         private static double sError = 0.01;
100         private static double sDError = 0.0001;
101         double mX1, mY1, mX2, mY2;
102 
CubicEasing(String configString)103         CubicEasing(String configString) {
104             // done this way for efficiency
105             mStr = configString;
106             int start = configString.indexOf('(');
107             int off1 = configString.indexOf(',', start);
108             mX1 = Double.parseDouble(configString.substring(start + 1, off1).trim());
109             int off2 = configString.indexOf(',', off1 + 1);
110             mY1 = Double.parseDouble(configString.substring(off1 + 1, off2).trim());
111             int off3 = configString.indexOf(',', off2 + 1);
112             mX2 = Double.parseDouble(configString.substring(off2 + 1, off3).trim());
113             int end = configString.indexOf(')', off3 + 1);
114             mY2 = Double.parseDouble(configString.substring(off3 + 1, end).trim());
115         }
116 
CubicEasing(double x1, double y1, double x2, double y2)117         CubicEasing(double x1, double y1, double x2, double y2) {
118             setup(x1, y1, x2, y2);
119         }
120 
setup(double x1, double y1, double x2, double y2)121         void setup(double x1, double y1, double x2, double y2) {
122             this.mX1 = x1;
123             this.mY1 = y1;
124             this.mX2 = x2;
125             this.mY2 = y2;
126         }
127 
getX(double t)128         private double getX(double t) {
129             double t1 = 1 - t;
130             // no need for because start at 0,0 double f0 = (1 - t) * (1 - t) * (1 - t);
131             double f1 = 3 * t1 * t1 * t;
132             double f2 = 3 * t1 * t * t;
133             double f3 = t * t * t;
134             return mX1 * f1 + mX2 * f2 + f3;
135         }
136 
getY(double t)137         private double getY(double t) {
138             double t1 = 1 - t;
139             // no need for because start at 0,0 double f0 = (1 - t) * (1 - t) * (1 - t);
140             double f1 = 3 * t1 * t1 * t;
141             double f2 = 3 * t1 * t * t;
142             double f3 = t * t * t;
143             return mY1 * f1 + mY2 * f2 + f3;
144         }
145 
146         @SuppressWarnings("unused")
getDiffX(double t)147         private double getDiffX(double t) {
148             double t1 = 1 - t;
149             return 3 * t1 * t1 * mX1 + 6 * t1 * t * (mX2 - mX1) + 3 * t * t * (1 - mX2);
150         }
151 
152         @SuppressWarnings("unused")
getDiffY(double t)153         private double getDiffY(double t) {
154             double t1 = 1 - t;
155             return 3 * t1 * t1 * mY1 + 6 * t1 * t * (mY2 - mY1) + 3 * t * t * (1 - mY2);
156         }
157 
158         /**
159          * binary search for the region
160          * and linear interpolate the answer
161          */
162         @Override
getDiff(double x)163         public double getDiff(double x) {
164             double t = 0.5;
165             double range = 0.5;
166             while (range > sDError) {
167                 double tx = getX(t);
168                 range *= 0.5;
169                 if (tx < x) {
170                     t += range;
171                 } else {
172                     t -= range;
173                 }
174             }
175 
176             double x1 = getX(t - range);
177             double x2 = getX(t + range);
178             double y1 = getY(t - range);
179             double y2 = getY(t + range);
180 
181             return (y2 - y1) / (x2 - x1);
182         }
183 
184         /**
185          * binary search for the region
186          * and linear interpolate the answer
187          */
188         @Override
get(double x)189         public double get(double x) {
190             if (x <= 0.0) {
191                 return 0;
192             }
193             if (x >= 1.0) {
194                 return 1.0;
195             }
196             double t = 0.5;
197             double range = 0.5;
198             while (range > sError) {
199                 double tx = getX(t);
200                 range *= 0.5;
201                 if (tx < x) {
202                     t += range;
203                 } else {
204                     t -= range;
205                 }
206             }
207 
208             double x1 = getX(t - range);
209             double x2 = getX(t + range);
210             double y1 = getY(t - range);
211             double y2 = getY(t + range);
212 
213             return (y2 - y1) * (x - x1) / (x2 - x1) + y1;
214         }
215     }
216 }
217