{
"cells": [
{
"cell_type": "markdown",
"id": "f66f7560",
"metadata": {},
"source": [
"[](https://colab.research.google.com/github/tayalmanan28/MuJoCo-Tutorial/blob/main/tutorial/05_contact.ipynb)\n",
"\n",
"# Tutorial 5: Contacts & Collisions\n",
"\n",
"Understand how MuJoCo handles contacts, read contact forces, and configure contact parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1cf52385",
"metadata": {},
"outputs": [],
"source": [
"!pip install -q mujoco mediapy matplotlib"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "048fbb07",
"metadata": {},
"outputs": [],
"source": [
"import mujoco\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import mediapy as media"
]
},
{
"cell_type": "markdown",
"id": "4433d861",
"metadata": {},
"source": [
"## Contact Detection\n",
"\n",
"MuJoCo detects contacts automatically between all geom pairs.\n",
"Active contacts are stored in `data.contact[:data.ncon]`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f9a56f4",
"metadata": {},
"outputs": [],
"source": [
"xml = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"model = mujoco.MjModel.from_xml_string(xml)\n",
"data = mujoco.MjData(model)\n",
"\n",
"# Simulate until contact\n",
"contact_times, contact_forces, heights = [], [], []\n",
"while data.time < 2.0:\n",
" mujoco.mj_step(model, data)\n",
" heights.append(data.qpos[2])\n",
" \n",
" # Read contact forces\n",
" total_force = 0.0\n",
" for i in range(data.ncon):\n",
" force = np.zeros(6)\n",
" mujoco.mj_contactForce(model, data, i, force)\n",
" total_force += np.linalg.norm(force[:3]) # normal + friction\n",
" \n",
" contact_times.append(data.time)\n",
" contact_forces.append(total_force)\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)\n",
"ax1.plot(contact_times, heights); ax1.set_ylabel('Height (m)'); ax1.grid(True)\n",
"ax2.plot(contact_times, contact_forces, color='red'); ax2.set_ylabel('Contact Force (N)')\n",
"ax2.set_xlabel('Time (s)'); ax2.grid(True)\n",
"plt.suptitle('Box falling: height and contact force'); plt.tight_layout(); plt.show()"
]
},
{
"cell_type": "markdown",
"id": "ba686441",
"metadata": {},
"source": [
"## Contact Parameters\n",
"\n",
"Control contact behavior with `solref` (stiffness/damping) and `friction`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4d4bfb0",
"metadata": {},
"outputs": [],
"source": [
"# Compare different friction values\n",
"xml_template = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"plt.figure(figsize=(10, 4))\n",
"for friction in [0.1, 0.5, 1.0, 2.0]:\n",
" m = mujoco.MjModel.from_xml_string(xml_template.format(friction=friction))\n",
" d = mujoco.MjData(m)\n",
" positions = []\n",
" while d.time < 2.0:\n",
" mujoco.mj_step(m, d)\n",
" positions.append(d.qpos[0]) # x position\n",
" plt.plot(np.linspace(0, 2, len(positions)), positions, label=f'friction={friction}')\n",
"\n",
"plt.xlabel('Time (s)'); plt.ylabel('X position (m)')\n",
"plt.title('Effect of friction on sliding'); plt.legend(); plt.grid(True); plt.show()"
]
},
{
"cell_type": "markdown",
"id": "3b815e57",
"metadata": {},
"source": [
"## Collision Filtering\n",
"\n",
"Use `contype` and `conaffinity` to control which geoms collide:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a97ea0a",
"metadata": {},
"outputs": [],
"source": [
"xml_filter = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"model_f = mujoco.MjModel.from_xml_string(xml_filter)\n",
"data_f = mujoco.MjData(model_f)\n",
"renderer = mujoco.Renderer(model_f, height=300, width=400)\n",
"\n",
"frames = []\n",
"while data_f.time < 2.0:\n",
" mujoco.mj_step(model_f, data_f)\n",
" if len(frames) < data_f.time * 30:\n",
" renderer.update_scene(data_f)\n",
" frames.append(renderer.render().copy())\n",
"\n",
"renderer.close()\n",
"media.show_video(frames, fps=30)"
]
},
{
"cell_type": "markdown",
"id": "ac94a9ce",
"metadata": {},
"source": [
"The red sphere collides with the floor; the blue sphere passes through it (different collision group)."
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}