packages/math/src/angle.ts
import type { FnN, FnN2, FnN3 } from "@thi.ng/api";
import { DEG2RAD, HALF_PI, INV_HALF_PI, PI, RAD2DEG, TAU } from "./api.js";
/**
* Returns vector of `[sin(theta)*n, cos(theta)*n]`.
*
* @param theta -
* @param n -
*/
export const sincos = (theta: number, n = 1): [number, number] => [
Math.sin(theta) * n,
Math.cos(theta) * n,
];
/**
* Returns vector of `[cos(theta)*n, sin(theta)*n]`.
*
* @param theta -
* @param n -
*/
export const cossin = (theta: number, n = 1): [number, number] => [
Math.cos(theta) * n,
Math.sin(theta) * n,
];
/**
* Projects `theta` into [0 .. 2π] interval.
*
* @param theta -
*/
export const absTheta: FnN = (theta) => (
(theta %= TAU), theta < 0 ? TAU + theta : theta
);
export const absInnerAngle: FnN = (theta) => (
(theta = Math.abs(theta)), theta > PI ? TAU - theta : theta
);
/**
* Returns smallest absolute angle difference between `a` and `b`.
* Result will be in [0 .. π] interval.
*
* @param a -
* @param b -
*/
export const angleDist: FnN2 = (a, b) =>
absInnerAngle(absTheta((b % TAU) - (a % TAU)));
/**
* Like `Math.atan2`, but always returns angle in [0 .. TAU) interval.
*
* @param y -
* @param x -
*/
export const atan2Abs: FnN2 = (y, x) => absTheta(Math.atan2(y, x));
/**
* Returns quadrant ID (0-3) of given angle (in radians).
*
* @param theta -
*/
export const quadrant: FnN = (theta) => (absTheta(theta) * INV_HALF_PI) | 0;
/**
* Converts angle to degrees.
*
* @param theta - angle in radians
*/
export const deg: FnN = (theta) => theta * RAD2DEG;
/**
* Converts angle to radians.
*
* @param theta - angle in degrees
*/
export const rad: FnN = (theta) => theta * DEG2RAD;
/**
* Cosecant. Approaches `±Infinity` for `theta` near multiples of π.
*
* @param theta - angle in radians
*/
export const csc: FnN = (theta) => 1 / Math.sin(theta);
/**
* Secant. Approaches `±Infinity` for `theta` near π/2 ± nπ
*
* @param theta - angle in radians
*/
export const sec: FnN = (theta) => 1 / Math.cos(theta);
/**
* Cotangent. Approaches `±Infinity` for `theta` near multiples of π.
*
* @param theta - angle in radians
*/
export const cot: FnN = (theta) => 1 / Math.tan(theta);
/**
* Law of Cosines. Takes length of two sides of a triangle and the inner
* angle (in radians) between them. Returns length of third side.
*
* @param a -
* @param b -
* @param gamma -
*/
export const loc: FnN3 = (a, b, gamma) =>
Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(gamma));
/**
* Approximates cos(xπ) for x in [-1,1]
*
* @param x -
*/
export const normCos: FnN = (x) => {
const x2 = x * x;
return 1.0 + x2 * (-4 + 2 * x2);
};
const __fastCos: FnN = (x) => {
const x2 = x * x;
return 0.99940307 + x2 * (-0.49558072 + 0.03679168 * x2);
};
/**
* Fast cosine approximation using {@link normCos} (polynomial). Max. error
* ~0.00059693
*
* In [0 .. 2π] interval, approx. 18-20% faster than `Math.cos` on V8.
*
* @param theta - in radians
*/
export const fastCos: FnN = (theta) => {
theta %= TAU;
theta < 0 && (theta = -theta);
switch ((theta * INV_HALF_PI) | 0) {
case 0:
return __fastCos(theta);
case 1:
return -__fastCos(PI - theta);
case 2:
return -__fastCos(theta - PI);
default:
return __fastCos(TAU - theta);
}
};
/**
* {@link fastCos}
*
* @param theta - in radians
*/
export const fastSin: FnN = (theta) => fastCos(HALF_PI - theta);
/**
* Converts angle from DMS to decimal.
*
* @remarks
* See {@link toDMS} for reverse op.
*
* Reference:
* https://en.wikipedia.org/wiki/Decimal_degrees
*
* @param deg
* @param min
* @param sec
*/
export const fromDMS = (deg: number, min: number, sec: number) =>
deg + min / 60 + sec / 3600;
/**
* Converts decimal angle to DMS. Returns result as 3-tuple with the first item
* (degrees) having the same sign as the input.
*
* @remarks
* See {@link fromDMS} for reverse op.
*
* Reference: https://en.wikipedia.org/wiki/Decimal_degrees
*
* @param theta
*/
export const toDMS = (theta: number) => {
const sign = Math.sign(theta);
theta = Math.abs(theta);
const deg = Math.trunc(theta);
theta = (theta - deg) * 60;
const min = Math.trunc(theta);
return [deg * sign, min, (theta - min) * 60];
};