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( array:T[], currentIndex:number, direction:number ) { let nextIndex = currentIndex + direction; nextIndex = MathX.repeat( nextIndex, array.length ); return nextIndex; } static nextIndex( array:T[], currentIndex:number ) { return MathX.advanceIndex( array, currentIndex, 1 ); } static previousIndex( array:T[], currentIndex:number ) { return MathX.advanceIndex( array, currentIndex, -1 ); } }