/**
* Randomizer class that's seedable with a random integer (mulberry32 by Tommy Ettinger, under public domain).
* @alias HB.Math.SeededRandom
* @memberof HB
*/
class SeededRandom {
/**
* Create a new randomizer with a seed to extract values from.
* @param {number} seed - Integer to use as seed, defaults to current time in milliseconds.
* @param {boolean} integer - Whether to return integers or floats.
*/
constructor(seed = new Date().getTime(), integer = false) {
this.t = seed + 0x6D2B79F;
this.integer = integer;
}
/**
* Get a random value.
* @param {number} low=0 - The lowest value to return (inclusive).
* @param {number} high=1 - The highest value to return (exclusive), if 'integer' is true, this will default to the unsigned 32-bit integer max (4294967296).
* @param {boolean} integer=this.integer - Whether to return integers or floats, defaults to the value set in the constructor.
* @returns {number}
*/
value(low, high, integer = this.integer) {
this.t = Math.imul(this.t ^ this.t >>> 15, this.t | 1);
this.t ^= this.t + Math.imul(this.t ^ this.t >>> 7, this.t | 61);
let res = ((this.t ^ this.t >>> 14) >>> 0);
if (integer === false) {
res /= 4294967296;
if (high !== undefined) {
return res * (high - low) + low;
} else if (low !== undefined) {
return res * low;
}
} else {
if (high !== undefined) {
return Math.floor(res / 4294967296 * (high - low) + low);
} else if (low !== undefined) {
return Math.floor(res / 4294967296 * low);
}
}
return res;
}
}
/**
* Simple Noise class, (can't remember where I got it from, however, it seems really similar to [this]{@link https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-1D-noise} and [this]{@link https://www.michaelbromley.co.uk/blog/simple-1d-noise-in-javascript/}).
* @alias HB.Math.Noise
* @memberof HB
*/
class Noise {
/**
* Create a new Noise class.
* @param {number} amp - The amplitude of the noise.
* @param {number} scl - The scale of the noise.
* @param {Function} randFunc=Math.random - The random function used for generating the noise. For seeded noise, where srand is an {@link HB.Math.SeededRandom} instance, use '() => { srand.value(); }'.
*/
constructor(amp = 1, scl = 0.05, randFunc = Math.random) {
this.vertices = 256, this.amp = amp, this.scl = scl, this.r = [];
for (let i = 0; i < this.vertices; i++) this.r.push(randFunc());
}
/**
* Get the noise value at a specific value.
* @param {number} x - The value.
* @returns {number}
*/
value(x) {
const sclX = x * this.scl, floorX = Math.floor(sclX), t = sclX - floorX;
const xMin = floorX & this.vertices - 1, xMax = (xMin + 1) & this.vertices - 1;
return HBMath.lerp(this.r[xMin], this.r[xMax], t * t * (3 - 2 * t)) * this.amp;
}
}
/**
* Method to assign {@link HB.SeededRandom} and {@link HB.Noise} to {@link HB.Math}.
* @memberof HB
*/
function initMathObjects() {
HBMath.SeededRandom = SeededRandom;
HBMath.Noise = Noise;
}
/**
* The class that encompasses some useful static mathematical methods.
* @alias HB.Math
* @memberof HB
*/
class HBMath {
/**
* Method to convert cartesian degrees to radians.
* @param {number} degrees - The amount of degrees to convert.
* @returns {number}
*/
static radians(degrees) {
return degrees * (Math.PI / 180);
}
/**
* Method to convert radians to cartesian degrees.
* @param {number} degrees - The amount of radians to convert.
* @returns {number}
*/
static degrees(radians) { // convert radians to degrees
return radians * (180 / Math.PI);
}
/**
* Method to get the distance between two 2D points.
* @param {number} x1 - X-coordinate of the first point.
* @param {number} y1 - Y-coordinate of the first point.
* @param {number} x2 - X-coordinate of the second point.
* @param {number} y2 - Y-coordinate of the second point.
* @returns {number}
*/
static dist(x1, y1, x2, y2) { // gets distance between 2 x+y pairs
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
/**
* Method to map a number to another range.
* @param {number} value - The value to map.
* @param {number} valLow - The minimum of value.
* @param {number} valHigh - The maximum of value.
* @param {number} resLow - The minimum value of the result.
* @param {number} resHigh - The maximum value of the result.
* @returns {number}
*/
static map(value, valLow, valHigh, resLow, resHigh) {
return resLow + (resHigh - resLow) * (value - valLow) / (valHigh - valLow);
}
/**
* Utility method to get a random floating point value in a range, instead of 'Math.random' which only does 0-1.
* @param {number} low=0 - The minimum resulting value (inclusive).
* @param {number} high=1 - The maximum resulting value (exclusive).
* @returns {number}
*/
static random(low, high) {
if (high !== undefined) {
return Math.random() * (high - low) + low;
} else if (low !== undefined) {
return Math.random() * low;
} else {
return Math.random();
}
}
/**
* Utility method to get a random integer in a range, instead of 'Math.random' which only does floats 0-1.
* @param {number} low=0 - The minimum resulting value (inclusive).
* @param {number} high=1 - The maximum resulting value (exclusive).
* @returns {number}
*/
static randomInt(low, high) {
return Math.floor(this.random(low, high));
}
/**
* Linear interpolation method.
* @param {number} start - Value to interpolate from.
* @param {number} end - Value to interpolate to.
* @param {number} amt - Amount to interpolate by.
* @returns {number}
*/
static lerp(start, end, amt) {
return start + amt * (end - start);
}
/**
* Method to clamp a value between a range.
* @param {number} value - Value to clamp.
* @param {number} min - Minimum.
* @param {number} max - Maximum.
* @returns {number} Original value if it is not clamped.
*/
static clamp(value, min, max) {
if (value > max) {
return max;
} else if (value < min) {
return min;
} else {
return value;
}
}
/**
* Method to wrap a value once it is too big or small.
* @param {number} value - Value to wrap.
* @param {number} min - Minimum to wrap around.
* @param {number} max - Maximum to wrap around.
* @returns {number} Original value if it is not wrapped.
*/
static wrap(value, min, max) {
if (value > max) {
return min;
} else if (value < min) {
return max;
} else {
return value;
}
}
/**
* Method to check for a collision between two axis-aligned rectangles.
* @param {glMatrix.vec2} vectorA - Position of the first rectangle.
* @param {glMatrix.vec2} sizeA - Size of the first rectangle.
* @param {glMatrix.vec2} vectorB - Position of the second rectangle.
* @param {glMatrix.vec2} sizeB - Size of the second rectangle.
* @returns {boolean} true if colliding.
*/
static rectRectCollision(vectorA, sizeA, vectorB, sizeB) {
return (
Math.abs((vectorA[0] + sizeA[0] / 2) - (vectorB[0] + sizeB[0] / 2)) * 2 < (sizeA[0] + sizeB[0])
) && (
Math.abs((vectorA[1] + sizeA[1] / 2) - (vectorB[1] + sizeB[1] / 2)) * 2 < (sizeA[1] + sizeB[1])
);
}
/**
* Method to check for a collision between an axis-aligned rectangle and a circle.
* @param {glMatrix.vec2} rectPos - Position of the rectangle.
* @param {glMatrix.vec2} rectSize - Size of the rectangle.
* @param {glMatrix.vec2} circleCenter - Position of the center of the circle.
* @param {number} circleRadius - Radius of the circle.
* @returns {boolean} true if colliding.
*/
static rectCircleCollision(rectPos, rectSize, circleCenter, circleRadius) {
const dx = circleCenter[0] - Math.max(rectPos[0], Math.min(circleCenter[0], rectPos[0] + rectSize[0]));
const dy = circleCenter[1] - Math.max(rectPos[1], Math.min(circleCenter[1], rectPos[1] + rectSize[1]));
return (dx * dx + dy * dy) < circleRadius * circleRadius;
}
}
export {
initMathObjects,
HBMath as Math,
};