1a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk/*
2a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * Copyright (C) 2016 The Android Open Source Project
3a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk *
4a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * except in compliance with the License. You may obtain a copy of the License at
6a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk *
7a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk *      http://www.apache.org/licenses/LICENSE-2.0
8a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk *
9a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * Unless required by applicable law or agreed to in writing, software distributed under the
10a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * KIND, either express or implied. See the License for the specific language governing
12a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk * permissions and limitations under the License.
13a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk */
14a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
15a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monkpackage com.android.systemui.qs;
16a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
17a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monkimport android.graphics.Path;
18a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monkimport android.view.animation.BaseInterpolator;
19a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monkimport android.view.animation.Interpolator;
20a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
21a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monkpublic class PathInterpolatorBuilder {
22a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
23a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    // This governs how accurate the approximation of the Path is.
24a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private static final float PRECISION = 0.002f;
25a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
26a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private float[] mX; // x coordinates in the line
27a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private float[] mY; // y coordinates in the line
28a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private float[] mDist; // Cumulative percentage length of the line
29a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
30a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    public PathInterpolatorBuilder(Path path) {
31a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        initPath(path);
32a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
33a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
34a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    public PathInterpolatorBuilder(float controlX, float controlY) {
35a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        initQuad(controlX, controlY);
36a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
37a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
38a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    public PathInterpolatorBuilder(float controlX1, float controlY1, float controlX2,
39a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float controlY2) {
40a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        initCubic(controlX1, controlY1, controlX2, controlY2);
41a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
42a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
43a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private void initQuad(float controlX, float controlY) {
44a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        Path path = new Path();
45a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        path.moveTo(0, 0);
46a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        path.quadTo(controlX, controlY, 1f, 1f);
47a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        initPath(path);
48a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
49a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
50a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private void initCubic(float x1, float y1, float x2, float y2) {
51a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        Path path = new Path();
52a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        path.moveTo(0, 0);
53a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        path.cubicTo(x1, y1, x2, y2, 1f, 1f);
54a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        initPath(path);
55a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
56a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
57a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private void initPath(Path path) {
58a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        float[] pointComponents = path.approximate(PRECISION);
59a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
60a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        int numPoints = pointComponents.length / 3;
61a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        if (pointComponents[1] != 0 || pointComponents[2] != 0
62a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                || pointComponents[pointComponents.length - 2] != 1
63a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                || pointComponents[pointComponents.length - 1] != 1) {
64a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
65a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        }
66a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
67a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        mX = new float[numPoints];
68a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        mY = new float[numPoints];
69a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        mDist = new float[numPoints];
70a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        float prevX = 0;
71a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        float prevFraction = 0;
72a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        int componentIndex = 0;
73a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        for (int i = 0; i < numPoints; i++) {
74a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float fraction = pointComponents[componentIndex++];
75a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float x = pointComponents[componentIndex++];
76a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float y = pointComponents[componentIndex++];
77a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            if (fraction == prevFraction && x != prevX) {
78a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                throw new IllegalArgumentException(
79a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                        "The Path cannot have discontinuity in the X axis.");
80a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
81a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            if (x < prevX) {
82a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                throw new IllegalArgumentException("The Path cannot loop back on itself.");
83a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
84a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            mX[i] = x;
85a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            mY[i] = y;
86a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            if (i > 0) {
87a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                float dx = mX[i] - mX[i - 1];
88a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                float dy = mY[i] - mY[i - 1];
89a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                float dist = (float) Math.sqrt(dx * dx + dy * dy);
90a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                mDist[i] = mDist[i - 1] + dist;
91a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
92a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            prevX = x;
93a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            prevFraction = fraction;
94a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        }
95a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        // Scale down dist to 0-1.
96a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        float max = mDist[mDist.length - 1];
97a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        for (int i = 0; i < numPoints; i++) {
98a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            mDist[i] /= max;
99a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        }
100a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
101a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
102a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    public Interpolator getXInterpolator() {
103a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        return new PathInterpolator(mDist, mX);
104a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
105a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
106a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    public Interpolator getYInterpolator() {
107a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        return new PathInterpolator(mDist, mY);
108a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
109a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
110a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    private static class PathInterpolator extends BaseInterpolator {
111a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        private final float[] mX; // x coordinates in the line
112a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        private final float[] mY; // y coordinates in the line
113a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
114a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        private PathInterpolator(float[] xs, float[] ys) {
115a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            mX = xs;
116a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            mY = ys;
117a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        }
118a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
119a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        @Override
120a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        public float getInterpolation(float t) {
121a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            if (t <= 0) {
122a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                return 0;
123a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            } else if (t >= 1) {
124a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                return 1;
125a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
126a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            // Do a binary search for the correct x to interpolate between.
127a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            int startIndex = 0;
128a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            int endIndex = mX.length - 1;
129a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
130a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            while (endIndex - startIndex > 1) {
131a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                int midIndex = (startIndex + endIndex) / 2;
132a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                if (t < mX[midIndex]) {
133a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                    endIndex = midIndex;
134a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                } else {
135a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                    startIndex = midIndex;
136a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                }
137a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
138a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
139a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float xRange = mX[endIndex] - mX[startIndex];
140a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            if (xRange == 0) {
141a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk                return mY[startIndex];
142a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            }
143a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
144a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float tInRange = t - mX[startIndex];
145a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float fraction = tInRange / xRange;
146a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
147a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float startY = mY[startIndex];
148a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            float endY = mY[endIndex];
149a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk            return startY + (fraction * (endY - startY));
150a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk        }
151a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk    }
152a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk
153a97776abdeb20250040c0036256ae8bd0943f5c4Jason Monk}
154