diff --git a/packages/react-native-web/src/vendor/react-native/Animated/bezier.js b/packages/react-native-web/src/vendor/react-native/Animated/bezier.js index c7c11f6c88..2e8a924325 100644 --- a/packages/react-native-web/src/vendor/react-native/Animated/bezier.js +++ b/packages/react-native-web/src/vendor/react-native/Animated/bezier.js @@ -11,74 +11,34 @@ /** * BezierEasing - use bezier curve for transition easing function * https://github.com/gre/bezier-easing - * @copyright 2014-2015 Gaëtan Renaudeau. MIT License. + * @copyright 2014-2026 Gaëtan Renaudeau. MIT License. */ 'use strict'; -// These values are established by empiricism with tests (tradeoff: performance VS precision) -const NEWTON_ITERATIONS = 4; -const NEWTON_MIN_SLOPE = 0.001; -const SUBDIVISION_PRECISION = 0.0000001; -const SUBDIVISION_MAX_ITERATIONS = 10; - -const kSplineTableSize = 11; -const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); - -const float32ArraySupported = typeof Float32Array === 'function'; - -function A(aA1, aA2) { - return 1.0 - 3.0 * aA2 + 3.0 * aA1; -} -function B(aA1, aA2) { - return 3.0 * aA2 - 6.0 * aA1; -} -function C(aA1) { - return 3.0 * aA1; -} - -// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. -function calcBezier(aT, aA1, aA2) { - return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; -} - -// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. -function getSlope(aT, aA1, aA2) { - return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); -} - -function binarySubdivide(aX, _aA, _aB, mX1, mX2) { - let currentX, - currentT, - i = 0, - aA = _aA, - aB = _aB; - 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) > SUBDIVISION_PRECISION && - ++i < SUBDIVISION_MAX_ITERATIONS - ); - return currentT; -} - -function newtonRaphsonIterate(aX, _aGuessT, mX1, mX2) { - let aGuessT = _aGuessT; - for (let i = 0; i < NEWTON_ITERATIONS; ++i) { - const currentSlope = getSlope(aGuessT, mX1, mX2); - if (currentSlope === 0.0) { - return aGuessT; - } - const currentX = calcBezier(aGuessT, mX1, mX2) - aX; - aGuessT -= currentX / currentSlope; +// Solves x(t) = ((2a * t + 3b) * t + 3c) * t = x for t, with x in (0, 1): +// u = 1/t is the largest real root of x·u³ − 3c·u² − 3b·u − 2a = 0 +function solveTForX(x: number, a: number, b: number, c: number): number { + const j = 1 / Math.max(c, Math.sqrt(x)); + const k = x * j; + const l = k * j; + const s = c * j; + const q = b * l; + const m = s * s + q; + const h = -s * (s * s + 1.5 * q) - a * k * l; + const D = h * h - m * m * m; + let v: number; + if (m === 0 || D > 1e-12 * h * h) { + // one real root (Cardano) + const U = -Math.cbrt(h < 0 ? h - Math.sqrt(D) : h + Math.sqrt(D)); + v = (U + m / U) || 0; + } else { + // three real roots, take the largest + const r = Math.sqrt(m); + v = + 2 * r * Math.cos(Math.acos(Math.max(-1, Math.min(1, -h / (m * r)))) / 3); } - return aGuessT; + return Math.min(1, k / (v + s)); } export default function bezier( @@ -91,63 +51,29 @@ export default function bezier( throw new Error('bezier x values must be in [0, 1] range'); } - // Precompute samples table - const sampleValues = float32ArraySupported - ? new Float32Array(kSplineTableSize) - : new Array(kSplineTableSize); - if (mX1 !== mY1 || mX2 !== mY2) { - for (let i = 0; i < kSplineTableSize; ++i) { - sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); - } + if (mX1 === mY1 && mX2 === mY2) { + return function LinearEasing(x: number): number { + return x; + }; } - function getTForX(aX) { - 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); - if (initialSlope >= NEWTON_MIN_SLOPE) { - return newtonRaphsonIterate(aX, guessForT, mX1, mX2); - } else if (initialSlope === 0.0) { - return guessForT; - } else { - return binarySubdivide( - aX, - intervalStart, - intervalStart + kSampleStepSize, - mX1, - mX2, - ); - } - } + // x(t) = ((2a * t + 3b) * t + 3c) * t, y(t) = ((ay * t + by) * t + cy) * t + const a = (3 * mX1 - 3 * mX2 + 1) / 2; + const b = mX2 - 2 * mX1; + const c = mX1; + const ay = 3 * mY1 - 3 * mY2 + 1; + const by = 3 * (mY2 - 2 * mY1); + const cy = 3 * mY1; return function BezierEasing(x: number): number { - if (mX1 === mY1 && mX2 === mY2) { - return x; // linear - } - // Because JavaScript number are imprecise, we should guarantee the extremes are right. - if (x === 0) { + // x outside (0, 1) saturates to 0 / 1 + if (x <= 0) { return 0; } - if (x === 1) { + if (x >= 1) { return 1; } - return calcBezier(getTForX(x), mY1, mY2); + const t = solveTForX(x, a, b, c); + return ((ay * t + by) * t + cy) * t; }; };