Phaser AE

3D Geometry Builders

Free packv3.0.0-43DLogic

A mesh-modelling toolkit as pure functions: primitives, lathes, sweeps, vessels, leaves, deformers, scattering, coating, plane slicing, numbered dice and CSG. You make the shape in code, register it once, and from then on it is an ordinary mesh your game instances.

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

Released October 14, 2026 · updated October 20, 2026

3D Geometry Builders: 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

geometry3d

function geometry3d(): Pack<{ geometry3d: Geometry3dApi }>

The geometry-3d pack: pure vertex builders for every 3D primitive, and a registry so packs can add their own. Headless — it touches no GPU.

Helpers

alongPoints

function alongPoints(values: ReadonlyArray<number>, at: number, closed = false): number

A value given at each control point (a radius, a width), read at a sample's at: smoothly between the points.

arcLengths

function arcLengths(samples: ReadonlyArray<CurveSample>, closed = false): number[]

How far along a sampled curve each sample is, 0 at the start and 1 at the end (by length).

capsuleVerts

function capsuleVerts(segs = 24, rings = 8, r = 0.25): Float32Array<ArrayBuffer>

Capsule (pill): total height 1, radius r (default 0.25) — scale h for longer pills.

circleVerts

function circleVerts(segs = 32, arc = 1): Float32Array<ArrayBuffer>

Flat disc (or pie slice via arc 0..1) in the XZ plane, double-sided, unit diameter.

coatSoup

function coatSoup(target: Soup, o: CoatOptions): Soup

The coat over target: its outer surface and the skirt round its edge.

coneVerts

function coneVerts(segs = 32, open = false, arc = 1): Float32Array<ArrayBuffer>

Capped cone: unit base diameter, unit height, apex up. Smooth sides. open skips the base and double-sides the shell (a visible inside); a based arc (0..1) portion is sealed with flat cut walls.

cubeVerts

function cubeVerts(segs = 1): Float32Array<ArrayBuffer>

Unit cube (flat normals) — the workhorse box. segs grids each face (visually identical when undeformed; matters under vertex fog/deformers).

cylinderVerts

function cylinderVerts(segs = 32, rTop = 1, rBottom = 1, open = false, arc = 1): Float32Array<ArrayBuffer>

Capped cylinder, diameter 1, height 1: smooth side, flat caps. Generalised to a frustum: rTop/rBottom are each end's radius as a fraction of the unit radius (1 = full 0.5; rTop 0 = a cone). open skips the caps and makes the shell DOUBLE-SIDED (an open pipe has a visible inside); a capped arc (0..1) portion is SEALED with flat cut walls (a solid wedge).

deforms

const deforms = (d: Deform | undefined): boolean=> …

Does this deform do anything?

deformSoup

function deformSoup(s: Soup, d: Deform): Soup

A reshaped copy of s (positions only; normals are left as they were — recompute them).

DIE_SIDES

const DIE_SIDES: readonly [4, 6, 8, 10, 12, 20]

The sides a die can have.

dieSoup

function dieSoup(sides: DieSides, o: DieOptions = {}): Soup

A die resting on a face, centered, fitted to its natural box (its longest side 1). Faces are numbered so opposite faces add up to sides + 1 (the d4, which has none opposite, 1–4); the top shows the highest number. Flat normals.

discVerts

function discVerts(fillet = 0.35, segs = 40, filletSegs = 6): Float32Array<ArrayBuffer>

Disc: a coin / casino chip / wheel — a cylinder whose rim edges are FILLETED (rounded where the flat faces meet the flat side). Built as a lathe: the 2D profile (face → quarter-round fillet → straight rim → fillet → face) revolved around Y, with exact normals from the profile. Unit diameter × unit height: scale h thin for a chip, roll it upright (pitch/roll ±90°) for a wheel. fillet = fraction of the smaller half-extent (0..1).

earClip

function earClip(pts: Array<[number, number]>): Array<[number, number, number]>

