ROB-360 · Lesson 6 of 12 · 29 min

Building simulation worlds and robot models

A simulator is only as good as the models fed into it. A robot model with the wrong mass or inertia will accelerate and tip differently from the real machine; a lidar model without noise will make localization look perfect; a warehouse world without reflective shrink-wrap and moving forklifts will never expose the failures that happen on site. This lesson covers how robots are described for simulation and visualization (URDF, Xacro and SDF), how to get mass and inertia right, how collision and visual geometry differ, how sensors and their noise are modelled and how to build worlds and scenarios that test what matters.

Describing a robot: URDF and Xacro

The Unified Robot Description Format (URDF) is the standard way to describe a robot in ROS. It is an XML file listing links (rigid bodies) and joints (connections between links). Each link can have visual geometry for display, collision geometry for contact computation and inertial properties: mass, centre of mass and inertia tensor. Each joint has a type (fixed, revolute, continuous, prismatic, floating or planar), a parent and child link, an origin transform, an axis and limits on position, velocity and effort. Joints form a tree, which tf2 and robot_state_publisher use to compute frame transforms from joint states.

Handwritten URDF quickly becomes long and repetitive: a robot with four identical wheels repeats the same block four times. Xacro (XML macros) adds variables, mathematical expressions, macros and file inclusion, so that a wheel is defined once and instantiated four times with different positions. Xacro files are expanded into plain URDF at build or launch time. Keeping dimensions as named properties in one place also helps keep the model consistent with the mechanical design.

A Xacro fragment defining a wheel once and instantiating it twice (base link omitted)
<?xml version="1.0"?>
<robot name="delivery_base" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <xacro:property name="wheel_r" value="0.08"/>     <!-- m -->
  <xacro:property name="wheel_w" value="0.04"/>
  <xacro:property name="track" value="0.42"/>
  <xacro:macro name="wheel" params="side y">
    <link name="${side}_wheel">
      <visual><geometry><cylinder radius="${wheel_r}" length="${wheel_w}"/></geometry></visual>
      <collision><geometry><cylinder radius="${wheel_r}" length="${wheel_w}"/></geometry></collision>
      <inertial><mass value="1.2"/>
        <inertia ixx="0.00208" iyy="0.00208" izz="0.00384" ixy="0" ixz="0" iyz="0"/></inertial>
    </link>
    <joint name="${side}_wheel_joint" type="continuous">
      <parent link="base_link"/><child link="${side}_wheel"/>
      <origin xyz="0 ${y} ${wheel_r}" rpy="${-pi/2} 0 0"/><axis xyz="0 0 1"/>
    </joint>
  </xacro:macro>
  <xacro:wheel side="left"  y="${track/2}"/>
  <xacro:wheel side="right" y="${-track/2}"/>
</robot>
A tree diagram of a delivery robot model. The root box is base_footprint, connected by a fixed joint to base_link. From base_link, branches lead to left_wheel and right_wheel through continuous joints, to caster through a fixed joint, to lidar_link through a fixed joint and to camera_link through a fixed joint. Camera_link has a child camera_optical_frame through a fixed joint. Each joint is labelled with its type.
The link and joint tree of a small delivery robot as described in URDF

SDF and simulator-specific descriptions

URDF was designed for describing a single robot for ROS tools, and it has limits: it cannot describe closed kinematic loops, worlds with many models, lights, terrain or detailed physics properties. The Simulation Description Format (SDF), used natively by Gazebo, covers all of these. In practice teams keep the URDF (generated from Xacro) as the single source of truth for the robot, and Gazebo converts it to SDF when the robot is spawned, with simulator-specific details such as friction coefficients, sensor plugins and the ros2_control plugin added through extension tags. Worlds, buildings and obstacles are written directly in SDF. Other simulators use their own formats, such as MJCF for MuJoCo and USD for Isaac Sim, with importers for URDF.

Mass, centre of mass and inertia

