Physics · interface

TriangleMesh

A static triangle mesh as a collision shape: the triangles, their planes, and the tree over them.

Explained in Your first game, Rigid bodies.

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

In depth

Static only, and the limit is enforced rather than documented. PhysicsWorld.addBody refuses one on anything but a static body and colliderFromShape refuses one outright. A moving concave mesh needs its tree rebuilt or refitted every tick and needs a mass tensor a triangle soup does not have — and the usual answer to a moving concave thing is convex decomposition, which is a row of its own. Most consumers want exactly this: the level is a mesh and everything in it is a hull, a box or a sphere.

The tree is DynamicTree, built once and never moved. A second acceleration structure would be a second thing to get right, and the one this package already has is an AABB tree with a tested query — its incremental machinery simply goes unused. What that costs is the 10 cm fat margin every proxy carries, which for a small triangle means a query returns a few candidates it did not need; the narrow phase rejects them in one separating-axis test each. What would make it wrong is a mesh of millimetre triangles, where the margin dwarfs the geometry and every query returns most of the mesh.

The interior-edge data is the half that decides whether it feels right. A box sliding across two triangles catches on their shared edge, because at the seam the narrow phase finds the edge rather than the face and pushes the box back along it — the classic symptom is a character stumbling on a perfectly flat floor every metre. Each edge is therefore classified once, here, as convex or not, and meshContact.ts uses that to replace an edge normal with the face's.

Properties

NameTypeDescription
positionsreadonlyFloat32Arrayxyz per vertex, in the mesh's own space.
indicesreadonlyUint32ArrayThree vertex indices per triangle.
triangleCountreadonlynumber
planesreadonlyFloat32Arraynx, ny, nz, d per triangle: the outward plane, with d = n · v0.
convexEdgesreadonlyUint8ArrayThree bits per triangle, one per edge: set where the fold at that edge turns away from the front face, or where the edge has no neighbour at all.
More

Edge i runs from local vertex i to (i + 1) % 3. A boundary edge counts as convex because there is nothing on the other side to be caught between; a coplanar neighbour counts as not convex, because a flat seam is exactly the case a contact must never come off.

treereadonlyDynamicTree | nullOver triangle indices, built once — and null for a heightfield, which needs none.
More

A field's candidates are an index range rather than a tree query: the cells a box covers are arithmetic on its own bounds. See heightfieldShape.ts, which is the other producer of this shape and the reason this is nullable.

fieldreadonlyoptionalHeightfieldSet where the triangles are generated from a heightfield rather than stored.
More

The arrays above are then empty and nothing reads them: meshContact.ts and query.ts take their corners, planes and edge classifications from the field instead. Everything else about a mesh — the one-sided rule, the many manifolds, the interior-edge filter — is shared, which is the whole design.