Math & geometry

Math & geometry

Vectors, matrices, quaternions, Catmull-Rom splines, simplex and curl noise, and robust geometric predicates in the engine's core math library.

The engine’s core math library lives under @woosh/meep-engine/src/core/geom/ and @woosh/meep-engine/src/core/math/. Every function in it follows the same design contract: results are written into a caller-supplied output parameter, never returned as new allocations. This keeps hot paths — physics, animation, rendering — allocation-free at steady state.

Vectors

Vectors are plain JavaScript objects or arrays. The library does not impose a class hierarchy; most functions accept any object with the right shape.

Vec2

Utilities under core/geom/vec2/:

FunctionDescription
v2_dot(ax, ay, bx, by)Dot product — returns a scalar
v2_length(x, y)Euclidean length
v2_length_sqr(x, y)Squared length (avoids a square root)
v2_distance(ax, ay, bx, by)Distance between two points
v2_distance_sqr(ax, ay, bx, by)Squared distance
v2_cross_product(ax, ay, bx, by)2D cross product (scalar)
v2_angle_between(ax, ay, bx, by)Angle between two vectors in radians
v2_matrix3_cm_multiply(out, x, y, m)Transform by a column-major 3×3 matrix

Vec3

Utilities under core/geom/vec3/:

FunctionDescription
v3_dot(ax, ay, az, bx, by, bz)Dot product
v3_length(x, y, z)Euclidean length
v3_length_sqr(x, y, z)Squared length
v3_distance(ax, ay, az, bx, by, bz)Distance
v3_distance_sqr(...)Squared distance
v3_lerp(result, ax, ay, az, bx, by, bz, t)Linear interpolation; writes to result.set(x, y, z)
v3_slerp(result, ...)Spherical linear interpolation
v3_angle_between(ax, ay, az, bx, by, bz)Angle in radians
v3_displace_in_direction(result, ox, oy, oz, dx, dy, dz, dist)Move a point along a direction
v3_quat3_apply(out, offset, vx, vy, vz, qx, qy, qz, qw)Rotate a vector by a unit quaternion
v3_quat3_apply_inverse(out, offset, ...)Rotate by the conjugate (inverse rotation)
v3_matrix4_multiply(out, offset, x, y, z, m)Full 4×4 transform including translation
v3_matrix4_rotate(out, offset, x, y, z, m)Rotational part only (no translation)

The out-parameter convention is uniform: the output buffer and an integer offset come first, followed by the inputs. For example:

import { v3_quat3_apply } from "@woosh/meep-engine/src/core/geom/vec3/v3_quat3_apply.js";

const out = new Float32Array(3);
// Rotate (0, 1, 0) by a unit quaternion stored as (qx, qy, qz, qw):
v3_quat3_apply(out, 0, 0, 1, 0, qx, qy, qz, qw);

No intermediate object is created. out may be a plain array, a Float32Array, or any array-like.

Vec4

Minimal utilities under core/geom/vec4/:

FunctionDescription
v4_dot(ax, ay, az, aw, bx, by, bz, bw)Dot product
v4_length(x, y, z, w)Euclidean length
v4_length_sqr(x, y, z, w)Squared length
v4_multiply_mat4(out, x, y, z, w, m)Multiply by a 4×4 matrix

Matrices

Mat3

Column-major 3×3 utilities under core/geom/mat3/:

FunctionDescription
m3_multiply(out, a, b)Matrix product out = a × b
m3_multiply_vec3(out, m, x, y, z)Transform a vector
m3_determinant(m)Scalar determinant
m3_cm_invert(out, m)Invert a column-major matrix
m3_cm_compose_transform(out, tx, ty, rotation, sx, sy)Build a 2D TRS matrix
m3_cm_extract_rotation(out, m)Extract the rotation sub-matrix
m3_make_rotation(out, angle)Rotation about the Z axis

Mat4

Column-major 4×4 utilities under core/geom/3d/mat4/:

FunctionDescription
m4_multiply(out, a, b)Matrix product; returns out
m4_invert(out, m)General inverse
m4_inverse_rotation_translation(out, m)Fast inverse for rigid-body matrices
compose_matrix4_array(out, position, rotation, scale)Build a TRS matrix from position, unit quaternion, and scale objects
decompose_matrix_4_array(m, position, rotation, scale)Decompose a TRS matrix
m4_make_translation(out, x, y, z)Translation matrix
m4_make_scale(out, x, y, z)Scale matrix
m4_extract_scale(out, m)Extract scale factors
m4_orthographic_off_center_z0(out, l, r, b, t, near, far)Orthographic projection, depth mapped to [0, 1]
m4_rotation_translation(out, quat, pos)Rotation + translation (no scale)
apply_mat4_transform_to_v3_array(positions, count, m)Batch-transform a flat Float32Array of positions in place
apply_mat4_transform_to_direction_v3_array(dirs, count, m)Batch-transform directions (ignores translation)

Quaternions

