/maath

🪶 Math helpers for the rest of us

Primary LanguageTypeScript

Discord Shield


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: 🪶

🟡 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

🪶 inTorus(buffer, { innerRadius, outerRadius })

TODO

Triangle

import * as triangle from "maath/triangle";

TBD

Inspiration

The kitchen-sink nature of the library was inspired by other projects that manage to bring together an immense amount of knowledge from different domains that would otherwise be fragmented in many places or even lost:

  • drei 🌭 useful helpers for react-three-fiber
  • lygia a granular and multi-language shader library designed for performance and flexibility

🟡 Roadmap

  • Make the random generator seedable for every function
  • Figure out a good API for vectors
  • Figure out a good API for functions that might work on both buffers and arrays of vectors
  • Fix type errors that might come from using different vector libs
  • Keep adding tests
  • Figure out if we can get rid of the Three.js dependency. While useful, it feels superfluous