UNPKG

3.87 kBJavaScriptView Raw
1"use strict";
2// Taken from https://github.com/facebook/react-native/blob/0b9ea60b4fee8cacc36e7160e31b91fc114dbc0d/Libraries/Animated/src/bezier.js
3Object.defineProperty(exports, "__esModule", { value: true });
4exports.bezier = void 0;
5const NEWTON_ITERATIONS = 4;
6const NEWTON_MIN_SLOPE = 0.001;
7const SUBDIVISION_PRECISION = 0.0000001;
8const SUBDIVISION_MAX_ITERATIONS = 10;
9const kSplineTableSize = 11;
10const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
11const float32ArraySupported = typeof Float32Array === 'function';
12function a(aA1, aA2) {
13 return 1.0 - 3.0 * aA2 + 3.0 * aA1;
14}
15function b(aA1, aA2) {
16 return 3.0 * aA2 - 6.0 * aA1;
17}
18function c(aA1) {
19 return 3.0 * aA1;
20}
21// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
22function calcBezier(aT, aA1, aA2) {
23 return ((a(aA1, aA2) * aT + b(aA1, aA2)) * aT + c(aA1)) * aT;
24}
25// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
26function getSlope(aT, aA1, aA2) {
27 return 3.0 * a(aA1, aA2) * aT * aT + 2.0 * b(aA1, aA2) * aT + c(aA1);
28}
29function binarySubdivide({ aX, _aA, _aB, mX1, mX2, }) {
30 let currentX;
31 let currentT;
32 let i = 0;
33 let aA = _aA;
34 let aB = _aB;
35 do {
36 currentT = aA + (aB - aA) / 2.0;
37 currentX = calcBezier(currentT, mX1, mX2) - aX;
38 if (currentX > 0.0) {
39 aB = currentT;
40 }
41 else {
42 aA = currentT;
43 }
44 } while (Math.abs(currentX) > SUBDIVISION_PRECISION &&
45 ++i < SUBDIVISION_MAX_ITERATIONS);
46 return currentT;
47}
48function newtonRaphsonIterate(aX, _aGuessT, mX1, mX2) {
49 let aGuessT = _aGuessT;
50 for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
51 const currentSlope = getSlope(aGuessT, mX1, mX2);
52 if (currentSlope === 0.0) {
53 return aGuessT;
54 }
55 const currentX = calcBezier(aGuessT, mX1, mX2) - aX;
56 aGuessT -= currentX / currentSlope;
57 }
58 return aGuessT;
59}
60function bezier(mX1, mY1, mX2, mY2) {
61 if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
62 throw new Error('bezier x values must be in [0, 1] range');
63 }
64 // Precompute samples table
65 const sampleValues = float32ArraySupported
66 ? new Float32Array(kSplineTableSize)
67 : new Array(kSplineTableSize);
68 if (mX1 !== mY1 || mX2 !== mY2) {
69 for (let i = 0; i < kSplineTableSize; ++i) {
70 sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
71 }
72 }
73 function getTForX(aX) {
74 let intervalStart = 0.0;
75 let currentSample = 1;
76 const lastSample = kSplineTableSize - 1;
77 for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
78 intervalStart += kSampleStepSize;
79 }
80 --currentSample;
81 // Interpolate to provide an initial guess for t
82 const dist = (aX - sampleValues[currentSample]) /
83 (sampleValues[currentSample + 1] - sampleValues[currentSample]);
84 const guessForT = intervalStart + dist * kSampleStepSize;
85 const initialSlope = getSlope(guessForT, mX1, mX2);
86 if (initialSlope >= NEWTON_MIN_SLOPE) {
87 return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
88 }
89 if (initialSlope === 0.0) {
90 return guessForT;
91 }
92 return binarySubdivide({
93 aX,
94 _aA: intervalStart,
95 _aB: intervalStart + kSampleStepSize,
96 mX1,
97 mX2,
98 });
99 }
100 return function (x) {
101 if (mX1 === mY1 && mX2 === mY2) {
102 return x; // linear
103 }
104 // Because JavaScript number are imprecise, we should guarantee the extremes are right.
105 if (x === 0) {
106 return 0;
107 }
108 if (x === 1) {
109 return 1;
110 }
111 return calcBezier(getTForX(x), mY1, mY2);
112 };
113}
114exports.bezier = bezier;