New Our agentic pipeline is live — the agent researches an object before it builds it. Read the post →
RIGYD
Launch app
Comparison

Convex hulls vs triangle mesh colliders

Almost every collision-geometry decision in robotics simulation comes back to one constraint: solvers are fast because they assume convexity. That is why your detailed mesh gets replaced by a hull you did not ask for, and why the exact-geometry option quietly stops working the moment the object starts moving.

Updated 2026-08-31

The short answer

A convex hull is the smallest convex shape enclosing a mesh — no dents, no interior pockets — and it is what physics solvers collide against directly. A triangle mesh collider uses the mesh's actual triangles, concavities included. The hull is cheaper and works on moving bodies; the triangle mesh is exact and, in most engines, is restricted to static geometry. That restriction, not accuracy, is usually what decides the question.

Side-by-side comparison

DimensionConvex hullsConvex approximation, any bodyTriangle mesh collidersExact surface, usually static
What the engine collides againstA convex shell wrapped around the mesh's verticesThe mesh's own triangles, concavities included
Concave features (cups, shelves, handles)Lost — a mug's interior fills inPreserved exactly
Dynamic (non-kinematic) rigid bodiesSupported everywhereGenerally not supported — see the engine rows below
Static and kinematic geometrySupportedSupported — this is its normal use
MuJoCoThe only option: collision is limited to convex geoms, and meshes are replaced by their convex hullsNot available for standard geoms; height fields and SDF plugins are the documented exceptions
PhysX (Isaac Sim, Omniverse)Supported on any actorRejected as a simulation shape on non-kinematic dynamic actors
Bullet (PyBullet)Supported on any bodybtBvhTriangleMeshShape is static-only; btGImpactMeshShape handles moving concave meshes
Authoring in OpenUSDphysics:approximation = "convexHull" or "convexDecomposition"physics:approximation = "none" — the schema default
Authoring in SDF (Gazebo)<mesh optimization="convex_hull"> or "convex_decomposition"The default: no optimization attribute set
Size limitsPhysX caps a cooked convex mesh at 256 vertices and polygons, 64 for GPU-compatible meshesBounded by the mesh itself, so a scanned asset can carry very large triangle counts
Modelling a concave *moving* objectDecompose it into several hulls on one bodyNot the intended path in any of the engines above
Effect on mass and inertiaMuJoCo can compute inertia from the hull (inertia="convex")MuJoCo's inertia="exact" uses the true mesh, but needs a watertight, well-oriented one
Typical useAnything the robot picks up, pushes, or throwsFloors, walls, terrain, fixed fixtures, scanned environments

Why solvers insist on convexity

The convexity requirement is not a limitation anyone chose for its own sake; it is what makes narrow-phase collision fast. Algorithms like GJK/EPA and MPR work on convex sets, where a support function answers "how far does this shape extend in that direction" in one pass, and where two shapes overlap in exactly one connected region. Concave geometry breaks both properties. MuJoCo states the consequence plainly: with the exception of its SDF plugins, collision detection is limited to convex geoms, all primitive types are convex, and meshes supplied by the user "are replaced with their convex hulls", computed by the qhull library. Note the split that follows from this — the mesh you see is not the mesh you collide with. MuJoCo renders your non-convex mesh exactly as authored while simulating its hull, which is why a gripper can visibly pass through the opening of a cup and still report contact.

Static or dynamic is the line that actually decides it

Most write-ups frame this as accuracy versus speed. In practice the engine decides for you, based on whether the object moves. PhysX documents that attaching a triangle mesh, heightfield or plane geometry shape configured as a simulation shape is not supported for non-kinematic dynamic actors — so in Isaac Sim and anything else built on PhysX, a triangle mesh collider is available for the floor and the workcell, and unavailable for the part being picked up. Bullet draws the same line in its source: btBvhTriangleMeshShape is "a static-triangle mesh shape, it can only be used for fixed/non-moving objects", and the header directs anyone needing moving concave meshes to perform convex decomposition or to use btGImpactMeshShape instead. If your object is static, the exact mesh is usually available and usually fine. If it moves, the question answers itself and the real work becomes how to decompose it well.

The exceptions worth knowing

The rule has documented exits, and they matter because they are where the field is moving. MuJoCo treats height fields as a collection of triangular prisms rather than convex geoms, and its SDF plugins step outside convex collision entirely. PhysX supports signed-distance-field colliders that give a dynamic body collision behaviour following its true surface, but with a hard constraint: SDF colliders only work when the GPU solver is used, enabled via the scene's GPU dynamics flag. Bullet's GImpact path handles moving concave meshes on the CPU. None of these makes triangle-soup collision the default choice — MuJoCo's documentation notes that a general-purpose "triangle soup" collider can be installed through a custom callback but explicitly does not recommend it, on the grounds that pre-processing into convex pieces "pays off at runtime and yields both faster and more stable simulation".

