munum is a minimalistic numerical library for high-performance 3D math with Rust, WebAssembly, and JavaScript bindings. The math is written once in no_std Rust; the same source powers a native crate, a compact WebAssembly module, and two JavaScript surfaces β a zero-copy WebAssembly binding and a dependency-free pure-JS implementation with the same API.
[JavaScript] Install via npm:
npm install --save munum
[Rust] Install as Cargo dependency:
cargo add munum
Features:
std - enables std support. Enabled by default.libm - enables trigonometry related functions in no_std environment using libm.jsmath - enables trigonometry related functions in no_std WebAssembly environment using the JS Math binding.serde - enables serde serialize/deserialize implementations.wasm - produces the WebAssembly module and WebAssembly component (WIP).Sample usage to build a perspective camera view-projection matrix below:
import { lookAt, perspective, Mat4, Vec3 } from 'munum'; // Or load from CDN, e.g. 'https://unpkg.com/munum@latest'
using eye = new Vec3(1, 1, 1);
using target = new Vec3(0, 0, 0);
using up = new Vec3(0, 1, 0);
const view = lookAt(eye, target, up);
const aspectRatio = width / height;
const yfov = Math.PI / 4;
const znear = 1;
const zfar = 100;
using viewProj = perspective(aspectRatio, yfov, znear, zfar).mul(view);
The WebAssembly binding stores munum values directly in the WebAssembly module's linear memory and operates on them in place (zero-copy), so vectors and matrices need to be deallocated after use. Memory is managed per-MemoryManager: each value captures a manager (the module default unless one is passed) and is auto-reclaimed by that manager's embedded FinalizationRegistry, so explicit cleanup via using (which calls .free() when the value goes out of scope) or a manual .free() is recommended but not required.
To wrap existing memory without owning it, use the view factory β disposing a borrowed view never frees the underlying storage:
import { Vec3, float64View } from 'munum/js';
// Zero-copy view over a raw glTF Float64Array (pure-JS build):
const positions = new Float64Array(gltfBuffer); // e.g. accessor data, packed vec3s
const p1 = Vec3.view(float64View(positions), 3 * 3 * 8); // 2nd vertex (byteOffset)
p1.scale(2); // mutates `positions` in place
// p1.free() is a no-op β `positions` is owned by the caller.
The module is loaded through a portable top-level-await loader (munum/wasm) that inlines the WebAssembly module β it works unchanged in Node, browsers, and any bundler, with no WebAssembly-ESM integration or asset-loader configuration required.
Import from munum/js for the pure-JavaScript implementation, which mirrors the WebAssembly binding's API but has no WebAssembly dependency:
import { lookAt, perspective, Mat4, Vec3 } from 'munum/js';
Sample usage to build a perspective camera view-projection matrix:
use core::f32::{consts::PI, INFINITY};
use munum::{transform, vec3};
let eye = vec3(0_f32, 2., 0.);
let target = vec3(0., 0.6, 0.);
let up = vec3(0., 0., -1.);
let view = transform::look_at(eye, target, up);
let proj = transform::perspective(2., PI/2., 1., INFINITY);
let view_proj = proj * view;
Requires Node.js >= 26 and a Rust toolchain (edition 2024, rustc >= 1.85) with the wasm32-unknown-unknown target.
rustup target add wasm32-unknown-unknown
npm install
npx playwright install chromium # one-time: the browser test project needs Chromium
npm run build # cargo -> wasm-opt -> loader gen -> jco component -> vite (dist/)
npm run lint # eslint (flat config)
npm run typecheck # tsc --noEmit
npm test # vitest: node + browser (Chromium) projects
npm run test:rust # cargo test --all-features
npm run doc # typedoc -> docs/
See AGENTS.md for the full architecture, build pipeline, and contributor guidelines.
This repository and the code inside it is licensed under the MIT License. Read LICENSE for more information.