extended-math.js

"use strict";

/**
 * @module extendedMath
 */
var extendedMath = { };

function isInvalidNumber(value) {
	return typeof value !== "number" || isNaN(value) || value === -Infinity || value === Infinity;
}

/**
 * A static constant number representing 1/2 of PI.
 *
 * @constant {number} HalfPI=1.57079632679489661923
 * @since 1.0.0
 * @memberOf module:extendedMath
 */
Object.defineProperty(extendedMath, "HalfPI", {
	value: 1.57079632679489661923,
	enumerable: true
});

/**
 * A static constant number representing 1/4 of PI.
 *
 * @constant {number} QuarterPI=0.78539816339744830962
 * @since 1.0.0
 * @memberOf module:extendedMath
 */
Object.defineProperty(extendedMath, "QuarterPI", {
	value: 0.78539816339744830962,
	enumerable: true
});

/**
 * A static constant number representing 2x PI.
 *
 * @constant {number} TwoPI=6.28318530717958647693
 * @since 1.0.0
 * @memberOf module:extendedMath
 */
Object.defineProperty(extendedMath, "TwoPI", {
	value: 6.28318530717958647693,
	enumerable: true
});

/**
 * Clamps a number between a minimum and maximum value.
 * +/- Infinity are not valid minimum / maximum values.
 *
 * @function clamp
 * @param {number} value - The number to clamp.
 * @param {number} min - The minimum number to clamp to.
 * @param {number} max - The maximum number to clamp to.
 * @returns {number} A value which is inclusively adjusted between the specified mimum and maximum values, or NaN if any arguments are not valid numbers.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.clamp(3.141592654, -1, 3)); // 3
 * console.log(extendedMath.clamp(-1.337, -4.2, 6.9)); // -1.337
 * console.log(extendedMath.clamp(-800, -2.8, 1)); // -2.8
 * console.log(extendedMath.clamp(1, 2, Infinity)); // NaN
 * console.log(extendedMath.clamp(1, -Infinity, 4)); // NaN
 * console.log(extendedMath.clamp(Infinity, 0, 1)); // NaN
 */
extendedMath.clamp = function clamp(value, min, max) {
	return isInvalidNumber(value) || isInvalidNumber(min) || isInvalidNumber(max) ? NaN : value < min ? min : value > max ? max : value;
};

/**
 * Calculates the positive difference between two numbers.
 * Values must be valid finite numbers.
 *
 * @function difference
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} A positive value representing the difference between two numbers, or NaN if either argument is not a valid number.
 * @since 1.0.8
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.difference(3, 9)); // 6
 * console.log(extendedMath.difference(4, -4)); // 8
 * console.log(extendedMath.difference(Infinity, 4)); // NaN
 * console.log(extendedMath.difference(NaN, 8080)); // NaN
 */
extendedMath.difference = function difference(a, b) {
	return isInvalidNumber(a) || isInvalidNumber(b) ? NaN : Math.abs(b - a);
};

/**
 * Calculates the positive distance between two numbers.
 *
 * @function distance
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} A positive value representing the distance between two numbers, or NaN if either argument is not a valid number.
 * @deprecated Renamed to difference so the unit context of the values is not assumed.
 * @see {@link module:extendedMath.difference|difference}
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.distance(3, 9)); // 6
 * console.log(extendedMath.distance(4, -4)); // 8
 * console.log(extendedMath.distance(Infinity, 4)); // NaN
 * console.log(extendedMath.distance(NaN, 8080)); // NaN
 */
extendedMath.distance = extendedMath.difference;

/**
 * Converts an angle value in radians to degrees.
 *
 * @function radiansToDegrees
 * @param {number} value - An angle value represented in radians.
 * @returns {number} A value representing the angle in degrees instead of radians, or NaN if the argument is not a valid number.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.radiansToDegrees(Math.PI * 2)); // 360
 * console.log(extendedMath.radiansToDegrees(Math.PI / 2)); // 90
 * console.log(extendedMath.radiansToDegrees(-Infinity)); // NaN
 * console.log(extendedMath.radiansToDegrees(NaN)); // NaN
 */
extendedMath.radiansToDegrees = function radiansToDegrees(value) {
	return isInvalidNumber(value) ? NaN : value * (180 / Math.PI);
};

/**
 * Converts an angle value in degrees to radians.
 *
 * @function degreesToRadians
 * @param {number} value - An angle value represented in degrees.
 * @returns {number} A value representing the angle in radians instead of degrees, or NaN if the argument is not a valid number.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.degreesToRadians(360)); // 6.283185307179586
 * console.log(extendedMath.degreesToRadians(180)); // 3.141592653589793
 * console.log(extendedMath.degreesToRadians(Infinity)); // NaN
 * console.log(extendedMath.degreesToRadians(NaN)); // NaN
 */
extendedMath.degreesToRadians = function degreesToRadians(value) {
	return isInvalidNumber(value) ? NaN : value * (Math.PI / 180);
};

