UNPKG

react-native-redash

Version:
254 lines (232 loc) 6.16 kB
import type { Vector } from "./Vectors"; export const { PI } = Math; export const TAU = PI * 2; /** * @summary Convert a boolean value into a number. * This can be useful in reanimated since 0 and 1 are used for conditional statements. * @worklet */ export const bin = (value: boolean): 0 | 1 => { "worklet"; return value ? 1 : 0; }; /** * Linear interpolation * @param value * @param x * @param y * @worklet */ export const mix = (value: number, x: number, y: number) => { "worklet"; return x * (1 - value) + y * value; }; /** * @summary Check is value is almost equal to the target. * @worklet */ export const approximates = ( value: number, target: number, epsilon = 0.001 ) => { "worklet"; return Math.abs(value - target) < epsilon; }; /** * @summary Normalize any radian value between 0 and 2PI. * For example, if the value is -PI/2, it will be comverted to 1.5PI. * Or 4PI will be converted to 0. * @worklet */ export const normalizeRad = (value: number) => { "worklet"; const rest = value % TAU; return rest > 0 ? rest : TAU + rest; }; /** * @summary Transforms an angle from radians to degrees. * @worklet */ export const toDeg = (rad: number) => { "worklet"; return (rad * 180) / Math.PI; }; /** * @summary Transforms an angle from degrees to radians. * @worklet */ export const toRad = (deg: number) => { "worklet"; return (deg * Math.PI) / 180; }; /** * * @summary Returns the average value * @worklet */ export const avg = (values: number[]) => { "worklet"; return values.reduce((a, v) => a + v, 0) / values.length; }; /** * @summary Returns true if node is within lowerBound and upperBound. * @worklet */ export const between = ( value: number, lowerBound: number, upperBound: number, inclusive = true ) => { "worklet"; if (inclusive) { return value >= lowerBound && value <= upperBound; } return value > lowerBound && value < upperBound; }; /** * @summary Clamps a node with a lower and upper bound. * @example clamp(-1, 0, 100); // 0 clamp(1, 0, 100); // 1 clamp(101, 0, 100); // 100 * @worklet */ export const clamp = ( value: number, lowerBound: number, upperBound: number ) => { "worklet"; return Math.min(Math.max(lowerBound, value), upperBound); }; /** * @description Returns the coordinate of a cubic bezier curve. t is the length of the curve from 0 to 1. * cubicBezier(0, p0, p1, p2, p3) equals p0 and cubicBezier(1, p0, p1, p2, p3) equals p3. * p0 and p3 are respectively the starting and ending point of the curve. p1 and p2 are the control points. * @worklet */ export const cubicBezier = ( t: number, from: number, c1: number, c2: number, to: number ) => { "worklet"; const term = 1 - t; const a = 1 * term ** 3 * t ** 0 * from; const b = 3 * term ** 2 * t ** 1 * c1; const c = 3 * term ** 1 * t ** 2 * c2; const d = 1 * term ** 0 * t ** 3 * to; return a + b + c + d; }; /** * @summary Computes animation node rounded to precision. * @worklet */ export const round = (value: number, precision = 0) => { "worklet"; const p = Math.pow(10, precision); return Math.round(value * p) / p; }; // https://stackoverflow.com/questions/27176423/function-to-solve-cubic-equation-analytically const cuberoot = (x: number) => { "worklet"; const y = Math.pow(Math.abs(x), 1 / 3); return x < 0 ? -y : y; }; const solveCubic = (a: number, b: number, c: number, d: number) => { "worklet"; if (Math.abs(a) < 1e-8) { // Quadratic case, ax^2+bx+c=0 a = b; b = c; c = d; if (Math.abs(a) < 1e-8) { // Linear case, ax+b=0 a = b; b = c; if (Math.abs(a) < 1e-8) { // Degenerate case return []; } return [-b / a]; } const D = b * b - 4 * a * c; if (Math.abs(D) < 1e-8) { return [-b / (2 * a)]; } else if (D > 0) { return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)]; } return []; } // Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a) const p = (3 * a * c - b * b) / (3 * a * a); const q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a); let roots; if (Math.abs(p) < 1e-8) { // p = 0 -> t^3 = -q -> t = -q^1/3 roots = [cuberoot(-q)]; } else if (Math.abs(q) < 1e-8) { // q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0 roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []); } else { const D = (q * q) / 4 + (p * p * p) / 27; if (Math.abs(D) < 1e-8) { // D = 0 -> two roots roots = [(-1.5 * q) / p, (3 * q) / p]; } else if (D > 0) { // Only one real root const u = cuberoot(-q / 2 - Math.sqrt(D)); roots = [u - p / (3 * u)]; } else { // D < 0, three roots, but needs to use complex numbers/trigonometric solution const u = 2 * Math.sqrt(-p / 3); const t = Math.acos((3 * q) / p / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1] const k = (2 * Math.PI) / 3; roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)]; } } // Convert back from depressed cubic for (let i = 0; i < roots.length; i++) { roots[i] -= b / (3 * a); } return roots; }; /** * @summary Given a cubic Bèzier curve, return the y value for x. * @example const x = 116; const from = vec.create(59, 218); const c1 = vec.create(131, 39); const c2 = vec.create(204, 223); const to = vec.create(227, 89); // y= 139 const y = cubicBezierYForX(x, from, c1, c2, to))); * @worklet */ export const cubicBezierYForX = ( x: number, a: Vector, b: Vector, c: Vector, d: Vector, precision = 2 ) => { "worklet"; const pa = -a.x + 3 * b.x - 3 * c.x + d.x; const pb = 3 * a.x - 6 * b.x + 3 * c.x; const pc = -3 * a.x + 3 * b.x; const pd = a.x - x; // eslint-disable-next-line prefer-destructuring const t = solveCubic(pa, pb, pc, pd) .map((root) => round(root, precision)) .filter((root) => root >= 0 && root <= 1)[0]; return cubicBezier(t, a.y, b.y, c.y, d.y); }; export const fract = (x: number) => { "worklet"; return x - Math.floor(x); };