Wrong inertial values are one of the most common causes of unrealistic simulations: robots that tip over too easily, arms that oscillate, wheels that spin up instantly. Every link needs a positive mass and a physically consistent inertia tensor, with principal moments that satisfy the triangle inequality (each at most the sum of the other two). The best source is the CAD model, which can export mass properties from material densities. For simple shapes, standard formulas give good approximations.

Computing inertia tensors for a chassis and a wheel, checking validity and applying the parallel axis theorem
# Inertia of simple shapes about their CoM (kg m^2)
def box(m, x, y, z):
    return (m * (y*y + z*z) / 12, m * (x*x + z*z) / 12, m * (x*x + y*y) / 12)
def cylinder_z(m, r, h):          # axis along z
    ixx = m * (3*r*r + h*h) / 12
    return (ixx, ixx, m * r * r / 2)
def check(I):
    a, b, c = I
    return a > 0 and b > 0 and c > 0 and a <= b + c and b <= a + c and c <= a + b
chassis = box(18.0, 0.60, 0.45, 0.20)
wheel = cylinder_z(1.2, 0.08, 0.04)
print("chassis ixx, iyy, izz = %.4f, %.4f, %.4f  valid=%s" % (*chassis, check(chassis)))
print("wheel   ixx, iyy, izz = %.5f, %.5f, %.5f  valid=%s" % (*wheel, check(wheel)))
bad = (0.01, 0.01, 0.05)          # a typo: izz too large for the others
print("typo tensor valid =", check(bad))
# parallel axis theorem: wheel spinning about the robot's vertical axis, offset 0.21 m sideways
d = 0.21
print("wheel izz about robot vertical axis = %.4f" % (wheel[0] + 1.2 * d * d))
Output
chassis ixx, iyy, izz = 0.3638, 0.6000, 0.8438  valid=True
wheel   ixx, iyy, izz = 0.00208, 0.00208, 0.00384  valid=True
typo tensor valid = False
wheel izz about robot vertical axis = 0.0550

Inertia is expressed in the link's own frame. In the Xacro listing, the joint's rotation of −π/2 about x turns the wheel link so that its z axis points sideways, along the robot's y axis; the cylinder therefore stays aligned with the link's z axis, and izz carries the larger value m r²/2 while ixx and iyy take the other moment. Mixing up the link frame and the robot frame is a classic error that gives wheels the wrong spin inertia. Tools such as the Gazebo inertia visualization, which draws an equivalent box for each link, make such mistakes easy to spot.

Worked problem

Will the robot tip when it brakes?

A simulated delivery robot has its centre of mass 0.45 m above the floor and its front wheels 0.25 m ahead of the centre of mass. What braking deceleration tips it forward? What if a modelling error places the centre of mass at 0.30 m?

Show solution
  1. Tipping begins when the inertial force moment exceeds the weight moment about the front contact: \( m a h > m g d \), so \( a > g d / h \).
  2. With h = 0.45 m: \( a > 9.81 \times 0.25 / 0.45 \approx 5.5 \) m/s².
  3. With the erroneous h = 0.30 m: \( a > 9.81 \times 0.25 / 0.30 \approx 8.2 \) m/s².

Answer: The real robot tips above about 5.5 m/s², but the mismodelled one tolerates 8.2 m/s², so a simulated emergency-stop test would wrongly pass. Centre-of-mass height should come from CAD or a measurement, not a guess.

Collision geometry versus visual geometry

Visual geometry is for appearance and can use detailed meshes. Collision geometry is used by the physics engine and should be as simple as possible: boxes, cylinders, spheres and capsules, or convex hulls of meshes, because collision checking cost grows with complexity and concave meshes are poorly supported by many solvers. A wheel modelled as a detailed tyre mesh may bounce and jitter; modelled as a cylinder or sphere it rolls smoothly. Simplified collision shapes should still be slightly conservative where it matters for safety, such as the outline of the robot body near people.