/**
 * Performs a comparison on two angles represented in degrees.
 * If an angle is less than 0 or greater than 360, it is transformed so that it is within this scale before comparing.
 *
 * @function compareAnglesDegrees
 * @param {a} value - An angle value represented in degrees.
 * @param {b} value - Another angle value represented in degrees to compare against.
 * @returns {number} A value of 0 if the .angles are equal, 1 if the first angle is to the left of the second angle, or -1 if the first angle is to the right of the second angle. If any arguments are not valid numbers, then NaN is returned instead.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.compareAnglesDegrees(0, 360)); // 0
 * console.log(extendedMath.compareAnglesDegrees(-90, 90)); // 1
 * console.log(extendedMath.compareAnglesDegrees(120, 30)); // -1
 * console.log(extendedMath.compareAnglesDegrees(-Infinity, 1)); // NaN
 * console.log(extendedMath.compareAnglesDegrees(-2, NaN)); // NaN
 */
extendedMath.compareAnglesDegrees = function compareAnglesDegrees(a, b) {
	if(isInvalidNumber(a) || isInvalidNumber(b)) {
		return NaN;
	}

	if(a === b) {
		return 0;
	}

	var c = a % 360;
	var d = b % 360;

	if(c < 0) {
		c += 360;
	}

	if(d < 0) {
		d += 360;
	}

	if(c === d) {
		return 0;
	}

	return Math.cos(extendedMath.degreesToRadians(a - b) + (Math.PI / 2)) < 0 ? -1 : 1;
};

/**
 * Performs a comparison on two angles represented in radians.
 * If an angle is less than 0 or greater than 360, it is transformed so that it is within this scale before comparing.
 *
 * @function compareAnglesRadians
 * @param {a} value - An angle value represented in radians.
 * @param {b} value - Another angle value represented in radians to compare against.
 * @returns {number} A value of 0 if the .angles are equal, 1 if the first angle is to the left of the second angle, or -1 if the first angle is to the right of the second angle. If any arguments are not valid numbers, then NaN is returned instead.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.compareAnglesRadians(0, Math.PI * 2)); // 0
 * console.log(extendedMath.compareAnglesRadians(-(Math.PI / 2), Math.PI / 2)); // 1
 * console.log(extendedMath.compareAnglesRadians(2 * (Math.PI / 3), (Math.PI / 3))); // -1
 * console.log(extendedMath.compareAnglesRadians(-Infinity, 1)); // NaN
 * console.log(extendedMath.compareAnglesRadians(-2, NaN)); // NaN
 */
extendedMath.compareAnglesRadians = function compareAnglesRadians(a, b) {
	return isInvalidNumber(a) || isInvalidNumber(b) ? NaN : extendedMath.compareAnglesDegrees(extendedMath.radiansToDegrees(a), extendedMath.radiansToDegrees(b));
};

/**
 * Linerarly interpolates between two numbers relative to a multiplier value.
 *
 * @function lerp
 * @param {number} a - The low end of the interpolation scale.
 * @param {number} b - The high end of the interpolation scale.
 * @param {number} amount - The multiplier number to interpolate by.
 * @returns {number} A number interpolated relative to the low and high end numbers, or NaN if any of the arguments are not valid numbers.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.lerp(0, 1.5, 2)); // 3
 * console.log(extendedMath.lerp(-4, 8, 0.5)); // 2
 * console.log(extendedMath.lerp(0.5, 1.5, 0.75)); // 1.25
 * console.log(extendedMath.lerp(0, 1, Infinity)); // NaN
 * console.log(extendedMath.lerp(NaN, 320, 240)); // NaN
 */
extendedMath.lerp = function lerp(a, b, amount) {
	if(isInvalidNumber(a) || isInvalidNumber(b) || isInvalidNumber(amount)) {
		return NaN;
	}

	if(amount === 0) {
		return a;
	}
	else if(amount === 1) {
		return b;
	}

	return a + ((b - a) * amount);
};

/**
 * Normalizes a number based on a min and max value.
 *
 * @function normalize
 * @param {number} value - The number to normalize.
 * @param {number} min - The low end number to normalize relative to.
 * @param {number} max - The high end number to normalize relative to.
 * @returns {number} A number normalized relative to the low and high end values, or NaN if any of the arguments are not valid numbers.
 * @since 1.0.0
 * @memberOf module:extendedMath
 * @example
 * console.log(extendedMath.normalize(1, 0, 2)); // 0.5
 * console.log(extendedMath.normalize(4, 1, 5)); // 0.75
 * console.log(extendedMath.normalize(-3, -4, 0)); // 0.25
 * console.log(extendedMath.normalize(16, 0, 4)); // 4
 * console.log(extendedMath.normalize(NaN, 0, 1)); // NaN
 * console.log(extendedMath.normalize(1, -Infinity, 2)); // NaN
 */
extendedMath.normalize = function normalize(value, min, max) {
	return isInvalidNumber(value) || isInvalidNumber(min) || isInvalidNumber(max) ? NaN : (value - min) / (max - min);
};

module.exports = extendedMath;