summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/ui/motion/UnitBezier.java
blob: 242f5455652c8996e275bef504dcd0c691fa10d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.camera.ui.motion;

/**
 * This represents is a precomputed cubic bezier curve starting at (0,0) and
 * going to (1,1) with two configurable control points. Once the instance is
 * created, the control points cannot be modified.
 *
 * Generally, this will be used for computing timing curves for with control
 * points where an x value will be provide from 0.0 - 1.0, and the y value will
 * be solved for where y is used as the timing value in some linear
 * interpolation of a value.
 */
public class UnitBezier implements UnitCurve {

    private static final float EPSILON = 1e-6f;

    private final DerivableFloatFn mXFn;
    private final DerivableFloatFn mYFn;

    /**
     * Build and pre-compute a unit bezier. This assumes a starting point of
     * (0, 0) and end point of (1.0, 1.0).
     *
     * @param c0x control point x value for p0
     * @param c0y control point y value for p0
     * @param c1x control point x value for p1
     * @param c1y control point y value for p1
     */
    public UnitBezier(float c0x, float c0y, float c1x, float c1y) {
        mXFn = new CubicBezierFn(c0x, c1x);
        mYFn = new CubicBezierFn(c0y, c1y);
    }

    /**
     * Given a unit bezier curve find the height of the curve at t (which is
     * internally represented as the xAxis).
     *
     * @param t the x position between 0 and 1 to solve for y.
     * @return the closest approximate height of the curve at x.
     */
    @Override
    public float valueAt(float t) {
        return mYFn.value(solve(t, mXFn));
    }

    /**
     * Given a unit bezier curve find a value along the x axis such that
     * valueAt(result) produces the input value.
     *
     * @param value the y position between 0 and 1 to solve for x
     * @return the closest approximate input that will produce value when provided
     * to the valueAt function.
     */
    @Override
    public float tAt(float value) {
        return mXFn.value(solve(value, mYFn));
    }

    private float solve(float target, DerivableFloatFn fn) {
        // For a linear fn, t = value. This makes value a good starting guess.
        float input = target;

        // Newton's method (Faster than bisection)
        for (int i = 0; i < 8; i++) {
            float value = fn.value(input) - target;
            if (Math.abs(value) < EPSILON) {
                return input;
            }
            float derivative = fn.derivative(input);
            if (Math.abs(derivative) < EPSILON) {
                break;
            }
            input = input - value / derivative;
        }

        // Fallback on bi-section
        float min = 0.0f;
        float max = 1.0f;
        input = target;

        if (input < min) {
            return min;
        }
        if (input > max) {
            return max;
        }

        while (min < max) {
            float value = fn.value(input);
            if (Math.abs(value - target) < EPSILON) {
                return input;
            }

            if (target > value) {
                min = input;
            } else {
                max = input;
            }

            input = (max - min) * .5f + min;
        }

        // Give up, return the closest match we got too.
        return input;
    }

    private interface DerivableFloatFn {
        float value(float x);
        float derivative(float x);
    }

    /**
     * Precomputed constants for a given set of control points along a given
     * cubic bezier axis.
     */
    private static class CubicBezierFn implements DerivableFloatFn {
        private final float c;
        private final float a;
        private final float b;

        /**
         * Build and pre-compute a single axis for a unit bezier. This assumes p0
         * is 0 and p1 is 1.
         *
         * @param c0 start control point.
         * @param c1 end control point.
         */
        public CubicBezierFn(float c0, float c1) {
            c = 3.0f * c0;
            b = 3.0f * (c1 - c0) - c;
            a = 1.0f - c - b;
        }

        public float value(float x) {
            return ((a * x + b) * x + c) * x;
        }
        public float derivative(float x) {
            return (3.0f * a * x + 2.0f * b) * x + c;
        }
    }
}