Typical visual and collision geometry choices
ElementVisual geometryCollision geometry
ChassisDetailed CAD mesh with filletsOne or two boxes
WheelTyre mesh with treadCylinder (or sphere for casters)
LidarBranded housing meshShort cylinder
Arm linkSmooth cast shapeCapsule or convex hull
Shelving in the worldTextured meshBoxes for uprights and shelves

Sensor models and noise

Simulated sensors in Gazebo are added through sensor elements and plugins: cameras, depth cameras, gpu_lidar, IMU, contact, force-torque, GPS (navsat), magnetometer, altimeter and others. Each has parameters for rate, resolution, field of view and range, and most support noise models, typically additive Gaussian noise with a mean and standard deviation, sometimes with bias and bias drift. Noise-free sensors are a trap: algorithms tuned on perfect data often fail on real data, and some failure modes, such as a localization filter becoming overconfident, only appear with realistic noise.

Good sensor models go beyond Gaussian noise where it matters: lidar dropouts on dark or shiny surfaces, multipath and range spikes, rolling-shutter and motion blur in cameras, IMU bias that changes with temperature, GPS outages near buildings and wheel encoder slip. The parameters should come from datasheets and, better, from measurements of the actual sensors, the same characterization used in ROB-220 and ROB-310.

A lidar range model with noise, dropouts and spurious short returns (parameters illustrative)
import random, math
# A simple lidar range model: Gaussian noise, dropouts on dark surfaces, occasional spikes
rnd = random.Random(42)
def lidar_range(true_r, dark=False, sigma=0.015, p_drop=0.02, p_spike=0.005, r_max=12.0):
    if dark: p_drop = 0.25                 # dark material returns fail more often
    u = rnd.random()
    if u < p_drop: return float("inf")     # no return
    if u < p_drop + p_spike: return rnd.uniform(0.1, true_r)   # spurious short return
    return min(r_max, rnd.gauss(true_r, sigma))
for dark in (False, True):
    rs = [lidar_range(4.0, dark) for _ in range(5000)]
    valid = [r for r in rs if math.isfinite(r)]
    mean = sum(valid) / len(valid)
    sd = (sum((r - mean) ** 2 for r in valid) / len(valid)) ** 0.5
    print(f"dark={dark}: valid {len(valid) / len(rs):.1%}, mean {mean:.3f} m, std {sd:.3f} m")
Output
dark=False: valid 97.8%, mean 3.993 m, std 0.130 m
dark=True: valid 75.0%, mean 3.984 m, std 0.203 m

The standard deviation of the valid returns is much larger than the 1.5 cm Gaussian noise because the rare spurious short returns are counted in it. This is exactly the kind of outlier behaviour that robust estimators and gating (ROB-310) must handle, and a simulation that omits it gives a false sense of security.

Worked problem

Matching a noise parameter to data

A real lidar is placed 3.000 m from a flat white wall and 1000 readings are logged. Their standard deviation is 0.012 m and their mean 3.006 m. What Gaussian noise parameters should the simulated sensor use?

Show solution
  1. Bias: \( 3.006 - 3.000 = 0.006 \) m.
  2. Standard deviation: 0.012 m.
  3. Repeat at several ranges and surfaces; if the deviation grows with range, model it as a function of range, and add dropout rates for dark surfaces measured the same way.

Answer: Mean 0.006 m and standard deviation 0.012 m at 3 m, refined with measurements at other ranges and materials. Measured parameters are far better than datasheet typical values.

Building worlds and scenarios

A world is the environment: buildings, floors, furniture, lighting, other agents and weather. Worlds can be modelled from floor plans, built from asset libraries such as Gazebo Fuel, or generated procedurally to create many variations. A scenario is a world plus a script of what happens: start pose, goal, people walking across, a pallet appearing in an aisle, the network dropping. Scenario lists come directly from the use cases and exception flows of lesson 2, so that each requirement has simulated tests that exercise it.

Moving agents make scenarios much more valuable. Gazebo supports actors with scripted trajectories and animations; pedestrian simulation libraries add crowd behaviour. Parameterized scenarios let one description generate many test cases: a person crossing at a random point, speed and angle within stated ranges. Lesson 8 extends this idea to domain randomization, where variation is used deliberately to make software robust to the gap between simulation and reality.

