UNPKG

4.28 kBPlain TextView Raw
1'use strict';
2/**
3 * https://github.com/gre/bezier-easing
4 * BezierEasing - use bezier curve for transition easing function
5 * by Gaëtan Renaudeau 2014 - 2015 – MIT License
6 */
7
8// These values are established by empiricism with tests (tradeoff: performance VS precision)
9
10const NEWTON_ITERATIONS = 4;
11const NEWTON_MIN_SLOPE = 0.001;
12const SUBDIVISION_PRECISION = 0.0000001;
13const SUBDIVISION_MAX_ITERATIONS = 10;
14
15const kSplineTableSize = 11;
16const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
17
18function A(aA1: number, aA2: number): number {
19 'worklet';
20 return 1.0 - 3.0 * aA2 + 3.0 * aA1;
21}
22function B(aA1: number, aA2: number): number {
23 'worklet';
24 return 3.0 * aA2 - 6.0 * aA1;
25}
26function C(aA1: number) {
27 'worklet';
28 return 3.0 * aA1;
29}
30
31// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
32function calcBezier(aT: number, aA1: number, aA2: number): number {
33 'worklet';
34 return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
35}
36
37// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
38function getSlope(aT: number, aA1: number, aA2: number): number {
39 'worklet';
40 return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
41}
42
43function binarySubdivide(
44 aX: number,
45 aA: number,
46 aB: number,
47 mX1: number,
48 mX2: number
49): number {
50 'worklet';
51 let currentX;
52 let currentT;
53 let i = 0;
54 do {
55 currentT = aA + (aB - aA) / 2.0;
56 currentX = calcBezier(currentT, mX1, mX2) - aX;
57 if (currentX > 0.0) {
58 aB = currentT;
59 } else {
60 aA = currentT;
61 }
62 } while (
63 Math.abs(currentX) > SUBDIVISION_PRECISION &&
64 ++i < SUBDIVISION_MAX_ITERATIONS
65 );
66 return currentT;
67}
68
69function newtonRaphsonIterate(
70 aX: number,
71 aGuessT: number,
72 mX1: number,
73 mX2: number
74): number {
75 'worklet';
76 for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
77 const currentSlope = getSlope(aGuessT, mX1, mX2);
78 if (currentSlope === 0.0) {
79 return aGuessT;
80 }
81 const currentX = calcBezier(aGuessT, mX1, mX2) - aX;
82 aGuessT -= currentX / currentSlope;
83 }
84 return aGuessT;
85}
86
87export function Bezier(
88 mX1: number,
89 mY1: number,
90 mX2: number,
91 mY2: number
92): (x: number) => number {
93 'worklet';
94
95 function LinearEasing(x: number): number {
96 'worklet';
97 return x;
98 }
99
100 if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
101 throw new Error('[Reanimated] Bezier x values must be in [0, 1] range.');
102 }
103
104 if (mX1 === mY1 && mX2 === mY2) {
105 return LinearEasing;
106 }
107
108 // FIXME: Float32Array is not available in Hermes right now
109 //
110 // var float32ArraySupported = typeof Float32Array === 'function';
111 // const sampleValues = float32ArraySupported
112 // ? new Float32Array(kSplineTableSize)
113 // : new Array(kSplineTableSize);
114
115 // Precompute samples table
116 const sampleValues = new Array(kSplineTableSize);
117
118 for (let i = 0; i < kSplineTableSize; ++i) {
119 sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
120 }
121
122 function getTForX(aX: number): number {
123 'worklet';
124 let intervalStart = 0.0;
125 let currentSample = 1;
126 const lastSample = kSplineTableSize - 1;
127
128 for (
129 ;
130 currentSample !== lastSample && sampleValues[currentSample] <= aX;
131 ++currentSample
132 ) {
133 intervalStart += kSampleStepSize;
134 }
135 --currentSample;
136
137 // Interpolate to provide an initial guess for t
138 const dist =
139 (aX - sampleValues[currentSample]) /
140 (sampleValues[currentSample + 1] - sampleValues[currentSample]);
141 const guessForT = intervalStart + dist * kSampleStepSize;
142
143 const initialSlope = getSlope(guessForT, mX1, mX2);
144 if (initialSlope >= NEWTON_MIN_SLOPE) {
145 return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
146 } else if (initialSlope === 0.0) {
147 return guessForT;
148 } else {
149 return binarySubdivide(
150 aX,
151 intervalStart,
152 intervalStart + kSampleStepSize,
153 mX1,
154 mX2
155 );
156 }
157 }
158
159 return function BezierEasing(x) {
160 'worklet';
161 if (mX1 === mY1 && mX2 === mY2) {
162 return x; // linear
163 }
164 // Because JavaScript number are imprecise, we should guarantee the extremes are right.
165 if (x === 0) {
166 return 0;
167 }
168 if (x === 1) {
169 return 1;
170 }
171 return calcBezier(getTForX(x), mY1, mY2);
172 };
173}