Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Heuristic:Google deepmind Mujoco Mesh Quality For Collision

From Leeroopedia
Knowledge Sources
Domains Optimization, Physics_Simulation
Last Updated 2026-02-15 05:00 GMT

Overview

Keep coplanar mesh faces under 20 vertices and decimate complex meshes to avoid collision detection inaccuracy and performance degradation in MJX.

Description

MJX processes convex mesh hulls for collision detection by merging coplanar triangular facets into polygons. When a coplanar face exceeds 20 vertices (`_MAX_HULL_FACE_VERTICES`), the vertices are downsampled by striding, which loses geometric accuracy. Additionally, mesh-mesh and mesh-heightfield collisions do not support margin/gap parameters in MJX, which can cause `NotImplementedError` if the model specifies non-zero margins for these geometry types.

Usage

Use this heuristic when preparing mesh assets for MJX simulation, debugging collision issues, or encountering warnings about coplanar face vertices.

The Insight (Rule of Thumb)

  • Action: Decimate meshes so that no coplanar face has more than 20 vertices after convex hull computation.
  • Value: `_MAX_HULL_FACE_VERTICES = 20` is the hard limit.
  • Trade-off: Simpler meshes lose geometric detail but gain collision accuracy and speed. Downsampled faces (> 20 vertices) lose the most accuracy.
  • Margin/Gap: Set `margin=0` and `gap=0` for mesh and heightfield geoms in MJX. Non-zero values raise `NotImplementedError`.

Reasoning

MJX's collision detection requires fixed-size arrays for JAX compilation (no dynamic shapes). The 20-vertex limit ensures collision polygons fit within pre-allocated buffers. When exceeded, every Nth vertex is sampled (stride = `face.shape[0] // 20 + 1`), creating a coarser polygon that may miss concave features or produce contact artifacts.

For mesh-mesh collisions, margin/gap support would require variable-size broadphase results, which conflicts with JAX's static shape requirement. This is a known limitation documented via `NotImplementedError`.

Code Evidence

Vertex limit and warning from `mjx/mujoco/mjx/_src/mesh.py:38,138-148`:

_MAX_HULL_FACE_VERTICES = 20

# resize faces that exceed max polygon vertices
if face.shape[0] > _MAX_HULL_FACE_VERTICES:
    name = m.names[m.name_meshadr[meshid] :]
    name = name[: name.find(b'\x00')].decode('utf-8')
    warnings.warn(
        f'Mesh "{name}" has a coplanar face with more than '
        f'{_MAX_HULL_FACE_VERTICES} vertices. This may lead to performance '
        'issues and inaccuracies in collision detection. Consider '
        'decimating the mesh.'
    )
every = face.shape[0] // _MAX_HULL_FACE_VERTICES + 1
face = face[::every]

Margin/gap check from `mjx/mujoco/mjx/_src/io.py:347-356`:

# margin/gap not supported for meshes and height fields
no_margin = {mujoco.mjtGeom.mjGEOM_MESH, mujoco.mjtGeom.mjGEOM_HFIELD}
if no_margin.intersection({t1, t2}):
    # ...
    if margin.any():
        t1, t2 = mujoco.mjtGeom(t1), mujoco.mjtGeom(t2)
        raise NotImplementedError(f'({t1}, {t2}) margin/gap not implemented.')

Edge handling for degenerate meshes from `mjx/mujoco/mjx/_src/mesh.py:78-86`:

if len(v) > 2:
    # Meshes can be of poor quality and contain edges adjacent to more than
    # two faces. We take the first two unique face normals.
    v = np.unique(v, axis=0)[:2]
elif len(v) == 1:
    # Some edges are either degenerate or _MAX_HULL_FACE_VERTICES was hit
    # and face vertices were down sampled.
    continue

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment