
Melonjs Math
Use this skill for vector and matrix maths in melonJS, Vector2d, Vector3d, their...
Install
npx skills add melonjs/melonJS --skill melonjs-math -g -a claude-code -y- Repository
- melonjs/melonJS6.4k
- Category
- Engineering
- Updated
- 3d ago
- License
- MIT
- Context cost
- Mediumabout 3,000 tokens
- Runs scripts
- No, instructions only
What it does
Use this skill for vector and matrix maths in melonJS, Vector2d, Vector3d, their observable variants, Matrix2d, Matrix3d, and the math utility functions. Covers which methods mutate and which allocate, the vector pool and when clone() is the wrong tool, interpolation with lerp, slerp, damp and moveTowards, and the argument defaults that silently drop a component. Triggers on: Vector2d, Vector3d, ObservableVector2d, ObservableVector3d, Matrix2d, Matrix3d, vector, vector maths, dot, cross, normalize, length, distance, angle, lerp, slerp, damp, moveTowards, project, clamp, degToRad, radToDeg, clone, vector pool, vector3dPool, setMuted, setCallback, my vector is wrong, z is zero, scale ignores z., moveTowards ignores z, projectN, currentTransform, setTransform, matrix, transform matrix, column-major, val, Float32Array, basis matrix, orientation matrix, Noise, getNoise1d, flicker, wobble.
Vectors, matrices and maths
The one rule that explains most surprises
Methods mutate this and return it. They do not return a new vector.
const a = new Vector3d(1, 0, 0);
const b = new Vector3d(0, 1, 0);
a.cross(b); // a IS NOW the cross product; the original a is gone
a.add(b).normalize().scale(3, 3, 3); // chains, all in place
That is what makes the API allocation-free, and it is the opposite of what a functional style expects. If you need to keep the original, copy into something you own first:
out.copy(a).cross(b); // a untouched, nothing allocated
The exceptions are named. A handful of methods return a NEW vector, and
each has a *Self sibling that does not:
| allocates a new vector | mutates in place |
|---|---|
negate() | negateSelf() |
floor() | floorSelf() |
ceil() | ceilSelf() |
clamp(low, high) | clampSelf(low, high) |
clone() | copy(v) |
So v.negate() in a per-frame loop allocates every frame and v.negateSelf()
does not. The names are the only clue; nothing in the call site shows it.
clone() comes from a pool, and only pays off if you give it back
clone() is vector3dPool.get(...). A pool is fast when instances come back:
get() takes a free one and resets it. Nothing comes back on its own, so a
clone that is simply dropped makes get() miss, construct, and write two
registry entries.
Measured, 3M operations:
out.copy(a).cross(b) | 39ms |
new Vector3d(ax, ay, az).cross(b) | 37ms |
a.clone().cross(b) | 2919ms |
So:
- Transient value inside a function? Use a module-level scratch vector, or
new Vector3d(). Notclone(). - Keeping it, then done with it?
clone()andvector3dPool.release(v)when finished. Releasing is what makes the pool a pool. - Writing a helper that returns a vector? Take an optional
out:
const cross = (a, b, out = new Vector3d()) => out.copy(a).cross(b);
