284 lines
5.8 KiB
TypeScript
284 lines
5.8 KiB
TypeScript
![]() |
export class MathX
|
||
|
{
|
||
|
static stringify( value:number, fractionDigits:number = 3, intDigits:number = 0, cutZeroFraction:boolean = true )
|
||
|
{
|
||
|
let fixed = value.toFixed( fractionDigits );
|
||
|
|
||
|
let regex = /(\+|\-)?(\d*)\.(\d*)/;
|
||
|
let parsed = regex.exec( fixed );
|
||
|
|
||
|
let sign = parsed[ 1 ] || "";
|
||
|
let ints = parsed[ 2 ];
|
||
|
let fraction = parsed[ 3 ];
|
||
|
|
||
|
while ( intDigits > ints.length )
|
||
|
{
|
||
|
ints = "0" + ints;
|
||
|
}
|
||
|
|
||
|
if ( cutZeroFraction )
|
||
|
{
|
||
|
fraction = fraction.replace( /0+$/, "" );
|
||
|
}
|
||
|
|
||
|
if ( fraction.length === 0 )
|
||
|
{
|
||
|
return sign + ints;
|
||
|
}
|
||
|
|
||
|
return sign + ints + "." + fraction;
|
||
|
|
||
|
}
|
||
|
|
||
|
static log( value:number, base:number = Math.E )
|
||
|
{
|
||
|
return Math.log( value )/ Math.log( base );
|
||
|
}
|
||
|
|
||
|
static normalize( value:number, min:number, max:number )
|
||
|
{
|
||
|
return ( value - min ) / ( max - min );
|
||
|
}
|
||
|
|
||
|
static map( value:number, inputMin:number, inputMax:number, outputMin:number, outputMax:number )
|
||
|
{
|
||
|
let normalized = MathX.normalize( value, inputMin, inputMax );
|
||
|
return MathX.lerp( outputMin, outputMax, normalized );
|
||
|
}
|
||
|
|
||
|
static mapClamped( value:number, inputMin:number, inputMax:number, outputMin:number, outputMax:number )
|
||
|
{
|
||
|
let normalized = MathX.normalize( value, inputMin, inputMax );
|
||
|
normalized = MathX.clamp01( normalized );
|
||
|
return MathX.lerp( outputMin, outputMax, normalized );
|
||
|
}
|
||
|
|
||
|
static maxAbs( ...values:number[] )
|
||
|
{
|
||
|
let maxValue = 0;
|
||
|
let index = 0;
|
||
|
|
||
|
for ( let i = 0; i < values.length; i++ )
|
||
|
{
|
||
|
let v = values[ i ];
|
||
|
let abs = Math.abs( v );
|
||
|
|
||
|
if ( Math.abs( v ) > maxValue )
|
||
|
{
|
||
|
maxValue = abs;
|
||
|
index = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return values[ index ];
|
||
|
}
|
||
|
|
||
|
|
||
|
static median( ...values:number[] )
|
||
|
{
|
||
|
let sorted = [].concat( values ).sort( ( a,b ) => a - b );
|
||
|
|
||
|
console.log( sorted );
|
||
|
return sorted[ Math.round( sorted.length/2 ) ];
|
||
|
}
|
||
|
|
||
|
static average( ...values:number[] )
|
||
|
{
|
||
|
let value = 0;
|
||
|
|
||
|
values.forEach( v => value += v )
|
||
|
|
||
|
return value / values.length;
|
||
|
}
|
||
|
|
||
|
static base( exponent:number, power:number )
|
||
|
{
|
||
|
return Math.pow( power, 1 / exponent );
|
||
|
}
|
||
|
|
||
|
static exponent( base:number, power:number )
|
||
|
{
|
||
|
return Math.log( power ) / Math.log( base );
|
||
|
}
|
||
|
|
||
|
|
||
|
static format( value:number, numZeros:number = 2 ):string
|
||
|
{
|
||
|
if ( numZeros <= 0 )
|
||
|
{
|
||
|
return Math.round( value ) + "";
|
||
|
}
|
||
|
|
||
|
numZeros = Math.round( numZeros );
|
||
|
|
||
|
var sign = value < 0 ? "-" : "";
|
||
|
value = Math.abs( value );
|
||
|
|
||
|
var roundedBiggerValue = Math.round( value * Math.pow( 10, numZeros ) );
|
||
|
var stringValue = roundedBiggerValue + "";
|
||
|
var minimumLength = numZeros + 1;
|
||
|
|
||
|
while ( stringValue.length < minimumLength )
|
||
|
{
|
||
|
stringValue = "0" + stringValue;
|
||
|
}
|
||
|
|
||
|
|
||
|
var split = stringValue.length - numZeros;
|
||
|
|
||
|
return sign + stringValue.substring( 0, split ) + "." + stringValue.substring( split );
|
||
|
}
|
||
|
|
||
|
static lerpAngle( a:number, b:number, amount:number )
|
||
|
{
|
||
|
let difference = MathX.angleDifference( a, b );
|
||
|
|
||
|
|
||
|
let result = MathX.repeat( a + difference * amount, 360 );
|
||
|
console.log( "lerping angle", a, b, "diff:", difference, "amount:", amount, ">>", result )
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static modulo( a:number, b:number )
|
||
|
{
|
||
|
return a - Math.floor( a / b ) * b;
|
||
|
}
|
||
|
|
||
|
static angleDifference( a:number, b:number )
|
||
|
{
|
||
|
a = MathX.repeat( a, 360 );
|
||
|
b = MathX.repeat( b, 360 );
|
||
|
|
||
|
let difference = b - a;
|
||
|
|
||
|
if ( difference < -180 )
|
||
|
{
|
||
|
difference = 360 + difference;
|
||
|
return difference;
|
||
|
}
|
||
|
|
||
|
if ( difference > 180 )
|
||
|
{
|
||
|
difference = difference - 360;
|
||
|
return difference;
|
||
|
}
|
||
|
|
||
|
return difference;
|
||
|
}
|
||
|
|
||
|
static align( baseWidth:number, elementWidth:number, alginmentNormalized:number )
|
||
|
{
|
||
|
return ( baseWidth - elementWidth ) * alginmentNormalized;
|
||
|
}
|
||
|
|
||
|
static clamp( value:number, min:number, max:number )
|
||
|
{
|
||
|
return value < min ? min : value > max ? max : value;
|
||
|
}
|
||
|
|
||
|
static clamp01( value:number )
|
||
|
{
|
||
|
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||
|
}
|
||
|
|
||
|
static repeat( value:number, range:number )
|
||
|
{
|
||
|
while ( value < 0 )
|
||
|
{
|
||
|
value += range;
|
||
|
}
|
||
|
|
||
|
while ( value >= range )
|
||
|
{
|
||
|
value -= range;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
static repeatPolar( value:number, max:number )
|
||
|
{
|
||
|
while ( value > max )
|
||
|
{
|
||
|
value -= max * 2;
|
||
|
}
|
||
|
|
||
|
while ( value < -max )
|
||
|
{
|
||
|
value += max * 2;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
|
||
|
}
|
||
|
|
||
|
static lerp( a:number, b:number, t:number )
|
||
|
{
|
||
|
t = MathX.clamp01( t );
|
||
|
return a + t * ( b - a );
|
||
|
}
|
||
|
|
||
|
static lerpUnclamped( a:number, b:number, t:number )
|
||
|
{
|
||
|
return a + t * ( b - a );
|
||
|
}
|
||
|
|
||
|
static triangle( normalized:number )
|
||
|
{
|
||
|
normalized = normalized * 2;
|
||
|
let invertedNormalized = 2 - normalized;
|
||
|
|
||
|
return MathX.clamp01( Math.min( normalized, invertedNormalized ) );
|
||
|
}
|
||
|
|
||
|
static quantizeFloored( value:number, quantization:number )
|
||
|
{
|
||
|
return Math.floor( value / quantization ) * quantization;
|
||
|
}
|
||
|
|
||
|
static quantizeCeiled( value:number, quantization:number )
|
||
|
{
|
||
|
return Math.ceil( value / quantization ) * quantization;
|
||
|
}
|
||
|
|
||
|
static quantizeRounded( value:number, quantization:number )
|
||
|
{
|
||
|
return Math.round( value / quantization ) * quantization;
|
||
|
}
|
||
|
|
||
|
static inRange( value:number, lower:number, higher:number )
|
||
|
{
|
||
|
if ( value < lower )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( value > higher )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static advanceIndex<T>( array:T[], currentIndex:number, direction:number )
|
||
|
{
|
||
|
let nextIndex = currentIndex + direction;
|
||
|
nextIndex = MathX.repeat( nextIndex, array.length );
|
||
|
|
||
|
return nextIndex;
|
||
|
}
|
||
|
|
||
|
static nextIndex<T>( array:T[], currentIndex:number )
|
||
|
{
|
||
|
return MathX.advanceIndex( array, currentIndex, 1 );
|
||
|
}
|
||
|
|
||
|
static previousIndex<T>( array:T[], currentIndex:number )
|
||
|
{
|
||
|
return MathX.advanceIndex( array, currentIndex, -1 );
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|