Physics · interface

ConvexShape

Convex collision shapes: the volume an element actually occupies, rather than a world-axis box around it.

Explained in Your first game, Rigid bodies.

interface ConvexShape
import type { ConvexShape } from '@driftengine/physics';

In depth

One representation covers every convex solid — a point cloud plus two kinds of rounding. A box is its eight corners; a sphere is one point and a radius; a stretch of banked track is its own drawn cross-sections, so the geometry a player sees is the geometry that stops them. Face normals and edge directions are enumerated once, at build time, because they are the separating-axis candidates the sweep in collide.ts tests — a fixed, data-driven axis list per shape pair is what lets two machines replay the same run bit for bit, where an iterate-to-tolerance method would not.

Two kinds of rounding rather than one, as of 2026-08-27. radius grows a shape by a ball and is what makes a sphere and a capsule; sideRadius grows it by a disc perpendicular to the segment through its first two points, and is what makes a cylinder. The second exists because the first cannot express a cylinder at all: a uniform radius rounds the rim along with the side, and the rim is what a wheel rolls on. Everything that consumes a shape reads both, and a shape carrying neither is a plain polytope, which is most of them.

Build-time only: nothing here may be called per frame or per tick. The hull builder is O(n^4) in the point count — trivial for the intended n (a slab is 8–16 points, a body part 8), wrong for a render mesh, hence the cap. Shapes come from the few points that define an element, not from its triangles.

Properties

NameTypeDescription
verticesreadonlyFloat32Arrayxyz-packed points whose convex hull is the shape. Local space for a body's shape, world space for a static collider's — the sweep never needs to know which, because bodies carry a frame and colliders are baked.
faceNormalsreadonlyFloat32Arrayxyz-packed unit face normals, one per direction (a line: n and −n once).
edgeDirsreadonlyFloat32Arrayxyz-packed unit edge directions, one per direction.
radiusreadonlynumberRounding grown around the points — a sphere is one point and this.
sideRadiusreadonlynumberRounding grown around the points only perpendicular to the segment through them, in metres. Zero for everything but a cylinder, which is what it exists for.
More

radius grows a shape uniformly, which is why it cannot express a cylinder: it would round the rim as well as the side. This one grows a shape by a disc rather than by a ball, so the two points of cylinderShape become two flat cap discs joined by a curved side, with a sharp rim between them. The two are independent and compose — a shape with both is a cylinder with a filleted rim — and every constructor but cylinderShape leaves this at zero.

The axis is the shape's own first two points, not a stored vector, so a cylinder baked into world space by a collider carries its orientation the same way its position is carried.

boundRadiusreadonlynumberDistance from the local origin enclosing the whole shape, for broad bounds.
facePlanesreadonlyFloat32Arrayxyzw-packed outward face planes, four floats per face: normal then offset, so a point p lies on the face when dot(n, p) === d.
More

One entry per face, where faceNormals has one per direction. A box has six here and three there, and they are separate arrays because conflating them is what left the gap: SAT wants directions, while clipping and an inertia tensor want faces. Empty for a shape with no faces.

faceVertexStartreadonlyUint16ArrayCSR offsets into faceVertexIndices, length faceCount + 1. Empty when there are no faces.
faceVertexIndicesreadonlyUint16ArrayVertex indices, each face's loop wound counter-clockwise seen from outside.
trianglesreadonlyoptionalTriangleMeshA static triangle mesh, where this shape is one, or absent where it is a convex solid.
More

The one thing in this file that is not convex, and it is a field rather than a second type on purpose. A union would put a narrowing at every call site that takes a shape — the broad phase, the bounds, the collider set, the sweep, the mass, the queries, the cloth — for a shape only the narrow phase and the raycast can act on, and every one of those narrowings would be a place to forget one. A discriminating field is what sideRadius already is, one line up, and this reads the same way: the shape carries what it is and the two places that care ask.

A mesh's vertices are the eight corners of its bounding box, so everything that measures a shape measures the right box; its faceNormals and edgeDirs are empty, so nothing mistakes a hollow level for a solid brick. See meshShape.