Worked problem

How many scenario variations?

A crossing scenario has four parameters: person speed (3 values), crossing angle (4 values), robot speed (3 values) and lighting (2 values). How many combinations are there in a full grid, and how many runs does a pairwise design need at minimum?

Show solution
  1. Full grid: \( 3 \times 4 \times 3 \times 2 = 72 \) runs.
  2. A pairwise (all-pairs) design covers every pair of parameter values at least once; its minimum size is at least the product of the two largest factor sizes, \( 4 \times 3 = 12 \).
  3. Pairwise tools usually find designs close to that bound, often 12 to 14 runs here.

Answer: 72 runs for the full grid against about 12 to 14 for pairwise coverage. Pairwise designs are a cheap way to catch most interaction bugs before running the full grid or random sampling.

Common mistakes

  • MistakeLeaving default or zero inertia on links

    InsteadUse CAD mass properties or shape formulas, and check the triangle inequality.

  • MistakeUsing detailed visual meshes as collision geometry

    InsteadUse simple primitives or convex hulls for collisions.

  • MistakeSimulating perfect, noise-free sensors

    InsteadAdd measured noise, bias, dropouts and outliers.

  • MistakeKeeping separate, hand-edited URDF and SDF copies of the robot

    InsteadGenerate everything from one Xacro source and add simulator extensions.

  • MistakeTesting only in an empty world

    InsteadBuild scenarios from use cases and exception flows, with moving agents and variations.

Summary and key terms

  • URDF describes a robot as a tree of links and joints with visual, collision and inertial properties; Xacro adds macros and properties.
  • SDF describes complete worlds and simulator details; teams keep one Xacro source and add simulator extensions.
  • Mass, centre of mass and inertia must be realistic and consistent, ideally from CAD, or dynamics such as tipping will be wrong.
  • Collision geometry should be simple and conservative where safety matters; visual geometry can be detailed.
  • Sensor models need measured noise, bias, dropouts and outliers, not perfect data.
  • Worlds and scenarios should come from use cases and exception flows and include moving agents.
  • Parameterized scenarios and designs such as pairwise testing generate many useful cases cheaply.
URDF
The Unified Robot Description Format, an XML description of links and joints.
Xacro
An XML macro language that generates URDF from reusable, parameterized pieces.
SDF
The Simulation Description Format used by Gazebo for robots and worlds.
Link
A rigid body in a robot description.
Joint
A connection between two links with a type, axis and limits.
Inertia tensor
A 3 × 3 matrix describing a body's resistance to rotational acceleration.
Collision geometry
Simplified shapes used by the physics engine for contact.
Sensor plugin
Simulator code that generates sensor data and publishes it.
Actor
A scripted moving agent, such as a walking person, in a simulated world.
Pairwise testing
A test design that covers every pair of parameter values at least once.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. What three kinds of property can a URDF link have?

    Show answer

    Visual, collision and inertial.

  2. Name four URDF joint types.

    Show answer

    For example fixed, revolute, continuous and prismatic.

  3. Why use Xacro?

    Show answer

    To avoid repetition with macros, properties and expressions.

  4. What can SDF describe that URDF cannot?

    Show answer

    Worlds, lights, closed loops and detailed physics settings.

  5. What is the inertia of a solid cylinder about its axis?

    Show answer

    \( m r^2 / 2 \).

  6. What inequality must principal moments satisfy?

    Show answer

    Each must be at most the sum of the other two.

  7. Why simplify collision geometry?

    Show answer

    Collision checking is faster and more stable with simple convex shapes.

  8. Name two sensor effects beyond Gaussian noise.

    Show answer

    For example dropouts and spurious short returns.

  9. Where should scenario lists come from?

    Show answer

    From use cases and exception flows in the requirements.

  10. How many runs does a full grid of 2 × 3 × 5 values need?

    Show answer

    30.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz