1/*
2 * Copyright (C) 2015 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
17package com.android.messaging.util;
18
19import android.view.animation.Interpolator;
20
21/**
22 * Class that acts as an interpolator to match the cubic-bezier css timing function where p0 is
23 * fixed at 0,0 and p3 is fixed at 1,1
24 */
25public class CubicBezierInterpolator implements Interpolator {
26    private final float mX1;
27    private final float mY1;
28    private final float mX2;
29    private final float mY2;
30
31    public CubicBezierInterpolator(final float x1, final float y1, final float x2, final float y2) {
32        mX1 = x1;
33        mY1 = y1;
34        mX2 = x2;
35        mY2 = y2;
36    }
37
38    @Override
39    public float getInterpolation(float v) {
40        return getY(getTForXValue(v));
41    }
42
43    private float getX(final float t) {
44        return getCoordinate(t, mX1, mX2);
45    }
46
47    private float getY(final float t) {
48        return getCoordinate(t, mY1, mY2);
49    }
50
51    private float getCoordinate(float t, float p1, float p2) {
52        // Special case start and end.
53        if (t == 0.0f || t == 1.0f) {
54            return t;
55        }
56
57        // Step one - from 4 points to 3.
58        float ip0 = linearInterpolate(0, p1, t);
59        float ip1 = linearInterpolate(p1, p2, t);
60        float ip2 = linearInterpolate(p2, 1, t);
61
62        // Step two - from 3 points to 2.
63        ip0 = linearInterpolate(ip0, ip1, t);
64        ip1 = linearInterpolate(ip1, ip2, t);
65
66        // Final step - last point.
67        return linearInterpolate(ip0, ip1, t);
68    }
69
70    private float linearInterpolate(float a, float b, float progress) {
71        return a + (b - a) * progress;
72    }
73
74    private float getTForXValue(final float x) {
75        final float epsilon = 1e-6f;
76        final int iterations = 8;
77
78        if (x <= 0.0f) {
79            return 0.0f;
80        } else if (x >= 1.0f) {
81            return 1.0f;
82        }
83
84        // Try gradient descent to solve for t. If it works, it is very fast.
85        float t = x;
86        float minT = 0.0f;
87        float maxT = 1.0f;
88        float value = 0.0f;
89        for (int i = 0; i < iterations; i++) {
90            value = getX(t);
91            double derivative = (getX(t + epsilon) - value) / epsilon;
92            if (Math.abs(value - x) < epsilon) {
93                return t;
94            } else if (Math.abs(derivative) < epsilon) {
95                break;
96            } else {
97                if (value < x) {
98                    minT = t;
99                } else {
100                    maxT = t;
101                }
102                t -= (value - x) / derivative;
103            }
104        }
105
106        // If the gradient descent got stuck in a local minimum, e.g. because the
107        // derivative was close to 0, use an interval bisection instead.
108        for (int i = 0; Math.abs(value - x) > epsilon && i < iterations; i++) {
109            if (value < x) {
110                minT = t;
111                t = (t + maxT) / 2.0f;
112            } else {
113                maxT = t;
114                t = (t + minT) / 2.0f;
115            }
116            value = getX(t);
117        }
118        return t;
119    }
120}
121