Ear-clipping triangulation of a simple polygon (no holes, no self-intersections). Returns index triples into pts. O(n²) — geometry builds once per bucket, so simplicity beats asymptotics here.

extrudeVerts

function extrudeVerts(outline: Array<[number, number]>, bevel = 0, bevelSegs = 3): Float32Array<ArrayBuffer>

Extrusion of a simple 2D outline ([x, z] points, auto-fit) through unit height, with an optional rounded bevel where the walls meet the caps. bevel = bevel size as a fraction of the half-height (0..0.9); the caps inset by the bevel (spiky concave outlines want a SMALL bevel — the inset is a plain miter offset, not a full polygon offset).

flatNormals

function flatNormals(s: Soup): Soup

Per-face normals: the faceted look, one flat colour a face.

flipSoup

function flipSoup(s: Soup): Soup

Every triangle wound the other way (and its normals turned round): an inside-out soup put right.

GEOMETRY_STRIDE

const GEOMETRY_STRIDE: 8

Floats per vertex in every geometry: position xyz, normal xyz and uv, so 8.

gradientNoise

function gradientNoise(seed: number): (x: number, y: number, z: number) => number

Smooth 3D gradient noise, about −1…1, the same for the same seed: lumps on a potato, bumps on a rock. procgen's Perlin noise.

gridSamples

function gridSamples(target: Soup, rows: number, columns: number, o: { region?: SurfaceRegion | undefined; stagger?: boolean | undefined; jitter?: number | undefined; seed?: number | undefined } = {}): SurfacePoint[]

Rows × columns over a surface by its uv; alternate rows half a step round (stagger, default on), like corn.

intersectVerts

function intersectVerts(a: Float32Array, b: Float32Array): Float32Array

Returns the volume common to a and b, as one triangle soup.

latheForm

function latheForm(profile: ReadonlyArray<ProfilePoint>, o: FormDetail & { arc?: number } = {}): Soup

