Collision Detection and Colliders
Overview
RaiSim performs collision detection in two stages: an AABB-based broadphase filters candidate pairs, followed by a pair-specific narrowphase that generates contact points. The narrowphase algorithm depends on the shape pair (analytic tests, SAT, MPR, or GJK/EPA). Pairs rejected by the collision group and mask (see Contact and Collision) and pairs of two static bodies never reach the narrowphase.
The broadphase is selected with contact::BroadphaseSettings::type
(World::setBroadphaseSettings):
contact::BroadphaseType::Sap3Axis(default): sweep and prune on three axes.contact::BroadphaseType::MultiBoxPrune: multi-box pruning on a uniform grid; thembp*fields set the grid bounds and cell size.contact::BroadphaseType::None: no AABB culling; every pair goes to the narrowphase. Use it only for debugging.
Scenes with fewer than 35 collision bodies use an all-pairs AABB test regardless of this setting.
Global contact limits and merging
Per-pair limit:
ContactSettings::maxContactsPerPair(default 8) is clamped to [1, 8] inside the contact detector. Any pair listed below is capped by this limit, even if the narrowphase could generate more points.Merging for mesh/heightmap pairs: contacts produced by triangle-based tests are merged to reduce redundancy. The merge uses a distance threshold proportional to the smaller shape size (about 0.105 * characteristic length; a heightmap counts as infinitely large, so the other shape sets the scale), a normal alignment threshold of about 4 degrees, and a small positional epsilon (~1e-4) to coalesce nearly identical points. Mesh vs mesh pairs only coalesce points closer than 1e-4.
Heightmap manifold reduction: heightmap pairs first collect up to 8 contacts. RaiSim then drops contacts that duplicate another contact or lie between two contacts with nearly identical normals and add no extra depth, and keeps the deepest remaining contacts up to the per-pair limit.
Special caps: some generators have additional caps. Box-cylinder can produce up to 16 contacts internally but is still clamped to the per-pair limit; cylinder-heightmap accepts at most 3 contacts from each terrain triangle.
Shape categories
Primitive: sphere, box, capsule, cylinder, plane. A plane is the
World::addGroundground. Compound children are ordinary primitive bodies and follow the primitive tables. Cones are not supported.ConvexMesh: convex mesh representation (
MeshCollisionMode::CONVEX_HULLor each part of aMeshCollisionMode::CONVEXIFYdecomposition).Mesh: non-convex triangle mesh (
MeshCollisionMode::ORIGINAL_MESH).Heightmap: grid terrain represented by triangles. It can be translated and rotated arbitrarily; contacts are computed in the heightmap’s own frame and mapped back to the world.
Ray: query-only shape used by ray tests.
Pair list (narrowphase algorithms and contact counts)
Pairs are symmetric; a single entry covers both A vs B and B vs A.
Primitive vs Primitive
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Sphere vs Sphere |
Analytic distance check; contact on line between centers. |
1 |
Normal along center line. |
Sphere vs Box |
Closest point on oriented box (analytic). |
1 |
Uses box clamp in local frame. |
Sphere vs Capsule |
Closest point on capsule axis segment, then sphere-sphere contact. |
1 |
Segment endpoints are capsule caps. |
Sphere vs Cylinder |
Analytic finite-cylinder test (side and caps). |
1 |
Handles endcaps and side wall. |
Box vs Box |
SAT on 15 axes (3 + 3 + 9 cross), face/edge clipping. |
Up to 8 |
Edge-edge yields 1; face-face yields a clipped manifold. |
Box vs Capsule |
Segment-box closest approach; fallback to cap sphere-box tests. |
Up to 2 |
1 if segment interior hits; otherwise up to 2 cap contacts. |
Box vs Cylinder |
SAT + clipping (cylinder vs box face/circle); MPR if clipping yields no point. |
Up to 16 (clamped to the per-pair limit) |
Produces 1-2 for edge hits, more for face overlap. |
Capsule vs Capsule |
Segment-segment closest points; parallel overlap uses both ends of the overlapping axis interval. |
Up to 2 |
Parallel, overlapping axes can emit two contacts. |
Capsule vs Cylinder |
MPR (Minkowski Portal Refinement) support mapping. |
1 |
Generic convex-convex contact. |
Cylinder vs Cylinder |
Nearly parallel axes in end-to-end (cap-to-cap) contact: analytic rim sampling; otherwise MPR. |
Up to 8 (cap-to-cap), otherwise 1 |
Side-by-side contact and non-parallel axes use MPR. |
Primitive vs Plane
Plane contacts use the plane’s world-frame normal. The ground plane created by
World::addGround has the normal +Z (world up).
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Sphere vs Plane |
Analytic projection to plane. |
1 |
Contact at the sphere point deepest along the plane normal. |
Box vs Plane |
Projected box corners and face selection. |
Up to 4 |
Chooses deepest corner then up to 3 additional points. |
Capsule vs Plane |
Analytic endcap projection. |
1 |
Uses the lower end of the capsule axis, even when the capsule lies flat. |
Cylinder vs Plane |
Analytic: rim contacts if the axis is parallel to the plane normal or the whole lower rim is below the plane; otherwise two edge points. |
Up to 4 (parallel or fully submerged rim), otherwise up to 2 |
Parallel means the cylinder axis is aligned with the plane normal. |
ConvexMesh vs Primitive/ConvexMesh
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
ConvexMesh vs Sphere/Box/Capsule/Cylinder |
MPR (support mapping). |
1 |
Uses convex support points; no manifold. |
ConvexMesh vs ConvexMesh |
GJK for intersection + EPA for penetration. |
1 |
Contact point from support point along penetration normal. |
Convex (primitive/convex mesh) vs Mesh (triangle mesh)
Mesh pairs use a BVH to find candidate triangles, then test each triangle against the convex shape. Contacts are merged by distance/normal and capped by the per-pair limit.
Sphere, capsule and cylinder queries also reject BVH nodes whose triangles cannot lie within the radius of the center or axis segment. Once the contact set is full, nodes that cannot produce a deeper retained contact are skipped. Box queries reject nodes separated from a padded oriented box. The padding includes the triangle tests’ tolerances and the reach of the triangle-plane test, including for thin boxes. These checks reduce triangle work for dense meshes passing close to a primitive while preserving candidate order and contact merging.
The box-triangle path also checks separation before its closest-feature scan and GJK/EPA fallback. Triangles proven farther than the 0.2 mm separation margin no longer reach the fallback, which could previously report a false penetration for such a separated pair. This margin is a rejection tolerance; it does not add a collision skin to the box.
These checks apply automatically to non-convex mesh collision bodies, including articulated self-collision. Cylinder-mesh contacts still use the axis segment and radius as described below. See Benchmark for the dense-mesh proximity and G1 hand self-collision workloads.
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Sphere vs Mesh |
Closest point on triangle (distance check). |
Up to 8 |
One contact per penetrating triangle, then merged. |
Capsule vs Mesh |
Segment-triangle closest points. |
Up to 8 |
Uses capsule axis segment and radius. |
Cylinder vs Mesh |
Segment-triangle closest points. |
Up to 8 |
Uses cylinder axis segment and radius (the cylinder is treated like a capsule). |
Box vs Mesh |
Closest-feature tests + triangle plane test; GJK/EPA fallback. |
Up to 8 |
Box-triangle contact uses multiple feature checks. |
ConvexMesh vs Mesh |
GJK/EPA between convex mesh and triangle. |
Up to 8 |
One contact per triangle candidate, then merged. |
Mesh vs Mesh
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Mesh vs Mesh |
BVH traversal + GJK/EPA on triangle pairs. |
Up to 8 |
Only nearly coincident points (closer than 1e-4) are merged. |
Heightmap interactions
Heightmap contacts operate on grid triangles. A rotated heightmap is handled in its own frame: the other body’s pose (and, for swept CCD, its motion) is expressed in the unrotated map’s frame, the contacts are found there and their points and normals are rotated back into the world. When the footprint of a box, capsule, cylinder, or convex mesh lies over an exactly level patch of the heightmap, RaiSim reuses the corresponding plane routine (see the plane tables). All heightmap pairs go through the manifold reduction described above.
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Sphere vs Heightmap |
Closest points on the terrain triangles within reach of the sphere. |
Up to 8 (1 on a planar patch) |
Keeps the deepest contact and adds contacts from surfaces whose normals differ from it by more than 20 degrees (for example, walls in a corner). |
Box vs Heightmap |
Per-cell plane-box contacts. |
Up to 8 |
Contacts merged by distance/normal. |
Capsule vs Heightmap |
Per-cell plane-capsule contacts. |
Up to 8 |
Contacts merged by distance/normal. |
Cylinder vs Heightmap |
Per-cell plane-cylinder contacts. |
Up to 8 |
At most 3 contacts per terrain triangle; merged by distance/normal. |
ConvexMesh vs Heightmap |
Vertex vs triangle plane tests. |
Up to 8 |
Uses convex mesh vertices to generate contacts. |
Mesh vs Heightmap |
BVH traversal + GJK intersection test on mesh triangle vs heightmap triangle; contact from triangle-triangle closest points. |
Up to 8 |
Normal from the terrain triangle; a recovery pass handles mesh vertices that are already below the terrain. |
Plane vs Mesh / ConvexMesh
Pair |
Narrowphase algorithm |
Contact points |
Notes |
|---|---|---|---|
Plane vs Mesh |
Penetrating vertices/edges binned by angle around centroid. |
Up to 8 |
Produces a coarse contact ring. |
Plane vs ConvexMesh |
Vertex binning around centroid. |
Up to 3 |
Uses deepest vertices below plane. |
Ray tests (query-only)
Ray pairs are used by ray tests and return a single hit (if any) with zero penetration.
Pair |
Algorithm |
Contact points |
Notes |
|---|---|---|---|
Ray vs Sphere |
Analytic ray-sphere intersection. |
1 |
Returns closest hit along ray. |
Ray vs Box |
Slab intersection in box local frame. |
1 |
Normal from hit face. |
Ray vs Capsule |
Analytic ray vs capsule (cylinder + caps). |
1 |
Handles inside-origin cases. |
Ray vs Cylinder |
Analytic ray vs finite cylinder. |
1 |
Includes caps. |
Ray vs Plane |
Line-plane intersection. |
1 |
Uses the plane normal, flipped for back-side hits. |
Ray vs Mesh/ConvexMesh |
BVH query + ray-triangle test. |
1 |
Closest triangle hit. |
Ray vs Heightmap |
Grid traversal / height test. |
1 |
Returns first hit if any. |
Ray vs Ray |
Closest segment-segment distance. |
1 |
Contact if distance <= 1e-6. |
Swept CCD
Swept continuous collision detection is opt-in through contact::ContactSettings. It is intended
for fast bodies that may pass through thin static terrain within one time step. It covers dynamic
sphere, capsule, box, and cylinder collision bodies against static ground planes and heightmaps;
other pairs use only discrete detection.
auto settings = world.getContactSettings();
settings.sweptCcdEnabled = true; // default: false
settings.sweptCcdMinSpeed = 8.0; // default: 0.0 (sweep every moving body)
settings.sweptCcdSpeculativeMargin = 1.0e-4; // default: 1e-4 m
world.setContactSettings(settings);
When enabled, RaiSim adds speculative contacts for bodies whose speed (linear speed plus angular
speed times the sweep radius) is at least sweptCcdMinSpeed.
Use World::setCollisionCountersEnabled(true) and getLastCollisionCounters() to inspect
sweptCcdCandidates and sweptCcdContacts in benchmark or diagnostic code. Deformable particles
do not use swept CCD: their contacts include speculative ones for every body a particle can reach within
the step (see Deformable Objects).
Collision counters
World::CollisionCounters exposes optional diagnostic counters, reset at every contact detection:
deformable particle contact statistics (deformableParticleCandidates,
deformableParticleAabbTests, deformableParticleContacts, deformableParticleGridCells)
and swept CCD counts (sweptCcdCandidates, sweptCcdContacts). Counters are disabled by
default to keep the normal simulation path cheap.
world.setCollisionCountersEnabled(true);
world.integrate();
const auto& counters = world.getLastCollisionCounters();
Unsupported or no-op pairs
Plane vs Plane, Plane vs Heightmap, Heightmap vs Heightmap: no contact generation (pairs of static bodies are never tested).
Cone: not supported; creating a cone is a fatal error.
Ray vs Plane/Primitive/Mesh/Heightmap: query-only (not solved as rigid contacts).