UNPKG

toosoon-utils

Version:
331 lines (330 loc) 12.2 kB
import { EPSILON, PI, TWO_PI } from './constants'; import { lerp } from './maths'; /** * Convert a radians value into degrees * * @param {number} radians Angle in radians * @returns {number} Angle in degrees */ export function toDegrees(radians) { return (radians * 180) / PI; } /** * Convert a degrees value into radians * * @param {number} degrees Angle in degrees * @returns {number} Angle in radians */ export function toRadians(degrees) { return (degrees * PI) / 180; } /** * Calculate the angle from a point to another * * @param {number} x1 X-axis coordinate of the start point * @param {number} y1 Y-axis coordinate of the start point * @param {number} x2 X-axis coordinate of the end point * @param {number} y2 Y-axis coordinate of the end point * @returns {number} Angle */ export function angle(x1, y1, x2, y2) { return Math.atan2(y2 - y1, x2 - x1); } /** * Find the closest angle between to angles * * @param {number} source Source angle (in radians) * @param {number} target Target angle (in radians) * @returns {number} Closest angle */ export function closestAngle(source, target) { const delta = target - source; return delta > PI ? target - 2 * PI : target < -PI ? delta + 2 * PI : target; } /** * Calculate the distance between two points * * @param {number} x1 X-axis coordinate of the first point * @param {number} y1 Y-axis coordinate of the first point * @param {number} x2 X-axis coordinate of the second point * @param {number} y2 Y-axis coordinate of the second point * @returns {number} Computed distance */ export function distance(x1, y1, x2, y2) { const dx = x1 - x2; const dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } /** * Calculate the dot product of two vectors * * @param {number} x1 X-axis coordinate of the first vector * @param {number} y1 Y-axis coordinate of the first vector * @param {number} x2 X-axis coordinate of the second vector * @param {number} y2 Y-axis coordinate of the second vector * @returns {number} Computed dot product */ export function dot(x1, y1, x2, y2) { return x1 * x2 + y1 * y2; } /** * Calculate the cross product of two vectors * * @param {number} x1 X-axis coordinate of the first vector * @param {number} y1 Y-axis coordinate of the first vector * @param {number} x2 X-axis coordinate of the second vector * @param {number} y2 Y-axis coordinate of the second vector * @returns {number} Computed cross product */ export function cross(x1, y1, x2, y2) { return x1 * x2 - y1 * y2; } /** * Calculate the length of the diagonal of a rectangle * * @param {number} width Width of the rectangle * @param {number} height Height of the rectangle * @returns {number} Diagonal length */ export function diagonal(width, height) { return Math.sqrt(width * width + height * height); } /** * Convert radians to a 3D point on the surface of a unit sphere * * @param {number} radius Radius of the sphere * @param {number} phi Polar angle from the y (up) axis : [0, PI] * @param {number} theta Equator angle around the y (up) axis : [0, 2*PI] * @param {Point3} target Target 3D point * @returns {Point3} */ export function radToSphere(radius, phi, theta, target = [0, 0, 0]) { target[0] = radius * Math.sin(phi) * Math.sin(theta); target[1] = radius * Math.cos(phi); target[2] = radius * Math.sin(phi) * Math.cos(theta); return target; } // ********************* // Curves // ********************* /** * Interpolate a point on a line * * @param {number} t Normalized time value to interpolate * @param {number} x1 X-axis coordinate of the start point * @param {number} y1 Y-axis coordinate of the start point * @param {number} x2 X-axis coordinate of the end point * @param {number} y2 Y-axis coordinate of the end point * @returns {Point} Interpolated coordinates on the line */ export function line(t, x1, y1, x2, y2) { const x = lerp(t, x1, x2); const y = lerp(t, y1, y2); return [x, y]; } /** * Interpolate a point on a Quadratic Bézier curve * * @param {number} t Normalized time value to interpolate * @param {number} x1 X-axis coordinate of the start point * @param {number} y1 Y-axis coordinate of the start point * @param {number} cpx X-axis coordinate of the control point * @param {number} cpy Y-axis coordinate of the control point * @param {number} x2 X-axis coordinate of the end point * @param {number} y2 Y-axis coordinate of the end point * @returns {Point} Interpolated coordinates on the curve */ export function quadraticBezier(t, x1, y1, cpx, cpy, x2, y2) { const t2 = t * t; const k = 1 - t; const k2 = k * k; const x = k2 * x1 + 2 * k * t * cpx + t2 * x2; const y = k2 * y1 + 2 * k * t * cpy + t2 * y2; return [x, y]; } /** * Interpolate a point on a Cubic Bézier curve * * @param {number} t Normalized time value to interpolate * @param {number} x1 X-axis coordinate of the start point * @param {number} y1 Y-axis coordinate of the start point * @param {number} cp1x X-axis coordinate of the first control point * @param {number} cp1y Y-axis coordinate of the first control point * @param {number} cp2x X-axis coordinate of the second control point * @param {number} cp2y Y-axis coordinate of the second control point * @param {number} x2 X-axis coordinate of the end point * @param {number} y2 Y-axis coordinate of the end point * @returns {Point} Interpolated coordinates on the curve */ export function cubicBezier(t, x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2) { const t2 = t * t; const t3 = t2 * t; const k = 1 - t; const k2 = k * k; const k3 = k2 * k; const x = k3 * x1 + 3 * k2 * t * cp1x + 3 * k * t2 * cp2x + t3 * x2; const y = k3 * y1 + 3 * k2 * t * cp1y + 3 * k * t2 * cp2y + t3 * y2; return [x, y]; } /** * Interpolate a point on a Catmull-Rom spline * * @param {number} t Normalized time value to interpolate * @param {number} x1 X-axis coordinate of the start point * @param {number} y1 Y-axis coordinate of the start point * @param {number} cp1x X-axis coordinate of the first control point * @param {number} cp1y Y-axis coordinate of the first control point * @param {number} cp2x X-axis coordinate of the second control point * @param {number} cp2y Y-axis coordinate of the second control point * @param {number} x2 X-axis coordinate of the end point * @param {number} y2 Y-axis coordinate of the end point * @returns {Point} Interpolated coordinates on the spline */ export function catmullRom(t, x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2) { const t2 = t * t; const t3 = t2 * t; const vx1 = (cp2x - x1) * 0.5; const vy1 = (cp2y - y1) * 0.5; const vx2 = (x2 - cp1x) * 0.5; const vy2 = (y2 - cp1y) * 0.5; const x = (2 * cp1x - 2 * cp2x + vx1 + vx2) * t3 + (-3 * cp1x + 3 * cp2x - 2 * vx1 - vx2) * t2 + vx1 * t + cp1x; const y = (2 * cp1y - 2 * cp2y + vy1 + vy2) * t3 + (-3 * cp1y + 3 * cp2y - 2 * vy1 - vy2) * t2 + vy1 * t + cp1y; return [x, y]; } /** * Interpolate a point on an elliptical arc * * @param {number} t Normalized time value to interpolate * @param {number} cx X-axis coordinate of the center of the ellipse * @param {number} cy Y-axis coordinate of the center of the ellipse * @param {number} rx X-radius of the ellipse * @param {number} ry Y-radius of the ellipse * @param {number} [rotation=0] Rotation angle of the ellipse (in radians), counterclockwise from the positive X-axis * @param {number} [startAngle=0] Start angle of the arc (in radians) * @param {number} [endAngle=2*PI] End angle of the arc (in radians) * @param {boolean} [counterclockwise=false] Flag indicating the direction of the arc * @returns {Point} Interpolated coordinates on the arc */ export function ellipse(t, cx, cy, rx, ry, rotation = 0, startAngle = 0, endAngle = TWO_PI, counterclockwise = false) { let deltaAngle = endAngle - startAngle; const isEmpty = Math.abs(deltaAngle) <= EPSILON; while (deltaAngle < 0) deltaAngle += TWO_PI; while (deltaAngle > TWO_PI) deltaAngle -= TWO_PI; if (deltaAngle <= EPSILON) { if (isEmpty) { deltaAngle = 0; } else { deltaAngle = TWO_PI; } } if (counterclockwise === true && !isEmpty) { if (deltaAngle === TWO_PI) { deltaAngle = -TWO_PI; } else { deltaAngle = deltaAngle - TWO_PI; } } const angle = startAngle + t * deltaAngle; let x = cx + rx * Math.cos(angle); let y = cy + ry * Math.sin(angle); if (rotation !== 0) { const cos = Math.cos(rotation); const sin = Math.sin(rotation); const deltaX = x - cx; const deltaY = y - cy; x = deltaX * cos - deltaY * sin + cx; y = deltaX * sin + deltaY * cos + cy; } return [x, y]; } /** * Interpolate a point on a circular arc * * @param {number} t Normalized time value to interpolate * @param {number} cx X-axis coordinate of the center of the circle * @param {number} cy Y-axis coordinate of the center of the circle * @param {number} radius Radius of the circle * @param {number} [startAngle] Start angle of the arc (in radians) * @param {number} [endAngle] End angle of the arc (in radians) * @param {boolean} [counterclockwise=false] Flag indicating the direction of the arc * @returns {Point} Interpolated coordinates on the arc */ export function arc(t, cx, cy, radius, startAngle, endAngle, counterclockwise) { return ellipse(t, cx, cy, radius, radius, 0, startAngle, endAngle, counterclockwise); } /** * Check if two points are coincident * * @param {number} x1 X-axis coordinate of the first point * @param {number} y1 Y-axis coordinate of the first point * @param {number} x2 X-axis coordinate of the second point * @param {number} y2 Y-axis coordinate of the second point * @returns {boolean} True if the two are coincident, false otherwise */ export function isCoincident(x1, y1, x2, y2) { return distance(x1, y1, x2, y2) <= EPSILON; } /** * Check if three points are collinear (aligned on the same line) * * @param {number} x1 X-axis coordinate of the first point * @param {number} y1 Y-axis coordinate of the first point * @param {number} x2 X-axis coordinate of the second point * @param {number} y2 Y-axis coordinate of the second point * @param {number} x3 X-axis coordinate of the third point * @param {number} y3 Y-axis coordinate of the third point * @returns {boolean} True if the three points are collinear, false otherwise */ export function isCollinear(x1, y1, x2, y2, x3, y3) { return Math.abs((x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2)) <= EPSILON; } /** * Make a target fit a container * * @param {object} target Dimensions of the target * @param {object} container Dimensions of the container * @param {'contain'|'cover'} mode Can be 'contain' | 'cover' * @returns {object} */ function fit(target, container, mode) { const ratioWidth = container.width / target.width; const ratioHeight = container.height / target.height; let scale; if (mode === 'contain') { scale = ratioWidth < ratioHeight ? ratioWidth : ratioHeight; } else { scale = ratioWidth > ratioHeight ? ratioWidth : ratioHeight; } return { left: (container.width - target.width * scale) >> 1, top: (container.height - target.height * scale) >> 1, width: target.width * scale, height: target.height * scale, scale }; } /** * Make a target fit a container (cover mode) * * @param {FitInput} target Dimensions of the target * @param {FitInput} container Dimensions of the container * @returns {FitOutput} */ export function cover(target, container) { return fit(target, container, 'cover'); } /** * Make a target fit a container (contain mode) * * @param {FitInput} target Dimensions of the target * @param {FitInput} container Dimensions of the container * @returns {FitOutput} */ export function contain(target, container) { return fit(target, container, 'contain'); }