A LATHE at real size: profile points [radius, y] (bottom to top, in the caller's units) as a smooth curve revolved about y. An end that does not reach the axis is sealed with a flat cap.

latheVerts

function latheVerts(points: Array<[number, number]>, segs = 32, arc = 1): Float32Array<ArrayBuffer>

Lathe: a 2D profile of [d, y] points (d = distance from the axis, authored BOTTOM → TOP) revolved around Y. Auto-fit to the unit box; smooth normals from the profile tangents. arc sweeps a portion. A profile that doesn't reach the axis (end d > 0) is AUTO-SEALED with a flat cap to the axis at both ends — no pinholes at the poles.

leafForm

function leafForm(length: number, width: number, o: LeafOptions = {}): Soup

A LEAF at real size: a thin blade length long (along +y, base at the origin) and width wide (across x), its face toward +z, closed (top and underside), folded, curled and twisted as asked.

linePoints

function linePoints(count: number, a: V3, b: V3): V3[]

count points evenly along a line from a to b.

mergeSoups

function mergeSoups(list: ReadonlyArray<Soup>): Soup

Several soups as one.

MESHES

const MESHES: Record<string, MeshBuilder>

Every built-in primitive, unit-sized. Options are per shape; anything omitted takes the shape's default.

moveSoup

function moveSoup(s: Soup, d: V3): Soup

Move a soup.

outward

function outward(s: Soup): Soup

A closed soup wound outward (CCW seen from outside, as the renderer culls).

panelVerts

function panelVerts(r = 0.35, seg = 6): Float32Array<ArrayBuffer>

Panel: an extruded ROUNDED-CORNER rectangle (unit footprint in XZ, unit thickness in y — scale h thin for cards/plaques). r = corner radius as a fraction of the half-extent. Smooth wall normals around the corner arcs via the same clamp-to-core trick as the rounded box.

placeCopies

function placeCopies(item: Soup, places: ReadonlyArray<Placement>): Soup

Copies of item (built about its own origin, +y up) at each placement, merged into one soup.

planeVerts

function planeVerts(segs = 1): Float32Array<ArrayBuffer>

A double-sided unit sheet in the XZ plane, centred on the origin, facing +y on top and -y underneath: ground sheets, water, cards laid flat. segs (rounded, at least 1; default 1) divides each side into that many cells, which a deformer or vertex effect needs: a two-triangle plane interpolates everything across one long diagonal.

polyhedronVerts

function polyhedronVerts(kind: 'tetrahedron' | 'octahedron' | 'icosahedron' | 'dodecahedron', detail = 0): Float32Array<ArrayBuffer>

A unit-sphere-fit Platonic solid with flat faces. detail subdivides each face 0..4 times toward a sphere. Default 0 (the raw solid).

quadVerts

function quadVerts(segs = 1): Float32Array<ArrayBuffer>

A unit quad in the XY plane facing +z. The billboard mesh: local x is right, local y is up.

ringPlacements

function ringPlacements(count: number, o: { radius?: number | undefined; tilt?: number | undefined; jitter?: number | undefined; seed?: number | undefined; scale?: [number, number] | undefined } = {}): Placement[]

Copies round a vertical axis: bases on a circle of radius, each leaning out by tilt degrees, evenly spaced.

ringVerts

function ringVerts(inner = 0.5, segs = 32, arc = 1): Float32Array<ArrayBuffer>

Flat ring/annulus (or portion via arc) in the XZ plane, double-sided. inner = inner radius as a fraction of the outer (0..1).

roundedBoxVerts

function roundedBoxVerts(r = 0.2, seg = 7): Float32Array<ArrayBuffer>

Rounded ("chamfered") unit box: edges and corners bevel smoothly with radius r (fraction of the half-extent, 0..0.5). The classic trick: build each face as a grid, clamp each point to the shrunken core box, and push it out along the (smooth) normal from the core — flat mid-faces, curved edges.

roundedPlaneVerts

function roundedPlaneVerts(r = 0.3, segs = 28): Float32Array<ArrayBuffer>

A double-sided flat sheet in the XZ plane with ROUNDED CORNERS, gridded so it still bends — a playing card, a token, a rounded sign. r is the corner radius as a fraction of the half-extent (0 = square, 1 = a stadium).

Why not panelVerts for a card: a panel is an EXTRUSION, so it has side walls (a surface effect runs on them, and real cards are far too thin for the thickness to read) and its faces are triangle FANS, which cannot carry a vertex bend. This is a grid, exactly like planeVerts, with the outline pushed onto a rounded rectangle.

segs sets BOTH the bend smoothness and how finely the corner arcs are sampled, so a large on-screen card wants more of it.

scaleSoup

function scaleSoup(s: Soup, k: V3): Soup

Scale a soup about the origin (per axis), normals carried properly.

shapeVerts

function shapeVerts(outline: Array<[number, number]>): Float32Array<ArrayBuffer>

Flat filled shape: a simple 2D outline of [x, z] points triangulated (ear clipping — no holes), double-sided in the XZ plane, auto-fit.

sliceByPlane

function sliceByPlane(s: Soup, axis: 'x' | 'y' | 'z', d: number, keep: 'below' | 'above' = 'below'): { kept: Soup; face: Soup }

The soup on one side of the plane axis = d, and the cap closing it (its normal pointing out of the kept side).

smoothCurve

function smoothCurve(points: ReadonlyArray<ReadonlyArray<number>>, o: CurveOptions = {}): CurveSample[]

The curve through points, sampled. Every control point is on the curve; corners and the two ends of an open curve are always samples. With smooth: false the samples are the points themselves.

smoothNormals

function smoothNormals(s: Soup, crease = 50): Soup

Smooth normals: every corner takes the area-weighted average of the faces meeting at its POINT whose normals are within crease degrees of its own face's, so curved surfaces read round and real edges (a cap's rim, a corner) stay sharp. Points are matched by position, so seams and separately built pieces join smoothly.

soupBounds

function soupBounds(s: Soup): SoupBounds

The box a soup fills; a zero box at the origin for an empty one.

soupVolume

