munsell
Version:
Library for Munsell Color System
111 lines (100 loc) • 3.46 kB
text/typescript
/**
* Provides several arithmetic operations (mainly in a circle group) which are
* called only internally.
*
* Note that no functions take `multiple laps' into consideration: i.e. the arc
* length of the interval [-2pi, 2pi] is not 4pi but 0.
* @module
*/
export type Vector2 = [number, number];
export type Vector3 = [number, number, number];
export type Matrix33 = [Vector3, Vector3, Vector3];
export const TWO_PI = Math.PI * 2;
export const mod = (dividend: number, divisor: number): number => {
const x = dividend % divisor;
if (x >= 0) {
return x;
} else {
return x + divisor;
}
};
export const clamp = (x: number, min: number, max: number): number => {
return Math.min(Math.max(x, min), max);
};
export const cartesianToPolar = (x: number, y: number, perimeter = TWO_PI): Vector2 => {
const factor = perimeter / TWO_PI;
return [Math.sqrt(x * x + y * y), mod(Math.atan2(y, x) * factor, perimeter)];
};
export const polarToCartesian = (r: number, theta: number, perimeter = TWO_PI): Vector2 => {
const factor = TWO_PI / perimeter;
const hueRad = theta * factor;
return [r * Math.cos(hueRad), r * Math.sin(hueRad)];
};
/**
* Is a counterclockwise linear interpolation from theta1 to theta2 in a
* circle group. It is guaranteed that the returned value is within the
* given interval if amount is in [0, 1].
* @param amount - should be in [0, 1]
* @param theta1
* @param theta2
* @param [perimeter]
*/
export const circularLerp = (
amount: number,
theta1: number,
theta2: number,
perimeter = TWO_PI,
): number => {
const theta1Mod = mod(theta1, perimeter);
const theta2Mod = mod(theta2, perimeter);
if (amount === 1) return theta2Mod; // special treatment to decrease computational error
const res =
theta1Mod * (1 - amount) + (theta1Mod > theta2Mod ? theta2Mod + perimeter : theta2Mod) * amount;
if (res >= perimeter) {
return Math.min(res - perimeter, theta2Mod);
} else {
return res;
}
};
/**
* Returns the 'difference' of two values in a circle group. The returned value
* Δ satisfies theta2 + Δ ≡ theta1 and -perimeter/2 <= Δ <= perimeter/2.
* @param theta1
* @param theta2
* @param [perimter]
*/
export const circularDelta = (theta1: number, theta2: number, perimeter = TWO_PI): number => {
const d = mod(theta1 - theta2, perimeter);
if (d <= perimeter / 2) {
return d;
} else {
return d - perimeter;
}
};
// We need only the following two kinds of multiplication.
export const multMatrixVector = (A: Matrix33, x: Vector3): Vector3 => {
return [
A[0][0] * x[0] + A[0][1] * x[1] + A[0][2] * x[2],
A[1][0] * x[0] + A[1][1] * x[1] + A[1][2] * x[2],
A[2][0] * x[0] + A[2][1] * x[1] + A[2][2] * x[2],
];
};
export const multMatrixMatrix = (A: Matrix33, B: Matrix33): Matrix33 => {
return [
[
A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0],
A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1],
A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2],
],
[
A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0],
A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1],
A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2],
],
[
A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0],
A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1],
A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2],
],
];
};