1 |
|
2 |
|
3 |
|
4 | function generateCubicBezier(mX1, mY1, mX2, mY2) {
|
5 | let NEWTON_ITERATIONS = 4,
|
6 | NEWTON_MIN_SLOPE = 0.001,
|
7 | SUBDIVISION_PRECISION = 0.0000001,
|
8 | SUBDIVISION_MAX_ITERATIONS = 10,
|
9 | kSplineTableSize = 11,
|
10 | kSampleStepSize = 1.0 / (kSplineTableSize - 1.0),
|
11 | float32ArraySupported = typeof Float32Array !== 'undefined';
|
12 |
|
13 |
|
14 | if (arguments.length !== 4) {
|
15 | return false;
|
16 | }
|
17 |
|
18 |
|
19 | for (let i = 0; i < 4; ++i) {
|
20 | if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) {
|
21 | return false;
|
22 | }
|
23 | }
|
24 |
|
25 |
|
26 | mX1 = Math.min(mX1, 1);
|
27 | mX2 = Math.min(mX2, 1);
|
28 | mX1 = Math.max(mX1, 0);
|
29 | mX2 = Math.max(mX2, 0);
|
30 |
|
31 | let mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
|
32 |
|
33 | function A(aA1, aA2) {
|
34 | return 1.0 - 3.0 * aA2 + 3.0 * aA1;
|
35 | }
|
36 |
|
37 | function B(aA1, aA2) {
|
38 | return 3.0 * aA2 - 6.0 * aA1;
|
39 | }
|
40 |
|
41 | function C(aA1) {
|
42 | return 3.0 * aA1;
|
43 | }
|
44 |
|
45 | function calcBezier(aT, aA1, aA2) {
|
46 | return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
|
47 | }
|
48 |
|
49 | function getSlope(aT, aA1, aA2) {
|
50 | return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
|
51 | }
|
52 |
|
53 | function newtonRaphsonIterate(aX, aGuessT) {
|
54 | for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
|
55 | let currentSlope = getSlope(aGuessT, mX1, mX2);
|
56 |
|
57 | if (currentSlope === 0.0) {
|
58 | return aGuessT;
|
59 | }
|
60 |
|
61 | let currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
62 | aGuessT -= currentX / currentSlope;
|
63 | }
|
64 |
|
65 | return aGuessT;
|
66 | }
|
67 |
|
68 | function calcSampleValues() {
|
69 | for (let i = 0; i < kSplineTableSize; ++i) {
|
70 | mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
71 | }
|
72 | }
|
73 |
|
74 | function binarySubdivide(aX, aA, aB) {
|
75 | let currentX, currentT, i = 0;
|
76 |
|
77 | do {
|
78 | currentT = aA + (aB - aA) / 2.0;
|
79 | currentX = calcBezier(currentT, mX1, mX2) - aX;
|
80 | if (currentX > 0.0) {
|
81 | aB = currentT;
|
82 | } else {
|
83 | aA = currentT;
|
84 | }
|
85 | } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
86 |
|
87 | return currentT;
|
88 | }
|
89 |
|
90 | function getTForX(aX) {
|
91 | let intervalStart = 0.0,
|
92 | currentSample = 1,
|
93 | lastSample = kSplineTableSize - 1;
|
94 |
|
95 | for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
|
96 | intervalStart += kSampleStepSize;
|
97 | }
|
98 |
|
99 | --currentSample;
|
100 |
|
101 | let dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample]),
|
102 | guessForT = intervalStart + dist * kSampleStepSize,
|
103 | initialSlope = getSlope(guessForT, mX1, mX2);
|
104 |
|
105 | if (initialSlope >= NEWTON_MIN_SLOPE) {
|
106 | return newtonRaphsonIterate(aX, guessForT);
|
107 | } else if (initialSlope === 0.0) {
|
108 | return guessForT;
|
109 | } else {
|
110 | return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
|
111 | }
|
112 | }
|
113 |
|
114 | let _precomputed = false;
|
115 |
|
116 | function precompute() {
|
117 | _precomputed = true;
|
118 | if (mX1 !== mY1 || mX2 !== mY2) {
|
119 | calcSampleValues();
|
120 | }
|
121 | }
|
122 |
|
123 | let f = function(aX) {
|
124 | if (!_precomputed) {
|
125 | precompute();
|
126 | }
|
127 | if (mX1 === mY1 && mX2 === mY2) {
|
128 | return aX;
|
129 | }
|
130 | if (aX === 0) {
|
131 | return 0;
|
132 | }
|
133 | if (aX === 1) {
|
134 | return 1;
|
135 | }
|
136 |
|
137 | return calcBezier(getTForX(aX), mY1, mY2);
|
138 | };
|
139 |
|
140 | f.getControlPoints = function() {
|
141 | return [{
|
142 | x: mX1,
|
143 | y: mY1
|
144 | }, {
|
145 | x: mX2,
|
146 | y: mY2
|
147 | }];
|
148 | };
|
149 |
|
150 | let str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")";
|
151 | f.toString = function() {
|
152 | return str;
|
153 | };
|
154 |
|
155 | return f;
|
156 | }
|
157 |
|
158 | export default generateCubicBezier;
|