UNPKG

3.84 kBJavaScriptView Raw
1/* global Float32Array */
2
3/*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */
4function 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 /* Must contain four arguments. */
14 if (arguments.length !== 4) {
15 return false;
16 }
17
18 /* Arguments must be numbers. */
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 /* X values must be in the [0, 1] range. */
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
158export default generateCubicBezier;