Skip to main content
Two hooks for working with MuJoCo contacts: useContacts for per-frame contact queries, and useContactEvents for enter/exit transitions.

useContacts

Query contacts involving a specific body (or all contacts).

Signature

useContacts(
  bodyName?: string,
  callback?: (contacts: ContactInfo[]) => void
): React.RefObject<ContactInfo[]>

Usage

import { useContacts } from "mujoco-react";

function ContactCounter() {
  const contacts = useContacts("block_1");

  useFrame(() => {
    if (contacts.current.length > 0) {
      console.log(`${contacts.current.length} contacts on block_1`);
    }
  });

  return null;
}

With Callback

const contacts = useContacts("gripper_left", (contacts) => {
  const touchingCube = contacts.some(c =>
    c.geom1Name === "cube" || c.geom2Name === "cube"
  );
  if (touchingCube) console.log("Gripper touching cube!");
});

All Contacts

const allContacts = useContacts(); // No body filter

useContactEvents

Subscribe to contact enter/exit transitions for a body.

Signature

useContactEvents(
  bodyName: string,
  handlers: {
    onEnter?: (info: ContactInfo) => void;
    onExit?: (info: ContactInfo) => void;
  }
): void

Usage

import { useContactEvents } from "mujoco-react";

function CollisionSound() {
  useContactEvents("ball", {
    onEnter: (info) => {
      playSound("bounce", { volume: Math.min(info.depth * 10, 1) });
    },
    onExit: (info) => {
      console.log("Lost contact with", info.geom2Name);
    },
  });

  return null;
}

ContactInfo

interface ContactInfo {
  geom1: number;        // Geom ID of first body
  geom1Name: string;    // Name of first geom
  geom2: number;        // Geom ID of second body
  geom2Name: string;    // Name of second geom
  pos: [number, number, number];  // Contact point
  depth: number;        // Penetration depth
}

Notes

  • Contact data is read from data.contact every physics frame
  • Geom names are cached to avoid repeated WASM string lookups — the cache clears on model change
  • useContactEvents diffs the contact list between frames to detect transitions
  • Contact reads are wrapped in try/catch (can fail with large ncon)
  • For a component-based API, see <ContactListener>