72 lines
2.6 KiB
JavaScript
72 lines
2.6 KiB
JavaScript
import { linear } from ".";
|
|
const a = (a1, a2) => 1.0 - 3.0 * a2 + 3.0 * a1;
|
|
const b = (a1, a2) => 3.0 * a2 - 6.0 * a1;
|
|
const c = (a1) => 3.0 * a1;
|
|
const calcBezier = (t, a1, a2) => ((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t;
|
|
const getSlope = (t, a1, a2) => 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, aA, aB, mX1, mX2) {
|
|
let currentX;
|
|
let currentT;
|
|
let i = 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, aGuessT, mX1, mX2) {
|
|
for (let i = 0; i < newtonIterations; ++i) {
|
|
const currentSlope = getSlope(aGuessT, mX1, mX2);
|
|
if (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, mY1, mX2, mY2) {
|
|
if (mX1 === mY1 && mX2 === mY2)
|
|
return linear;
|
|
const sampleValues = new Float32Array(kSplineTableSize);
|
|
for (let i = 0; i < kSplineTableSize; ++i) {
|
|
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
|
}
|
|
function getTForX(aX) {
|
|
let intervalStart = 0.0;
|
|
let currentSample = 1;
|
|
const lastSample = kSplineTableSize - 1;
|
|
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
|
|
intervalStart += kSampleStepSize;
|
|
}
|
|
--currentSample;
|
|
const dist = (aX - sampleValues[currentSample]) /
|
|
(sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
|
const guessForT = intervalStart + dist * kSampleStepSize;
|
|
const initialSlope = getSlope(guessForT, mX1, mX2);
|
|
if (initialSlope >= newtonMinSlope) {
|
|
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
|
}
|
|
else if (initialSlope === 0.0) {
|
|
return guessForT;
|
|
}
|
|
else {
|
|
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
|
|
}
|
|
}
|
|
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
}
|
|
//# sourceMappingURL=cubic-bezier.js.map
|