UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

253 lines (218 loc) • 6.01 kB
'use strict'; export const PATH_COMMAND_LENGTHS: Record<string, number> = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0, }; export const SEGMENT_PATTERN = /([achlmqstvz])([^achlmqstvz]*)/gi; export const NUMBER_PATTERN = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi; export function reflectControlPoint( curX: number, curY: number, oldX: number, oldY: number ): [number, number] { return [2 * curX - oldX, 2 * curY - oldY]; } export function lineToCubic( x1: number, y1: number, x2: number, y2: number ): number[] { return [ x1 + (x2 - x1) / 3, y1 + (y2 - y1) / 3, x1 + (2 * (x2 - x1)) / 3, y1 + (2 * (y2 - y1)) / 3, x2, y2, ]; } export function quadraticToCubic( curX: number, curY: number, qcx: number, qcy: number, x: number, y: number ): [number, number, number, number, number, number] { const cp1x = curX + (2 / 3) * (qcx - curX); const cp1y = curY + (2 / 3) * (qcy - curY); const cp2x = x + (2 / 3) * (qcx - x); const cp2y = y + (2 / 3) * (qcy - y); return [cp1x, cp1y, cp2x, cp2y, x, y]; } export function arcToCubic( startX: number, startY: number, radiusX: number, radiusY: number, xAxisRotation: number, largeArcFlag: number, sweepFlag: number, endX: number, endY: number ): number[][] { // Identical endpoints -- omit the segment if (startX === endX && startY === endY) { return []; } radiusX = Math.abs(radiusX); radiusY = Math.abs(radiusY); // Degenerate case: if (radiusX === 0 || radiusY === 0) { return [lineToCubic(startX, startY, endX, endY)]; } const fA = largeArcFlag !== 0 ? 1 : 0; const fS = sweepFlag !== 0 ? 1 : 0; const phi = ((xAxisRotation % 360) * Math.PI) / 180; const sinPhi = Math.sin(phi); const cosPhi = Math.cos(phi); const { centerX, centerY, startAngle, deltaAngle, correctedRadiusX, correctedRadiusY, } = getCenterParameterization( startX, startY, radiusX, radiusY, fA, fS, endX, endY, sinPhi, cosPhi ); return splitArcIntoSegments( centerX, centerY, startAngle, deltaAngle, correctedRadiusX, correctedRadiusY, sinPhi, cosPhi ); } function getCenterParameterization( startX: number, startY: number, radiusX: number, radiusY: number, largeArcFlag: number, sweepFlag: number, endX: number, endY: number, sinRotation: number, cosRotation: number ) { const xPrime = (cosRotation * (startX - endX)) / 2 + (sinRotation * (startY - endY)) / 2; const yPrime = (-sinRotation * (startX - endX)) / 2 + (cosRotation * (startY - endY)) / 2; const xPrimeSq = xPrime * xPrime; const yPrimeSq = yPrime * yPrime; let correctedRadiusX = radiusX; let correctedRadiusY = radiusY; const radCheck = xPrimeSq / (correctedRadiusX * correctedRadiusX) + yPrimeSq / (correctedRadiusY * correctedRadiusY); if (radCheck > 1) { const scale = Math.sqrt(radCheck); correctedRadiusX *= scale; correctedRadiusY *= scale; } const crxSq = correctedRadiusX * correctedRadiusX; const crySq = correctedRadiusY * correctedRadiusY; const sign = largeArcFlag === sweepFlag ? -1 : 1; const numerator = Math.max( 0, crxSq * crySq - crxSq * yPrimeSq - crySq * xPrimeSq ); const denominator = crxSq * yPrimeSq + crySq * xPrimeSq; const coef = sign * Math.sqrt(numerator / denominator); const cxp = coef * ((correctedRadiusX * yPrime) / correctedRadiusY); const cyp = coef * (-(correctedRadiusY * xPrime) / correctedRadiusX); const centerX = cosRotation * cxp - sinRotation * cyp + (startX + endX) / 2; const centerY = sinRotation * cxp + cosRotation * cyp + (startY + endY) / 2; const startVectorX = (xPrime - cxp) / correctedRadiusX; const startVectorY = (yPrime - cyp) / correctedRadiusY; const endVectorX = (-xPrime - cxp) / correctedRadiusX; const endVectorY = (-yPrime - cyp) / correctedRadiusY; const startAngle = calculateAngle(1, 0, startVectorX, startVectorY); let deltaAngle = calculateAngle( startVectorX, startVectorY, endVectorX, endVectorY ); if (sweepFlag === 0 && deltaAngle > 0) deltaAngle -= 2 * Math.PI; if (sweepFlag === 1 && deltaAngle < 0) deltaAngle += 2 * Math.PI; return { centerX, centerY, startAngle, deltaAngle, correctedRadiusX, correctedRadiusY, }; } function calculateAngle( uX: number, uY: number, vX: number, vY: number ): number { const dot = uX * vX + uY * vY; const mag = Math.sqrt(uX * uX + uY * uY) * Math.sqrt(vX * vX + vY * vY); const sign = uX * vY - uY * vX < 0 ? -1 : 1; return sign * Math.acos(Math.max(-1, Math.min(1, dot / mag))); } function splitArcIntoSegments( centerX: number, centerY: number, startAngle: number, deltaAngle: number, radiusX: number, radiusY: number, sinRotation: number, cosRotation: number ): number[][] { const segments = Math.ceil(Math.abs(deltaAngle) / (Math.PI / 2)); const segmentAngle = deltaAngle / segments; const k = (4 / 3) * Math.tan(segmentAngle / 4); const cubics: number[][] = []; for (let i = 0; i < segments; i++) { const a1 = startAngle + i * segmentAngle; const a2 = startAngle + (i + 1) * segmentAngle; const cos1 = Math.cos(a1), sin1 = Math.sin(a1); const cos2 = Math.cos(a2), sin2 = Math.sin(a2); const unitX1 = cos1 - k * sin1; const unitY1 = sin1 + k * cos1; const unitX2 = cos2 + k * sin2; const unitY2 = sin2 - k * cos2; const transformToEllipse = (uX: number, uY: number) => [ centerX + cosRotation * uX * radiusX - sinRotation * uY * radiusY, centerY + sinRotation * uX * radiusX + cosRotation * uY * radiusY, ]; const [cp1x, cp1y] = transformToEllipse(unitX1, unitY1); const [cp2x, cp2y] = transformToEllipse(unitX2, unitY2); const [dstX, dstY] = transformToEllipse(cos2, sin2); cubics.push([cp1x, cp1y, cp2x, cp2y, dstX, dstY]); } return cubics; }