Quaternions use (x, y, z, w) component order — w last — throughout. Utilities live under core/geom/3d/quaternion/.

FunctionDescription
quat3_createFromAxisAngle(axis, angle, result)Build a quaternion from an axis vector and an angle in radians
quat3_multiply(out, offset, ax, ay, az, aw, bx, by, bz, bw)Hamilton product out = a ⊗ b; applies b first then a
quat3_to_matrix3(out, qx, qy, qz, qw)Build a 3×3 rotation matrix

The quat3_multiply convention: to rotate a vector by q1 and then q2, compose q2 ⊗ q1.

import { quat3_multiply } from "@woosh/meep-engine/src/core/geom/3d/quaternion/quat3_multiply.js";

const out = [0, 0, 0, 0];
// Compose: apply q1 first, then q2.
quat3_multiply(out, 0, q2x, q2y, q2z, q2w, q1x, q1y, q1z, q1w);

Morton-encoded quaternions for compact storage: quat_encode_to_uint32 / quat_decode_from_uint32 pack a unit quaternion into a single uint32.

Catmull-Rom splines

computeCatmullRomSpline samples a centripetal (or uniform) Catmull-Rom spline through an arbitrary set of N-dimensional control points.

import { computeCatmullRomSpline } from
  "@woosh/meep-engine/src/core/math/spline/computeCatmullRomSpline.js";

const controlPoints = [0,0, 1,2, 3,1, 4,3]; // 4 points in 2D
const output = new Array(20 * 2);             // 20 samples × 2 dimensions

computeCatmullRomSpline(
    output,
    controlPoints, 4,   // input, point count
    2,                  // dimensions per point
    20,                 // number of output samples
    0.5                 // alpha (0 = uniform, 0.5 = centripetal, 1 = chordal)
);

The alpha parameter controls parameterisation: 0.5 (default) is centripetal and avoids cusps or self-intersections. A non-uniform variant computeNonuniformCatmullRomSplineSample lets you sample a single segment with explicit control points.

Related spline types with bounds and intersection tests are under the same directory: spline3_hermite, spline3_bezier, and their _bounds / _derivative / _integral companions.

Simplex and curl noise

Simplex noise

create_simplex_noise_2d returns a seeded 2D noise function with output in [-1, 1]:

import { create_simplex_noise_2d } from
  "@woosh/meep-engine/src/core/math/noise/create_simplex_noise_2d.js";

const noise = create_simplex_noise_2d(Math.random);
const v = noise(x, y); // -1..1

The underlying 3D gradient-noise primitive sdnoise3 (from sdnoise.js) returns both the noise value and its analytical spatial derivative — useful for domain-warping and normal-map generation without finite differences.

noise_octaves layers multiple octaves of any noise function:

import { noise_octaves } from
  "@woosh/meep-engine/src/core/math/noise/noise_octaves.js";

const value = noise_octaves(
    noise, null,           // noise function + context
    x, y, z,
    6,                     // octave count
    0.5,                   // persistence (amplitude falloff)
    2.0                    // lacunarity (frequency multiplier)
);

Curl noise

curl_noise_3d computes a divergence-free 3D vector field from three offset simplex noise samples. The result is written into a caller-supplied array:

import { curl_noise_3d } from
  "@woosh/meep-engine/src/core/math/noise/curl_noise_3d.js";

const result = [0, 0, 0];
curl_noise_3d(result, x, y, z);
// result[0], result[1], result[2] — divergence-free velocity

curl_noise_3dt adds a w (time) parameter for animated curl fields, displacing each noise sample along the time axis independently.

Robust geometric predicates

Two predicates delegate to the robust-predicates package for exact arithmetic, avoiding floating-point sign errors at degenerate configurations.

orient3d_robust(points, a, b, c, d) — tests whether point d is above, on, or below the plane defined by the counterclockwise triangle (a, b, c). points is a flat array of coordinates in stride-3 format ([x0, y0, z0, x1, y1, z1, ...]). Returns positive if d is above, negative if below, zero if coplanar.

in_sphere3d_robust(points, a, b, c, d, e) — tests whether point e is inside, on, or outside the circumsphere of the positively oriented tetrahedron (a, b, c, d). Returns positive if inside.

A fast (non-robust) variant orient3d_fast is available in the same directory for contexts where degenerate cases cannot occur.

import { orient3d_robust } from
  "@woosh/meep-engine/src/core/geom/3d/plane/orient3d_robust.js";
import { in_sphere3d_robust } from
  "@woosh/meep-engine/src/core/geom/3d/sphere/in_sphere3d_robust.js";

const pts = [/* flat x,y,z per point */];
const side  = orient3d_robust(pts, 0, 1, 2, 3);   // sign tells which side
const inside = in_sphere3d_robust(pts, 0, 1, 2, 3, 4); // sign tells in/out

These are used internally by the Delaunay/tetrahedral mesh code and the physics narrowphase.

Where to go next