Phaser AE

Vector Hulls

Free packv3.0.0-32DVector

What a physics engine needs and a vector outline never has. Hulls, convex pieces and triangles, so an outline you drew can become a collider or a fill.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

Vector Hulls: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

vectorHull

function vectorHull(): Pack<{ vecHull: VecHullApi }>

The vector-hull pack. Pure maths, so it needs no backend and runs headless.

Functions

convexHull

function convexHull(points: readonly VecPt[]): VecPt[]

The convex hull of a point set (monotone chain), counter-clockwise in standard axes — which is CLOCKWISE on screen, y being down.

Fewer than three distinct points comes back as-is; collinear points are dropped.

convexPieces

function convexPieces(points: readonly VecPt[], max = MAX_HULL_POINTS, tolerance = 0): VecPt[][]

Split an outline into CONVEX pieces (ear clipping, then merging ears back together while the join stays convex and within max vertices).

The honest way to give a concave silhouette — a ship, a plus, a letter — real collision instead of the hull's approximation: each piece becomes its own Box2D polygon on ONE body, so the notch is a notch.

Built for outlines a hand drew, not only clean authored ones. Repeated points, runs of collinear points and zero-width spikes are cleaned out first; an outline that touches itself at a point still decomposes; one that CROSSES itself is cut at each crossing into loops, each decomposed on its own.

tolerance is the smallest feature that counts, in the outline's own units: points nearer each other than this are one point, and a vertex nearer than this to the line through its neighbours lies on that line. No piece comes back with fewer than three points, two points that close, a vertex that flat, or a thickness under it. A sliver is merged into a neighbour where the join stays convex and is dropped where it will not, which loses at most a strip tolerance thick. At 0 (the default) it is a billionth of the outline's size, which removes rounding and nothing anyone could see; a physics wrapper passes its solver's own weld distance, so every piece survives the solver's hull step.

Pieces wind counter-clockwise in standard axes (clockwise on screen) and have at most max vertices; a max below 3 is taken as 3, since a triangle cannot be split. An outline that is already convex and within budget comes back as one piece, so the cheap case stays cheap; one with fewer than three usable points comes back as [].

hullFor

function hullFor(points: readonly VecPt[], max = MAX_HULL_POINTS): VecPt[]

Outline → a point list Box2D will actually accept: convex, at most MAX_HULL_POINTS vertices, in the shape's own local frame.

Do not re-centre the result on its centroid. Box2D computes centroid, mass and inertia from the polygon in body-local space, so passing the raw local points keeps the drawn outline and the collision shape registered. Re-centring looks tidier and is the bug — a shape that spins about a point it isn't drawn around.

isConvex

function isConvex(p: readonly VecPt[]): boolean

True if every turn goes the same way (a convex simple polygon).

simplifyHull

function simplifyHull(points: readonly VecPt[], max = MAX_HULL_POINTS): VecPt[]

Reduce a CONVEX outline to at most max vertices, dropping the ones that cost the least area — so the silhouette survives as well as it can.

This is the step that stops an 11-point asteroid becoming a null Box2D shape. Run convexHull first; on a concave input the areas are meaningless.

triangulate

function triangulate(points: readonly VecPt[]): Array<[number, number, number]>

Ear-clipping triangulation of a simple polygon — index triples into points. Handles concave outlines and either winding; no holes, no self-intersection. Degenerate input fans the remainder rather than looping.

This is what turns a vector OUTLINE into a filled face. It lives here rather than being borrowed from the 3D geometry module because the vector layer must never pull in the 3D chunk.

Constants

MAX_HULL_POINTS

const MAX_HULL_POINTS: 8

Vertex limit for one hull or convex piece: 8, Box2D's polygon maximum; the default max of the hull and piece functions.

Types

Vec2

interface Vec2

A 2D point or vector. Plain data, so anything can produce one.

  • x: number — Horizontal coordinate in the caller's own units (pixels for screen paths); grows rightward.
  • y: number — Vertical coordinate in the caller's own units (pixels for screen paths); grows downward on screen.

VecHullApi

type VecHullApi = typeof kit

game.vecHull — every function this pack carries, as one namespace.

VecPt

type VecPt = Vec2

A point on a path — the same shape, named for what it is used as.