A library for doin’ maf innit
Maf.js implements many common useful functions that are not supported in the JavaScript Math library. Instead of adding methods to Math.prototype and risking potential conflicts with future implementations, let’s create a Math library with those methods.
There’s a mix of GLSL, HLSL, Unity and other libraries and languages.
Disclaimer: this library is from 2015. Math still doesn’t have most of these, so it’s as useful as it was. If you use three.js, check MathUtils first (it’s had that name since r113, 2020): it has clamp, lerp, inverseLerp, mapLinear, smoothstep, degToRad, isPowerOfTwo and others
The library has been reviewed and has grown. What was fixed from version 1:
mix() and lerp() don’t clamp, like the mix of GLSL and the lerp of three.js: outside of 0 and 1 they keep going. They stopped at the ends, and so did mixUnclamped(), which was a copy of them. This is the change most likely to affect your code: use mixClamped() or lerpClamped() where you counted on itlerpAngle() doesn’t clamp eitherdeltaAngle() returns the shortest way from one angle to the other with its sign, from -180 to 180, like the one of Unity. It returned its size onlylerpAngle() takes the shortest way in both directions. It went the long way round when the shortest one was backwards. It’s also exported now, only lerpAngleDeg() and lerpAngleRad() weresmoothMax() uses k, it gave the wrong value for any k other than 1. And both smoothMin() and smoothMax() work with big numbers, they returned Infinityhash() and noise2d() are a real hash. hash() repeated itself every 2π and was the same for a number and its negative, and noise2d() had the same pattern in every row. Their values are different from the ones of version 1cartesianToLatLon() is the opposite of latLonToCartesian(): degrees, with y up. It returned radians of a different sphere. latLonToCartesian() doesn’t limit the latitude to 85 degrees anymoreisPowerOfTwo() is false for 0, for negative numbers and for the ones that are not wholeclosestPowerOfTwo() returns the closest one. It could return the next one when the previous was closer, 8 for 5.9map(), inverseMix(), norm() and smoothStep() return a number when the range they get is empty, not Infinity or NaNAnd what is new:
damp() and dampAngle(), that are the same at any frame rate, and moveTowards() and moveTowardsAngle()seededRandom(), and randomInt(), randomSign(), randomItem(), randomGaussian(), randomOnSphere() and shuffle()valueNoise(), perlinNoise() and fbm(), in one, two or three dimensionsmixClamped(), lerpClamped(), inverseMixClamped(), inverseLerpClamped() and mapClamped()smootherStep(), wrap(), repeat(), pingPong(), snap(), roundTo(), saturate()bias(), gain() and sinc() to shape valuessRGBToLinear() and linearToSRGB(), the exact onessmoothstep() with the name it has in GLSL, and almost() takes how close is close enoughOne thing is as it was, and worth knowing: the value goes last in the functions that come from GLSL, like step() and map(), and first in the others.
Use npm to install it
npm i spite-maf
and import what you need, or all of it
import { clamp, lerp, smoothStep } from 'spite-maf';
import * as Maf from 'spite-maf';
Maf.clamp( 1.5, 0, 1 ); // 1
It’s a single ES module with no dependencies, Maf.js, so it also works without npm or a bundler. It has types for TypeScript, and npm test runs its tests.
Clamping and steps
clamp( v, minVal, maxVal ): the value, not below one nor above the otherclamp01( v ), saturate( v ): the same, between 0 and 1step( edge, v ): 0 below the edge, 1 from it onsmoothStep( edge0, edge1, v ), smoothstep( edge0, edge1, v ): from 0 to 1 between the two edges, starting and ending slowlysmootherStep( edge0, edge1, v ), smootherstep( edge0, edge1, v ): the same, starting and ending even slowerfract( v ), frac( v ): what is after the pointmod( a, n ), repeat( a, n ): the remainder, that is never negativewrap( v, minVal, maxVal ): the value brought into a range that repeats, like an angle into -180 to 180pingPong( v, length ): goes up to length and back down, over and oversnap( v, step, offset ): the closest multiple of steproundTo( v, decimals ): rounded to that many decimalsMixing and mapping
The plain ones do the maths and nothing else: outside of their range they keep going. The ones that say Clamped stop at its ends.
mix( x, y, a ), lerp( x, y, a ): from x to y as a goes from 0 to 1. mixUnclamped() and lerpUnclamped() are other names for themmixClamped( x, y, a ), lerpClamped( x, y, a ): the same, that stops at x and at yinverseMix( a, b, v ), inverseLerp( a, b, v ): where v is between a and b, 0 at a and 1 at binverseMixClamped( a, b, v ), inverseLerpClamped( a, b, v ): the same, from 0 to 1 and no furthernorm( v, minVal, maxVal ): inverseMix() with the value firstmap( from1, to1, from2, to2, v ), scale( from1, to1, from2, to2, v ): a value of one range, in anothermapClamped( from1, to1, from2, to2, v ): the same, that doesn’t leave the second rangesmoothMin( a, b, k ), smoothMax( a, b, k ): the smallest or the biggest of two values, without a corner where they cross. A bigger k is closer to the real onegammaToLinearSpace( v ), linearToGammaSpace( v ): a color value from gamma space to linear and back, with a gamma of 2.2sRGBToLinear( v ), linearToSRGB( v ): the same with the exact curve of sRGBMoving towards a target
damp( current, target, lambda, dt ): closer to the target on every call, fast at first and slower as it gets there. dt is the time since the last call, in seconds, and the result is the same whatever the frame rate is. lambda is how fast: with 1 it covers 63% of the way in a secondmoveTowards( current, target, maxDelta ): towards the target by maxDelta at most, without going past it
// on every frame, with the seconds since the last one
position = damp( position, target, 4, dt );
Shaping, from the functions of Inigo Quilez and others
almostIdentity( x, m, n ): x, but below m it bends to end at n instead of at zeroimpulse( k, x ): grows fast to 1 and falls slowly. The peak is at 1 / kcubicPulse( c, w, x ): a bump of width w around cexpStep( x, k, n ): a step that falls from 1, sharper with a bigger nparabola( x, k ): 0 at both ends and 1 in the middle, sharper with a bigger kpowerCurve( x, a, b ): 0 at both ends, with the top where a and b put itsinc( x, k ): a wave that fades, with its top at 1 / kbias( x, b ): pushes the values of 0 to 1 towards 0 with b below .5, and towards 1 abovegain( x, g ): pushes them towards the middle with g below .5, and away from it aboveAngles, in degrees unless they say Rad
deg2Rad( degrees ), toRadians( degrees )rad2Deg( radians ), toDegrees( radians )deltaAngle( a, b ), deltaAngleDeg( a, b ), deltaAngleRad( a, b ): the shortest way from a to b, with its signlerpAngle( a, b, t ), lerpAngleDeg( a, b, t ), lerpAngleRad( a, b, t ): from a to b the shortest way. Like mix(), it keeps going when t is outside of 0 and 1dampAngle( current, target, lambda, dt ), dampAngleRad( ... ): damp() for angles, the shortest waymoveTowardsAngle( current, target, maxDelta ), moveTowardsAngleRad( ... ): moveTowards() for anglesRandom
seededRandom( seed ): returns a function like Math.random, that gives the same numbers every time for the same seed. The seed is a number or a stringrandomInRange( min, max )randomInt( min, max ): a whole number, that can be any of the tworandomSign(): 1 or -1randomItem( array ): one of themrandomGaussian( mean, deviation ): around the mean, closer to it more oftenrandomOnSphere(): the { x, y, z } of a point of the sphere of radius 1, all of them as likelyshuffle( array ): changes the order of the array, and returns itAll of them take the function that gives the numbers as their last parameter, Math.random by default:
const random = seededRandom( 'my sketch' );
randomInRange( 0, 10, random );
shuffle( cards, random );
Noise
hash( ...values ): a number from 0 to 1 that looks random, and is the same for the same numbers. It takes as many as you give ithashInt( ...values ): the same, as a whole number of 32 bitsnoise2d( x, y ): hash( x, y )valueNoise( x, y, z ): noise that changes smoothly, from 0 to 1. y and z are optional, in all of themperlinNoise( x, y, z ): the noise of Ken Perlin, from -1 to 1. It’s 0 at the whole numbersfbm( x, y, z, options ): several noises added, each one smaller and finer than the one before, from -1 to 1. The options are octaves (5), lacunarity (2), gain (0.5) and noise, the function that is added (perlinNoise)The rest
isPowerOfTwo( v )closestPowerOfTwo( v )nextPowerOfTwo( v ): the power of two that is v or the first after itlatLonToCartesian( lat, lon ): latitude and longitude in degrees, to the { x, y, z } of a point of a sphere of radius 1, with y upcartesianToLatLon( x, y, z ): the opposite, to { lat, lon } in degreesalmost( a, b, epsilon ): if two values are closer than epsilon, 0.0001 by defaultPI, TAU, VERSIONdamp() and dampAngle() do that job without having to keep a velocityMIT licensed
Copyright (C) 2015 Jaume Sanchez Elias https://twitter.com/thespite
https://www.clicktorelease.com