| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 |
83x
83x
83x
1x
47x
1x
36x
1x
1x
1x
1x
4x
32x
32x
32x
32x
4x
1x
1x
2x
1x
1x
11x
4x
4x
4x
4x
19x
4x
4x
4x
4x
4x
4x
1x
6x
| /*
Bezier function generator
Gaƫtan Renaudeau's BezierEasing
https://github.com/gre/bezier-easing/blob/master/src/index.js
https://github.com/gre/bezier-easing/blob/master/LICENSE
You're a hero
Use
const easeOut = cubicBezier(.17,.67,.83,.67);
const x = easeOut(0.5); // returns 0.627...
*/
import { linear } from "."
const a = (a1: number, a2: number) => 1.0 - 3.0 * a2 + 3.0 * a1
const b = (a1: number, a2: number) => 3.0 * a2 - 6.0 * a1
const c = (a1: number) => 3.0 * a1
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
const calcBezier = (t: number, a1: number, a2: number) =>
((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
const getSlope = (t: number, a1: number, a2: number) =>
3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1)
const subdivisionPrecision = 0.0000001
const subdivisionMaxIterations = 10
function binarySubdivide(
aX: number,
aA: number,
aB: number,
mX1: number,
mX2: number
) {
let currentX: number
let currentT: number
let i: number = 0
do {
currentT = aA + (aB - aA) / 2.0
currentX = calcBezier(currentT, mX1, mX2) - aX
if (currentX > 0.0) {
aB = currentT
} else {
aA = currentT
}
} while (
Math.abs(currentX) > subdivisionPrecision &&
++i < subdivisionMaxIterations
)
return currentT
}
const newtonIterations = 8
const newtonMinSlope = 0.001
function newtonRaphsonIterate(
aX: number,
aGuessT: number,
mX1: number,
mX2: number
) {
for (let i = 0; i < newtonIterations; ++i) {
const currentSlope = getSlope(aGuessT, mX1, mX2)
Iif (currentSlope === 0.0) {
return aGuessT
}
const currentX = calcBezier(aGuessT, mX1, mX2) - aX
aGuessT -= currentX / currentSlope
}
return aGuessT
}
const kSplineTableSize = 11
const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0)
export function cubicBezier(
mX1: number,
mY1: number,
mX2: number,
mY2: number
) {
// If this is a linear gradient, return linear easing
if (mX1 === mY1 && mX2 === mY2) return linear
// Precompute samples table
const sampleValues = new Float32Array(kSplineTableSize)
for (let i = 0; i < kSplineTableSize; ++i) {
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2)
}
function getTForX(aX: number) {
let intervalStart = 0.0
let currentSample = 1
const lastSample = kSplineTableSize - 1
for (
;
currentSample !== lastSample && sampleValues[currentSample] <= aX;
++currentSample
) {
intervalStart += kSampleStepSize
}
--currentSample
// Interpolate to provide an initial guess for t
const dist =
(aX - sampleValues[currentSample]) /
(sampleValues[currentSample + 1] - sampleValues[currentSample])
const guessForT = intervalStart + dist * kSampleStepSize
const initialSlope = getSlope(guessForT, mX1, mX2)
Eif (initialSlope >= newtonMinSlope) {
return newtonRaphsonIterate(aX, guessForT, mX1, mX2)
} else if (initialSlope === 0.0) {
return guessForT
} else {
return binarySubdivide(
aX,
intervalStart,
intervalStart + kSampleStepSize,
mX1,
mX2
)
}
}
// If animation is at start/end, return t without easing
return (t: number) =>
t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2)
}
|