math: a data-oriented math engine for the web
đź§® The playful web's math engine
At a glance
- What is it?
- The pmndrs math package is a TypeScript library where vectors, quaternions, matrices and noise functions write into caller-owned arrays instead of allocating, grouped behind tree-shakeable subpath entrypoints for shapes, geometry, time, random, noise, color and inverse kinematics. It suits a browser or WebGL project that calls math in a hot loop, and the allocation-free convention is the whole contract.
- Who is it for?
- Adopt pmndrs math if you are writing TypeScript for the browser and want math functions that write into your own buffers without allocating, especially in a per-frame loop. Skip it if you want a batteries-included linear algebra library, since this one is deliberately narrow and you assemble what you need from the subpath exports.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The allocation rule, which is the entire API
The quick start makes the convention explicit before anything else:
import { type Vec3, vec3 } from 'math';
// math types are plain arrays — the constructors just return literals:
const a: Vec3 = [1, 2, 3]; // a plain-array literal
const b = vec3.fromValues(1, 2, 3); // the same as `a`
const out = vec3.create(); // returns [0, 0, 0]
// functions write into their first argument, so nothing is allocated:
vec3.add(out, a, b); // out = [2, 4, 6]
vec3.normalize(out, out); // aliasing an argument is fineThree things follow from that. The types are plain arrays rather than class instances, so a `Vec3` is literally `[1, 2, 3]` and interoperates with typed arrays and buffers without a conversion. The functions do not return a new value, they write into the first argument, so a call site that ignored the return value would be a silent bug, which is why the naming convention puts the output first. And aliasing is explicitly fine, so `vec3.normalize(out, out)` is valid, which is the pattern you want when normalising in place. The package describes itself as data-oriented, with data-in, data-out functions over caller-owned data, without owning the data lifecycle.
The four promises, and what each one commits to
The README lists four properties and each is a design constraint rather than a slogan. High performance means allocation-free, monomorphic, benchmarked, and the monomorphic part is the one that is easy to miss, since it means the functions are written so that the shapes and types they see do not vary at a call site, which is what lets a JavaScript engine keep them in one optimised path. Tiny means mean and lean and tree-shakable, with only paying for what you use, and that promise is delivered by the module structure rather than by minification. Portable means it interops with WebGL, WebGPU, Wasm and your favourite renderer, which follows from the plain-array representation, since a `Vec3` is something a GPU buffer can hold. And data-oriented is the description of the shape of the API itself, which is the property that makes the other three possible rather than an additional claim.
The subpath exports, and why they matter
The library is grouped by domain behind subpath entrypoints, and the manifest lists them as separate export conditions for shapes, geometry, time, random, noise, color and ik alongside the main entry. The import style in the quick start shows how you are meant to use them:
import { mat4, quat, vec3 } from 'math'; // core: vectors, quats, matrices
import { simplex3d } from 'math/noise'; // perlin / simplex noise
import { mulberry32 } from 'math/random'; // seeded rng
import { easing, spring } from 'math/time'; // easings & springs
import { fabrik2, fabrik3 } from 'math/ik'; // inverse kinematics
// also: math/color, math/geometry, math/shapes — import only what you useBecause each subpath is a separate entry in the exports map with its own types file, a bundler can drop the whole domain if you never import it. In a project that needs one easing function and a quaternion, that is the difference between shipping a noise generator and a spring solver for nothing. The `files` array in the manifest lists `dist`, `skills`, `LICENSE`, `README.md` and `API.md`, so the skill and the API reference ship with the package, which is unusual and useful, since the documentation is then available offline in node_modules.
What the core entrypoint covers
The main `math` entry is the vector and matrix layer, and the API reference lists the members. Vectors come as vec2, vec3 and vec4. Rotations come as euler for Euler angles, quat for quaternions and quat2 for the dual quaternion used in skinning. Matrices come as mat2, mat2d, mat3 and mat4. There are also spherical and polar coordinate helpers. Then there is the scalar utility set, which is the part that separates this from a pure linear algebra package: EPSILON and round and equals for comparison, fade, lerp and the lagrange interpolation, clamp, repeat, wrap and remap and remapClamp for range handling, and the angle conversions with both the DEGREES_TO_RADIANS and RADIANS_TO_DEGREES constants and the degreesToRadians and radiansToDegrees functions, plus wrapAngle and deltaAngle for the shortest-path difference between two angles. The presence of both the constants and the functions suggests the constants are the ones you reach for in hot code.
Shapes, geometry and spatial queries
The `math/shapes` entrypoint holds the shape primitives and the spatial queries that go with them. There are box2, box3, obb3 for an oriented bounding box, plane3, sphere, circle, segment2, polygon2, triangle2 and triangle3. And there is raycast3, which is the ray intersection primitive every 3D engine needs, and frustum, which is the view volume used for culling. The `math/geometry` entrypoint holds the algorithms that operate on those shapes: circumcircle for the circle through three points, decomposePolygon2Quick and decomposePolygon2Quality for splitting a polygon into convex pieces in two different ways, triangulatePolygon2, and quickhull2 and quickhull3 for convex hulls. The existence of both a quick and a quality variant of the polygon decomposition is a nice detail, because it tells you the library expects you to choose between speed and robustness depending on whether the input is well-behaved.
Time, random, noise, color and inverse kinematics
The remaining domains each have a clear job. `math/time` holds easing and spring animation, with easing, spring, spring2, spring3 and spring4, so the spring family is available in several dimensions. `math/random` holds seeded random number generators, with isaac32, isaac64, mulberry32 and a general random function, and seeded generators are what you need when a simulation has to be reproducible. `math/noise` holds perlin, simplex and worley noise with fractal helpers, with perlin2d and perlin3d, simplex2d, simplex3d and simplex4d, worley2d and worley3d, and fbm, ridged and billow, plus domainWarp2, domainWarp3, curl2 and curl3. `math/color` holds colour and colorspace utilities with color, colorspace and hsl. And `math/ik` holds inverse kinematics with fabrik2 and fabrik3, the FABRIK algorithm in two and three dimensions. The example gallery shows what these are for, with a quaternion look-at, a Fibonacci sphere, an OBB3 contains-point test, convex hulls in 2D and 3D, polygon decomposition and triangulation, a dual quaternion skinning example, a flow field, and terrain generated from simplex, ridged and looping noise.
The skill, the benchmarks and the package identity
Two things about the repository are worth knowing before you adopt it. The first is the skill, which is an unusual feature for a math library. Math has a skill that teaches your agent how to get the most out of it, giving direction on how to design efficient, optimised and data-oriented JavaScript, outlining common gotchas and providing code examples that optimise memory, and you try your agent with `/math` and see how its performance compares. The install instructions show both the package and the skill:
> npm install math
> npx skills add pmndrs/math --skill mathThe second is the package identity, and this is where a real trap sits. The published releases are `[email protected]` on 2026-09-11 alongside `[email protected]` and `[email protected]`, so the library has been renamed, and the current package is at version 0.1.0 under the name `math`. The repository layout supports the idea of a rewrite, with `src/`, `benches/`, `tst/`, `examples/`, `website/`, `docs/`, `skills/`, a `rollup.config.mjs`, a `biome.json` and separate `tsconfig.build.json` and `tsconfig.json` files. The primary language is TypeScript, the licence is MIT, and the last push was on 2026-10-12.
Editorial conclusion
Adopt pmndrs math if you are writing TypeScript for the browser and want math functions that write into your own buffers without allocating, especially in a per-frame loop. Skip it if you want a batteries-included linear algebra library, since this one is deliberately narrow and you assemble what you need from the subpath exports. Before you start, note that the package is at version 0.1.0 under the `math` name while the older `maath` releases carry the 0.10.x line, so check which name your dependencies reference, and remember that the API is data-oriented, so every function writes into its first argument.
Frequently asked questions
What is the pmndrs math package?
A TypeScript math library described as the playful web's math engine, offering allocation-free, monomorphic, tree-shakable functions over vectors, quaternions, matrices, shapes, geometry, easing, springs, seeded random, noise, colour and inverse kinematics, with data-in and data-out functions over caller-owned data.
How do I use the math types?
They are plain arrays. A Vec3 is written as `[1, 2, 3]`, and `vec3.fromValues(1, 2, 3)` returns the same thing. Functions write into their first argument, so `vec3.add(out, a, b)` fills `out` rather than returning a new value, and aliasing an argument is allowed.
How do I import only the parts of the math library I need?
Use the subpath entrypoints, for example `import { simplex3d } from 'math/noise'`, `import { mulberry32 } from 'math/random'` or `import { easing, spring } from 'math/time'`. Each subpath is a separate export with its own types file, so unused domains are dropped by the bundler.
What is the relationship between math and maath?
The published releases show the current package as math at version 0.1.0, published 2026-09-11, alongside earlier maath releases at 0.10.8 and 0.10.7. The library has been renamed, so check which name your dependencies reference.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pmndrs-math)Was this useful?