The hull leaks into mass properties, not just contact

Collision geometry is usually discussed as a contact question, but in MuJoCo it also feeds inertia. The mesh inertia attribute selects between convex (volume and inertia from the convex hull, uniform density), exact (true values even for non-convex meshes, requiring a watertight, well-oriented mesh), shell (mass on the surface), and legacy. legacy is still the default for backward compatibility, and the documentation is blunt that it "leads to volume overcounting for non-convex meshes" and is not recommended. So a hollow or heavily concave object can end up both colliding as a solid block and weighing more than it should, from a single unset attribute. Throughput pushes the same way: MJX's guidance is to keep convex meshes small, suggesting maxhullvert of 64 or less, roughly 200 vertices or fewer for collisions against primitives, and fewer than 32 vertices for convex-convex pairs.

When to choose each

Convex hulls

Anything that moves under the solver — objects being manipulated, thrown, or pushed — plus any asset destined for GPU-parallel training, where vertex budgets are tight. For concave moving objects, this means several hulls on one body rather than one loose shell.

Triangle mesh colliders

Static geometry where exactness is cheap and valuable: floors, walls, terrain, shelving, fixed fixtures, and scanned environments a robot navigates but never lifts. Also the right call for kinematic bodies you drive directly rather than simulate.

Where Rigyd fits

Rigyd generates collision geometry as part of preparing an asset, emitting convex decompositions for the movable objects and carrying the choice through to both OpenUSD and MJCF so the same object behaves consistently in both. The honest limit: if your scene is a static environment — a scanned warehouse the robot drives through and never touches — you do not need decomposition at all, and a triangle mesh collider straight from the source mesh is the better answer. Rigyd is worth reaching for when objects move, when the same asset has to exist in more than one simulator, or when the number of assets makes hand-tuning hulls impractical. For a handful of hero objects, a careful engineer with the right tools still wins.

FAQ

Frequently asked questions

What is the difference between a convex hull and a triangle mesh collider?

A convex hull is the smallest convex shape that encloses a mesh's vertices — imagine shrink-wrapping the object, so every dent, pocket, and opening fills in. A triangle mesh collider instead uses the mesh's actual triangles, so concave features survive exactly. The hull is an approximation the physics solver can test cheaply and can attach to any body; the triangle mesh is exact but, in most engines, only usable on static or kinematic geometry.

Can I use a triangle mesh collider on a moving object?

Usually not directly. PhysX rejects a triangle mesh simulation shape on non-kinematic dynamic actors, and Bullet's btBvhTriangleMeshShape is documented as static-only. MuJoCo does not offer it at all for standard geoms, since it replaces meshes with their convex hulls. The supported routes for a concave moving object are convex decomposition, Bullet's btGImpactMeshShape, or PhysX's SDF colliders, which require the GPU solver.

Why does my concave object behave like a solid block?

Because the engine is colliding against the convex hull rather than the mesh you can see. This is the usual explanation for a gripper that cannot reach into a cup, an object that will not sit inside a bin, or a handle that cannot be hooked. MuJoCo makes the substitution automatically and still renders the original mesh, so the visual gives no warning. The fix is convex decomposition: split the shape into several convex pieces attached to the same body.

How do I set this in OpenUSD or SDF?

In OpenUSD, UsdPhysicsMeshCollisionAPI exposes physics:approximation, whose allowed values are none, convexHull, convexDecomposition, boundingSphere, boundingCube, and meshSimplification; the default is none, meaning the mesh geometry is used directly as a collider without approximation. In SDF, the <mesh> element takes an optimization attribute accepting convex_hull or convex_decomposition, with an empty default meaning no optimization; when decomposition is enabled, max_convex_hulls defaults to 16.

Is a convex hull the same as convex decomposition?

No, and conflating them causes real problems. A convex hull is one convex shape around the whole object. Convex decomposition splits the object into several convex pieces that together approximate the concave shape, all attached to the same body. A single hull on a mug is nearly useless; a decomposition of the same mug keeps the interior open. Tools such as CoACD exist for exactly this step, and MuJoCo's documentation points users to it.

How many vertices should a convex hull have?

Fewer than you would think, and the engine may cap it regardless. PhysX limits a cooked convex mesh to 256 vertices and polygons, dropping to 64 for a GPU-compatible mesh, with the cooking vertex limit defaulting to 255. For GPU-parallel training with MJX the guidance is tighter still: maxhullvert of 64 or less, roughly 200 vertices or fewer for collisions against primitives, and under 32 vertices for convex-convex pairs. If you are running thousands of environments in parallel, hull complexity is a throughput decision.

Build with Rigyd

Turn a 3D model, image, or text description into validated OpenUSD and MJCF.