1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | const NEWTON_ITERATIONS = 4;
|
11 | const NEWTON_MIN_SLOPE = 0.001;
|
12 | const SUBDIVISION_PRECISION = 0.0000001;
|
13 | const SUBDIVISION_MAX_ITERATIONS = 10;
|
14 |
|
15 | const kSplineTableSize = 11;
|
16 | const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
17 |
|
18 | function A(aA1: number, aA2: number): number {
|
19 | 'worklet';
|
20 | return 1.0 - 3.0 * aA2 + 3.0 * aA1;
|
21 | }
|
22 | function B(aA1: number, aA2: number): number {
|
23 | 'worklet';
|
24 | return 3.0 * aA2 - 6.0 * aA1;
|
25 | }
|
26 | function C(aA1: number) {
|
27 | 'worklet';
|
28 | return 3.0 * aA1;
|
29 | }
|
30 |
|
31 |
|
32 | function 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 |
|
38 | function 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 |
|
43 | function 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 |
|
69 | function 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 |
|
87 | export 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 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
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 |
|
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;
|
163 | }
|
164 |
|
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 | }
|