Maath
🪶 Math helpers for the rest of us
Install / Use
/learn @pmndrs/MaathREADME
<a href="https://github.com/pmndrs/maath"><img src="https://github.com/pmndrs/maath/blob/main/hero.svg?raw=true" /></a> <br />
yarn add maath
This is a collection of useful math helpers, random generators, bits and bobs.
The library is mostly meant to be used with three.js, so if you are using it outside of a three project, make sure you check the source and - if you don't need the dep - just copy paste!
Check out the demos on Codesandbox: 🪶
| <a href="https://codesandbox.io/s/github/pmndrs/maath/tree/main/demo/src/sandboxes/points"><img src="https://codesandbox.io/api/v1/sandboxes/lex1g/screenshot.png" /></a> | <a href="https://codesandbox.io/s/github/pmndrs/maath/tree/main/demo/src/sandboxes/convex-hull"><img src="https://codesandbox.io/api/v1/sandboxes/fh8l2/screenshot.png" /></a> | <a href="https://codesandbox.io/s/github/pmndrs/maath/tree/main/demo/src/sandboxes/circumcircle"><img src="https://codesandbox.io/api/v1/sandboxes/zuff9/screenshot.png" /></a> | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
🟡 The library is still heavily WIP
But why?
Yes, there are a lot of these libraries. The more the merrier! We are all here to learn, and maintaining a dedicated library for our creative endeavors at Poimandres just made sense.
Contributing
Do you want to add something? No rules, but keep these in mind:
- try to add explainers to whatever function you add, if you saw it in a tweet link that!
- add a cool example! Make it a sandbox or whatever, just show how the function can be used creatively
- keep copy-paste simple. Try not to add too many inter-dependencies so the functions are copy-paste friendly
- loose typing. Try to add typing, but don't go crazy with generics and complexity
If you are not sure how to help, check out the 🟡 Roadmap below.
🪶 Reference
Using specific entry points
// you can import the namespaces from the main entrypoint
import { buffer, random } from "maath";
// or import each function or all of them from each namespace entrypoint
import * as buffer from "maath/buffer";
import { inSphere } from "maath/random";
Buffer
import * as buffer from "maath/buffer";
🪶 toVectorArray(buffer, stride)
Converts an [..., x, y, z, ...] typed array to a Vector[]
const myBuffer = new Float32Array(100 * 3);
const myArray = toVectorArray(myBuffer, 3);
🪶 swizzleBuffer(buffer, axes)
Swizzle the individual vectors in a vector buffer
const myBuffer = new Float32Array(100 * 3);
myBuffer.push(0, 1, 2);
swizzleBuffer(myBuffer, "xzy"); // buffer is now [0, 2, 1]
This is a way to make simple rotations.
🪶 addAxis(buffer, size, getZValue)
Adds a z axis to an [..., x, y, ...] typed array:
const my2DBuffer = new Float32Array(100 * 2);
const my3DBuffer = addAxis(my2DBuffer, 2, () => Math.random()); // zAxis will now be a random value between 0 and 1
const my4DBuffer = addAxis(my3DBuffer, 3, () => 1); // 4th value (imagine a in rgba) will be 1
🪶 lerpBuffers(bufferA, bufferB, destinationBuffer, t)
Linearly interpolate two buffers, writing on a third one.
const mySphere = inSphere(new Float32Array(100 * 3), { radius: 4 });
const myBox = inBox(new Float32Array(100 * 3), { side: 4 });
const interpolationTarget = myBox.slice(0);
lerpBuffers(mySphere, myBox, interpolationTarget, Math.sin(performance.now()));
Geometry
import * as geometry from "maath/geometry";
🪶 roundedPlaneGeometry(width = 1, height = 1, radius = 0.2, segments = 16)
const geo = new RoundedPlaneGeometry();
const mesh = new THREE.Mesh(geo, material);
🪶 applyBoxUV(bufferGeometry)
Applies box-projected UVs to a buffer geometry.
🪶 applySphereUV(bufferGeometry)
Applies spherical UVs to a buffer geometry.
🪶 applyCylindricalUV(bufferGeometry)
Applies cylindrical-projected UVs to a buffer geometry.
Easing
import * as easing from "maath/easing";
Unity-smooth-damping functions based on Game Programming Gems 4 Chapter 1.10. These are fast, refresh-rate independent, interruptible animation primitives primed to THREE.Vector2D, 3D, 4D, Euler (shortest path), Matrix4, Quaternion and Color.
export function damp(
/** The object */
current: { [key: string]: any },
/** The key to animate */
prop: string,
/** To target (or goal) value */
target: number,
/** Approximate time to reach the target. A smaller value will reach the target faster. */
smoothTime = 0.25,
/** Frame delta, for refreshrate independence */
delta = 0.01,
/** Optionally allows you to clamp the maximum speed. If smoothTime is 0.25s and looks OK
* going between two close points but not for points far apart as it'll move very rapid,
* then a maxSpeed of e.g. 1 which will clamp the speed to 1 unit per second, it may now
* take much longer than smoothTime to reach the target if it is far away. */
maxSpeed = Infinity,
/** Easing function */
easing = (t: number) => 1 / (1 + t + 0.48 * t * t + 0.235 * t * t * t),
/** End of animation precision */
eps = 0.001
);
import { damp, damp2, damp3, damp4, dampE, dampM, dampQ, dampS, dampC } from 'maath/easing'
function frameloop() {
const delta = clock.getDelta()
// Animates foo.bar to 10
damp(foo, "bar", 10, 0.25, delta)
// Animates mesh.position to 0,1,2
damp3(mesh.position, [0, 1, 2], 0.25, delta)
// Also takes vectors, shallow vectors and scalars
// damp3(mesh.position, new THREE.Vector3(0, 1, 2), 0.25, delta)
// damp3(mesh.position, { x: 0, y: 1, z: 2 }, 0.25, delta)
// damp3(mesh.scale, 2, 0.25, delta)
dampC(mesh.material.color, "green", 0.25, delta)
// Also takes colors, fake colors, numbers and arrays
// dampC(mesh.material.color, new THREE.Color("green"), 0.25, delta)
// dampC(mesh.material.color, 0xdead00, 0.25, delta)
// dampC(mesh.material.color, [1, 0, 0], 0.25, delta)
// dampC(mesh.material.color, { r: 1, g: 0, b: 0 }, 0.25, delta)
// Animates an euler with a shortest-path algorithm
dampE(mesh.rotation, [Math.PI / 2, 0, 0], 0.25, delta)
// Also takes eulers
// dampE(mesh.rotation, new THREE.Euler(Math.PI / 2, 0, 0), 0.25, delta)
// damp2 for Vector2
// damp4 for Vector4
// dampM for Matrix4
// dampQ for Quaternion
// dampS for Spherical
There are two special damping functions for angles and lookAt:
import { dampAngle, dampLookAt } from "maath/easing";
// Animates angle to targetAngle, with a shortest-path algorithm
dampAngle(angle, targetAngle, 0.25, delta);
// Animates a meshes look-up
dampLookAt(mesh, focus, 0.25, delta);
🪶 easing functions
6 easing functions are available with in, out and inOut variants:
import { sine, cubic, quint, circ, quart, expo } from "maath/easing";
sine.in(t);
sine.out(t);
sine.inOut(t);
3 specific functions:
import { rsqw, exp, linear } from "maath/easing";
// rounded-square wave
rsqw(t, delta);
// unity smooth damping
exp(t);
// linear
linear(t); // === t
Matrix
import * as matrix from "maath/matrix";
🪶 determinant2(...matrixInRowMajorOrder)
Returns the determinant of a passed 2x2 matrix:
const d = determinant2(1, 1, 2, 2);
🪶 determinant3(...matrixInRowMajorOrder)
Returns the determinant of a passed 3x3 matrix:
const d = determinant3(1, 1, 1, 2, 2, 2);
🪶 determinant4(...matrixInRowMajorOrder) // TBD
🪶 getMinor(matrix, column, row)
Returns the minor of a given matrix.
const minor = getMinor([1, 2, 1, 2, 1, 1, 3, 2, 3], 1, 1);
// minor will be the determinant of the submatrix without row 1 and colum 1
// | 1 1 |
// | 2 3 |
Misc
// Clamps a value between a range.
clamp(value: number, min: number, max: number): number
// Loops the value t, so that it is never larger than length and never smaller than 0.
repeat(t: number, length: number): number
// Calculates the shortest difference between two given angles.
deltaAngle(current: number, target: number): number
// Converts degrees to radians.
degToRad(degrees: number): number
// Converts radians to degrees.
radToDeg(radians: number): number
// adapted from https://gist.github.com/stephanbogner/a5f50548a06bec723dcb0991dcbb0856 by https://twitter.com/st_phan
fibonacciOnSphere(buffer: TypedArray, { radius = 1 }): void
// Checks if vector a is equal to vector b, with tolerance
vectorEquals(a, b, eps = Number.EPSILON): boolean
/**
* Sorts vectors in lexicographic order, works with both v2 and v3
*
* Use as:
* const sorted = arrayOfVectors.sort(lexicographicOrder)
*/
// https://en.wikipedia.org/wiki/Lexicographic_order
lexicographic(a: Vector2 | Vector3, b: Vector2 | Vector3): number
/**
* Convex Hull
*
* Returns an array of 2D Vectors representing the convex hull of a set of 2D Vectors
*/
convexHull(_points: Vector2[]): Vector2[]
// ...
remap(x: number, [low1, high1]: number[], [low2, high2]: number[])
Random
import * as random from "maath/random";
🪶 onTorus(buffer, { innerRadius, outerRadius })
[TODO](https://ma
