Math primitives
The math layer is plain readonly data and pure functions. Every other module builds on it.
The y axis points down, as on a canvas. A positive angle therefore turns +x towards +y, which appears clockwise on screen.
Vectors
Section titled “Vectors”Vec is an unbranded { x, y } displacement. Use it for deltas, sizes and directions.
| Function | Result |
|---|---|
vecAdd(a, b) |
a + b |
vecSub(a, b) |
a - b |
vecScale(v, s) |
v · s |
vecRotate(v, θ) |
v rotated by θ radians |
vecLength(v) |
The Euclidean length of v |
Every function returns a plain Vec, even when its inputs are branded points.
Angles
Section titled “Angles”Radians is a branded number. Create one with radians(value). A plain number does not type-check where Radians is expected, so a value in degrees cannot slip through by accident.
Points and coordinate spaces
Section titled “Points and coordinate spaces”A point is a Vec branded with its coordinate space:
| Type | Space | Constructor |
|---|---|---|
PagePoint |
The infinite canvas | pagePoint(x, y) |
LocalPoint |
A frame’s unrotated space, origin top-left | localPoint(x, y) |
ScreenPoint |
The viewport, in CSS pixels | screenPoint(x, y) |
Passing a point from one space where another space is expected fails to compile. Any point is still a Vec, so the vector functions accept it. Their results are unbranded, and you must brand a result again on purpose.
Matrices
Section titled “Matrices”Mat is a 2D affine transform with the fields a to f, in the order of the canvas setTransform call:
x' = a·x + c·y + ey' = b·x + d·y + fMAT_IDENTITYis the identity transform.matTranslate(v),matRotate(θ)andmatScale(sx, sy)build the basic transforms.matMultiply(m, n)composes two transforms. The result appliesnfirst, thenm.matApply(m, v)transforms a vector.
There is no inverse yet. Frame conversions undo their transform step by step instead.
Box is an axis-aligned box { minX, minY, maxX, maxY }.
boxFromPoints(points)returns the smallest box that holds every point. The list must contain at least one point, and the type enforces this.boxUnion(a, b)returns the smallest box that holds both boxes.boxContains(box, p)checks whetherpis inside the box. Points on an edge count as inside.boxIntersects(a, b)checks whether two boxes overlap. Boxes that only touch along an edge or at a corner count as overlapping, so a zero-width box still intersects what it lies on.