function soupVolume(s: Soup): number

The signed volume a closed soup encloses: negative when it is wound inside out.

sphereVerts

function sphereVerts(segs = 28, rings = 18): Float32Array<ArrayBuffer>

UV sphere, diameter 1, smooth normals, equirect UVs (planet maps wrap).

spiralSamples

function spiralSamples(target: Soup, count: number, o: { region?: SurfaceRegion | undefined; jitter?: number | undefined; seed?: number | undefined } = {}): SurfacePoint[]

count points on the golden-angle spiral over a surface by its uv: the phyllotaxis of a pineapple or a pinecone.

subdivideSoup

function subdivideSoup(s: Soup, maxEdge: number, passes = 8): Soup

Split every edge longer than maxEdge at its middle, again until none is, so a shape with long flat faces (a box, a cylinder's sides) can bend. A split depends on the edge alone, so the two triangles that share an edge split it alike and no cracks open. passes caps the rounds.

subtractVerts

function subtractVerts(a: Float32Array, b: Float32Array): Float32Array

Returns a with b cut away, as one triangle soup.

surfaceSamples

function surfaceSamples(target: Soup, count: number, o: { region?: SurfaceRegion | undefined; seed?: number | undefined } = {}): SurfacePoint[]

count points spread EVENLY over a surface (best-candidate sampling: each new point is the farthest of a dozen random tries from the ones already placed), within a region. The same seed gives the same points.

sweepForm

function sweepForm(path: ReadonlyArray<ReadonlyArray<number>>, o: SweepOptions): Soup

A SWEEP at real size: a section swept along a smooth path through path's control points, its radius eased from point to point, with round, flat or pointed ends. uv: u round the section, v along the path (0…1), so a pattern can follow the curve.

torusKnotVerts

function torusKnotVerts(p = 2, q = 3, segs = 96, sides = 10, tube = 0.24): Float32Array<ArrayBuffer>

Torus knot (p, q winding) as a closed tube, fit to the unit box.

torusVerts

function torusVerts(major = 0.34, minor = 0.16, segs = 36, sides = 18, arc = 1): Float32Array<ArrayBuffer>

Torus in the XZ plane, outer diameter 1, smooth normals. arc (0..1) sweeps a portion of the ring; partial sweeps are SEALED with flat caps at both cut ends (a cut sausage, not a hollow shell).

toUnitBox

function toUnitBox(s: Soup, b: SoupBounds = soupBounds(s)): Soup

Fit a soup INTO THE UNIT BOX the renderer scales by a part's size: positions centred and divided by the soup's own extent on each axis (so it fills −0.5…0.5; pass b to fit several soups by one box), normals multiplied by that extent (the inverse transpose of the divide), so that once the renderer scales by the size — it divides normals by the instance size — they are the normals of the shape at that size. An axis the soup is flat along keeps a sliver of thickness, so normals never collapse.

transformVerts

function transformVerts(verts: Float32Array, xf: Transform): Float32Array

Return a copy of verts with a placement baked in: scale → rotate → move. Normals rotate too (divided by scale first for non-uniform, then renormalised).

tubeVerts

function tubeVerts(path: number[][], r = 0.16, sides = 12, closed = false): Float32Array<ArrayBuffer>

Tube swept along a 3D polyline (auto-fit to the unit box). r = tube radius as a fraction of the unit half-extent; closed joins the ends.

unionVerts

function unionVerts(a: Float32Array, b: Float32Array): Float32Array

Returns the union of two triangle soups as one soup — a plus b.

uvPoint

function uvPoint(target: Soup, u: number, v: number): SurfacePoint | null

The surface point at a uv (a lathe, a cylinder or a sweep's u round and v along), or null if no triangle has it.

vesselForm

function vesselForm(profile: ReadonlyArray<ProfilePoint>, wall: number, o: FormDetail & { fill?: VesselFill } = {}): VesselForm

A VESSEL at real size: a hollow lathe. profile is the OUTSIDE (bottom to top, [radius, y], the caller's units); the inside is the outside moved in by wall, joined to it by a rounded rim, with a floor wall thick. fill builds the contents to a level, filling the inside exactly.

volumePoints

function volumePoints(count: number, within: ScatterVolume, size: V3, gap: number, seed = 1): V3[]

Up to count points packed in a volume, at least gap apart (dart throwing), the same for the same seed.

wedgeVerts

function wedgeVerts(): Float32Array<ArrayBuffer>

Wedge (ramp): a unit box halved diagonally — flat floor, vertical back at -x, slope up toward -x.

Classes

Builder

class Builder

Triangle-soup builder for custom geometry (procgen packs use this via world.custom). quad()/triP() self-correct winding — nothing ships inside-out.

  • triP(a: number[], b: number[], c: number[]): void — One triangle from three (pos+normal, optionally +uv) points — 6 or 8 components each. WINDING IS SELF-CORRECTING: the pipeline culls back faces expecting CCW-from-outside, so if the triangle's geometric normal disagrees with its vertex normals (wound clockwise), it is flipped here — no generator can ship inside-out. Points without a uv are BOX-PROJECTED from the face's dominant axis (unit-box coords → 0..1).
  • quad(a: number[], b: number[], c: number[], d: number[]): void — Two triangles for the quad a-b-c-d (counter-clockwise seen from outside).
  • build(): Float32Array<ArrayBuffer> — Returns the collected vertices as one interleaved Float32Array (8 floats each).

Solid

class Solid

A solid you can boolean against another. Build from any triangle-soup (a this module's generators generator's output), optionally with a placement, then chain .subtract() / .union() / .intersect() and hand .toVerts() to registerMesh3d(k, name, solid.toVerts()) — one baked, instanced mesh.

const die = Solid.fromVerts(roundedBoxVerts(0.22, 3))
  .subtract(Solid.fromVerts(sphereVerts(16, 12), { pos: [0, 0.5, 0], scale: 0.2 }));
world.custom('die', () => die.toVerts(), { w: S, h: S, d: S, color });

Bake-time only; keep tools clean/closed (see the robustness note up top).

  • static fromVerts(verts: Float32Array, xf?: Transform): Solid — Wrap a triangle-soup, optionally baking a placement into it.
  • static merge(solids: Solid[]): Solid — Concatenate solids WITHOUT a boolean. Correct only when they do not overlap each other — and when they do not, it is the right answer, because the union of disjoint solids IS their concatenation. This is the one to reach for when building a tool set. A die's twenty-one pips are twenty-one separate balls: union would run twenty BSP booleans to discover they never touch, which is slow enough to stall a page, while this is an array copy. Overlapping tools still want union.
  • static union(solids: Solid[]): Solid — Union many solids into one, properly — a full boolean per pair, so overlapping solids come out as a single skin. Combine cutting TOOLS this way before a single subtract: "union the tools, subtract once" keeps the polygon count, and the bake time, an order of magnitude below N sequential subtracts. For tools you know are disjoint, merge does the same job without the booleans.
  • union(other: Solid): Solid — Returns a new solid that is this one plus other.
  • subtract(other: Solid): Solid — Returns a new solid with other cut away from this one.
  • intersect(other: Solid): Solid — Returns a new solid: the volume common to this one and other.
  • toVerts(): Float32Array — The result as interleaved (pos, normal, uv) soup for registerMesh3d.

Types

CoatOptions

interface CoatOptions

How a coat is laid.

  • thickness: number — How thick, in the target's units.
  • from?: number | undefined — Where its edge sits, 0 (the bottom) … 1 (the top) of the target's height. Default 0.5: the top half.
  • drips?: number | undefined — Drips running down from the edge. Default none.
  • dripDepth?: number | undefined — How far the longest drip runs, in the target's units. Default a fifth of the target's height.
  • wave?: number | undefined — How much the edge waves, in the target's units. Default a thirtieth of the target's height.
  • waves?: number | undefined — Waves round the edge. Default 7.
  • seed?: number | undefined — A different set of drips and waves.
  • under?: boolean | undefined — Cover the bottom instead (the edge rises to from, and the part below it is coated).

CurveOptions

interface CurveOptions

How a curve is drawn.

  • closed?: boolean | undefined — Join the last point back to the first. Default false.
  • corners?: ReadonlyArray<boolean> | undefined — Which control points are corners.
  • smooth?: boolean | undefined — False: straight lines between the points. Default true.
  • maxTurn?: number | undefined — Most degrees the curve may turn between two samples (smaller is smoother). Default 7.
  • maxStep?: number | undefined — Longest step between samples, in the curve's own units. Default: no limit.
  • steps?: number | undefined — Dense samples a span is first cut into, before the gentle ones are dropped. Default 32.

CurveSample

interface CurveSample

One sample of a curve.

  • p: number[] — The point.
  • at: number — Where it is among the control points: 2.5 is halfway from point 2 to point 3 (radii and widths follow this).
  • corner: boolean — A sharp angle here (a corner point, or an end).

Deform

interface Deform

How to reshape a soup. Every field is optional; they apply in this order: squash, lobes, taper, twist, noise, bend.

  • bend?: number — Degrees the y axis curves through, as a circular arc about the middle (banana, horn, cucumber).
  • bendAxis?: 'x' | 'z' — Which way it bends: toward +x (z, the default: it turns about z) or toward +z (x).
  • taper?: [number, number] — The section's scale at the top relative to the bottom, [x, z] (carrot, cone, trunk).
  • twist?: number — Degrees the section turns about y from bottom to top (licorice, rope).
  • lobes?: { count: number; depth: number; sharp?: number } — The section pushed in and out round y: count lobes, depth 0…1 of the radius, sharp 0 (round waves) … 1 (narrow creases).
  • noise?: { amount: number; scale?: number; seed?: number } — Smooth lumps along the surface: amount in the soup's units, scale the size of a lump (default a fifth of the shape), seed.
  • squash?: [number, number] — The section's scale, [x, z]: an oval.

DieOptions

interface DieOptions

How a die is made.

  • chamfer?: number | undefined — 0 (sharp) … 0.4: how much of each edge is cut away by a chamfer, as a fraction of the die's size. Default 0.12.

DieSides

type DieSides = typeof DIE_SIDES[number]

The number of sides of one die: 4, 6, 8, 10, 12 or 20.

FormDetail

interface FormDetail

How finely a curved form is cut.

  • around?: number | undefined — Segments round the axis (lathe, vessel) or round the section (sweep). Default 24.
  • maxTurn?: number | undefined — Most degrees a profile or path may turn between samples. Default 7.
  • smooth?: boolean | undefined — False: straight lines between control points. Default true.
  • crease?: number | undefined — Degrees past which two faces meet as a sharp edge rather than a smooth one. Default 45.

Geometry3dApi

interface Geometry3dApi

game.geometry3d: build a named primitive's vertices, or register your own.

  • readonly names: ReadonlyArray<string> — Every built-in primitive name.
  • build(name: string, opts?: Record<string, number | boolean>): Float32Array — Build a primitive's interleaved vertices (pos xyz, normal xyz, uv — 8 floats a vertex), unit-sized. Throws on an unknown name.
  • define(name: string, build: MeshBuilder): void — Register your own builder under a name, so build(name) and world3d.mesh(name, opts) find it (world-3d builds and uploads it on first ask). Defining a name again replaces the builder: world3d.mesh(name, opts) builds from the new one the next time it is asked, under the same mesh id, so the objects already wearing it change shape too. The same builder again changes nothing. Throws on a built-in primitive's name.
  • has(name: string): boolean — Whether a name is known.

LeafOptions

interface LeafOptions extends FormDetail

How a leaf is shaped.

  • widths?: ReadonlyArray<number> | undefined — Half-widths along its length, base to tip, 0…1 of the full width (smooth between). Default a classic leaf.
  • fold?: number | undefined — Degrees of the V folded along its midrib.
  • curl?: number | undefined — Degrees it curls along its length (toward its face, +z).
  • twist?: number | undefined — Degrees it twists from base to tip.
  • thickness?: number | undefined — Its thickness, in the caller's units. Default a fortieth of its length.

MeshBuilder

type MeshBuilder = (o: Record<string, number | boolean>) => Float32Array

Builds one primitive's interleaved vertices from an options record.

Placement

interface Placement

One copy's placement: where, which way its +y points, how far it is spun about that, and how big.

  • pos: V3 — Where the copy's origin goes, in world units.
  • up: V3 — The direction the copy's +y points; any non-zero length, as it is normalised.
  • spin?: number — Turn about up, in radians; default 0.
  • scale?: number | V3 — Size: one number for all three axes, or a factor for each of x, y and z; default 1.

ProfilePoint

type ProfilePoint = [number, number] | [number, number, number]

A profile point: [radius, y], with a third value 1 for a corner.

ScatterVolume

type ScatterVolume = 'sphere' | 'cone' | 'box' | 'cylinder' | 'bunch'

A volume copies are packed into, about the origin, size its box.

Soup

type Soup = Float32Array

An interleaved triangle soup: 8 floats a vertex, 3 vertices a triangle.

SoupBounds

interface SoupBounds

The axis-aligned box a soup fills.

  • min: V3 — The smallest x, y and z of any vertex.
  • max: V3 — The largest x, y and z of any vertex.

SurfacePoint

interface SurfacePoint

A point on a surface, and which way the surface faces there.

  • pos: V3 — The point on the surface, in the soup's own units.
  • normal: V3 — The unit direction the surface faces there.

SurfaceRegion

type SurfaceRegion = 'all' | 'top' | 'side' | 'bottom' | { from: number; to: number }

Which part of a surface: everything, the parts facing up, sideways or down, or a band of its height (0…1).

SweepOptions

interface SweepOptions extends FormDetail

How a shape is swept along a path.

  • radius: number | ReadonlyArray<number> — Radius (the caller's units): one value, or one per control point, eased between them.
  • section?: SweepSection | undefined — Round (default), a number of flat sides, a star, or a closed outline of [x, z] points.
  • squash?: [number, number] | undefined — The section's scale across [normal, binormal]: an oval.
  • caps?: 'round' | 'flat' | 'point' | 'none' | undefined — How the ends finish: round (default), flat, point, or none (open).
  • closed?: boolean | undefined — The path loops back to its start (no caps).
  • twist?: number | undefined — Degrees the section turns from start to end.
  • beads?: { count: number; depth: number } | undefined — The radius swells and narrows along the path: count beads, depth 0…1 how much it narrows between them.
  • corners?: ReadonlyArray<boolean> | undefined — Which control points are corners.

SweepSection

type SweepSection = 'round' | number | { star: number; depth: number } | { outline: ReadonlyArray<[number, number]> }

A sweep's cross-section.

Transform

interface Transform

Placement of one solid before it joins the shared CSG space. scale is uniform or per-axis; rotation follows the shader's roll·pitch·yaw order.

  • pos?: [number, number, number] — The translation, in world units. Default the origin.
  • scale?: number | [number, number, number] — Uniform (one number) or per-axis scale. Default 1.
  • yaw?: number — Rotation about Y, in radians. Default 0.
  • pitch?: number — Rotation about X, in radians. Default 0.
  • roll?: number — Rotation about Z, in radians. Default 0.

VesselFill

interface VesselFill

What a vessel holds.

  • level: number — How full: 0 (empty) … 1 (to the brim) of the inside height.
  • surface?: 'flat' | 'dome' | undefined — The top of the contents: flat (a drink) or a dome (a heaped bowl, ice cream). Default flat.

VesselForm

interface VesselForm

The pieces of a vessel.

  • outside: Soup — The outside wall, the rim and the base.
  • inside: Soup — The inside wall and floor.
  • fill: Soup | null — The contents (a solid filling the inside to the fill level), or null.