Builtin Library
README.md
MeshCollider (component)
Collision against mesh geometry. There are no size fields: the collider is the mesh, scaled by the entity's transform.
The two fits
fit | Shape | Bodies | Concavity |
|---|---|---|---|
"hull" (default) | Convex hull of the mesh's points | Static and dynamic | Filled in, so a doorway becomes a slab |
"exact" | The mesh's own triangles | Static | Followed exactly |
"exact" is for static bodies because a triangle mesh is a surface with no
interior: rapier cannot derive a mass tensor from one, and a dynamic body built
from it would fall through everything it touched. Setting fit = "exact" on a
dynamic body raises an error naming "hull" rather than quietly substituting it.
Choosing the geometry
mesh names the asset to collide against. Left unset, the collider tracks
whatever mesh the entity renders, including a later swap. Set it to collide
against a cheaper proxy than the one on screen, which is the usual move for
detailed scenery where the visual mesh carries far more triangles than collision
needs.
entity.find("rock").component.add("MeshCollider", {}) -- what you see
entity.find("level").component.add("MeshCollider", { fit = "exact" }) -- concave, static
entity.find("rock").component.add("MeshCollider", { mesh = "rock_collision" }) -- cheap proxy
Because mesh is an asset reference, a collision proxy is visible to the asset
graph and survives a rename.
The collider follows the mesh's geometry. renderer.mesh.setVertices or
renderer.mesh.update on the mesh it is built from rebuilds it by the next
physics step, in edit mode and in play, once per changed mesh per frame however
many times the mesh was written, and bodies asleep on it or within its reach
wake. An edit is a new shape rather than a moving one, so it gives what touches
it no velocity; the physics guide states what a rebuild costs.
The collider loads the mesh it names itself, so an entity that draws nothing collides all the same: an invisible wall, a trigger volume shaped like a mesh, a level whose collision is a separate simplified mesh. The engine keeps that mesh loaded for as long as a collider names it.
local wall = entity.spawn("invisibleWall")
wall.component.add("Physics", { kind = "static" })
wall.component.add("MeshCollider", { mesh = "@builtin::meshes.torus", fit = "exact" })
While a collider cannot be built, Physics.bodyState(entity) reads
exists = false with waitingFor naming what it is waiting for, and a wait
that lasts more than two seconds is logged as a warning naming the mesh.
Cost
Built shapes are shared: every collider asking for the same mesh, fit and scale gets the one built shape, so a field of a hundred identical rocks pays for one hull rather than a hundred.
Fields
| Field | Meaning |
|---|---|
mesh | Mesh asset to collide against. Unset tracks the entity's renderable. |
fit | "hull" or "exact". |
isTrigger | Report overlaps without blocking. |
friction | Surface friction. |
restitution | Bounciness. |
rollingResistance | How strongly the surface resists a body rolling on it: a rolling body slows at this many times gravity on level ground, so a ball rolled at v stops after v^2 / (2 * rollingResistance * 9.81). 0 rolls on. |
spinningResistance | The same for a body spinning on the surface about the contact normal. 0 spins on. |
surface | The surface the collider is made of, by name: one Physics.defineSurface defined gives it its friction, restitution and resistances in place of the fields above, and contacts, contact events and ray and shape casts report the name either way. Empty names none. |
oneWay | The direction, in the collider's own frame, bodies pass through it along: a body moving that way goes through, and a body meeting it from the side the direction points to stands on it (a jump-through platform is {0, 1, 0}). Physics.passThrough(entity) drops a standing body through. Unset blocks from every side. |
surfaceVelocity | The velocity, m/s in the collider's own frame, its surface moves at along itself while the collider stays put: friction carries what rests on it (a conveyor belt, a treadmill, an escalator). Write it at runtime to change the speed or direction. Unset holds the surface still. |
contactTag | The tag the world's contact filter reads this collider by: Physics.setContactFilter(tagA, tagB, mode) makes two tags pass through each other ("ignore"), report contacts and push nothing ("sensor") or collide ("solid"). Empty names none. |