Note: This is part of a series where I follow along with the ROS 2 Humble and Gazebo Fortress tutorials and demos. This is not original content.
Let’s take a look at how we can build a Gazebo world from scratch. Hopefully you followed along with Part 1, Part 2, and Part 3 prior to starting this tutorial, though Part 1 is optional.
Let’s do things a little different this time. We’ll start with a complete world file (which you should save as world.sdf
) and then work backwards from there.
A completed world Link to heading
Here’s a completed, (mostly) empty world. I say mostly empty because a can of Coke should appear at the center of the world when you run Gazebo.
<?xml version="1.0"?>
<sdf version="1.8">
<world name="world_demo">
<physics name="1ms" type="ignored">
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
</physics>
<plugin
filename="libignition-gazebo-physics-system.so"
name="ignition::gazebo::systems::Physics">
</plugin>
<plugin
filename="libignition-gazebo-user-commands-system.so"
name="ignition::gazebo::systems::UserCommands">
</plugin>
<plugin
filename="libignition-gazebo-scene-broadcaster-system.so"
name="ignition::gazebo::systems::SceneBroadcaster">
</plugin>
<gui fullscreen="0">
<!-- 3D scene -->
<plugin filename="MinimalScene" name="3D View">
<gz-gui>
<title>3D View</title>
<property type="bool" key="showTitleBar">false</property>
<property type="string" key="state">docked</property>
</gz-gui>
<engine>ogre2</engine>
<scene>scene</scene>
<ambient_light>0.4 0.4 0.4</ambient_light>
<background_color>0.8 0.8 0.8</background_color>
<camera_pose>-6 0 6 0 0.5 0</camera_pose>
<camera_clip>
<near>0.25</near>
<far>25000</far>
</camera_clip>
</plugin>
<plugin filename="GzSceneManager" name="Scene Manager">
<gz-gui>
<property key="resizable" type="bool">false</property>
<property key="width" type="double">5</property>
<property key="height" type="double">5</property>
<property key="state" type="string">docked</property>
<property key="showTitleBar" type="bool">false</property>
</gz-gui>
</plugin>
<!-- World control -->
<plugin filename="WorldControl" name="World control">
<ignition-gui>
<title>World control</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="resizable">false</property>
<property type="double" key="height">72</property>
<property type="double" key="width">121</property>
<property type="double" key="z">1</property>
<property type="string" key="state">docked</property>
<anchors target="3D View">
<line own="left" target="left" />
<line own="bottom" target="bottom" />
</anchors>
</ignition-gui>
<play_pause>true</play_pause>
<step>true</step>
<start_paused>true</start_paused>
<service>/world/world_demo/control</service>
<stats_topic>/world/world_demo/stats</stats_topic>
</plugin>
<!-- World statistics -->
<plugin filename="WorldStats" name="World stats">
<ignition-gui>
<title>World stats</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="resizable">false</property>
<property type="double" key="height">110</property>
<property type="double" key="width">290</property>
<property type="double" key="z">1</property>
<property type="string" key="state">docked</property>
<anchors target="3D View">
<line own="right" target="right" />
<line own="bottom" target="bottom" />
</anchors>
</ignition-gui>
<sim_time>true</sim_time>
<real_time>true</real_time>
<real_time_factor>true</real_time_factor>
<iterations>true</iterations>
<topic>/world/world_demo/stats</topic>
</plugin>
<!-- Entity tree -->
<plugin filename="EntityTree" name="Entity tree">
</plugin>
</gui>
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
<constant>0.9</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
<direction>-0.5 0.1 -0.9</direction>
</light>
<include>
<uri>
https://fuel.gazebosim.org/1.0/OpenRobotics/models/Coke
</uri>
</include>
</world>
</sdf>
Run Gazebo to have a look at your world:
ign gazebo world.sdf
See the can of coke? It’s a little small:
Notice how different the GUI looks now. That’s because we’ve manually defined which GUI components we want to load, inside the <gui></gui>
tags in world.sdf
.
Breaking it down Link to heading
Let’s step through the code bit-by-bit and examine what each piece is doing.
High-level view Link to heading
<?xml version="1.0"?>
<sdf version="1.8">
<world name="car_world">
<!-- Your code here -->
</world>
</sdf>
Our very first line tells us what XML schema we are using (1.0
here) and which version of SDF (1.8
).
The opening and closing <sdf></sdf>
tags contain everything. In this case, that means our <world></world>
which is going to contain all of our plugins, world physics settings, light sources, models, and so on.
At the start of our <world>
we define properties of simulator:
<physics name="1ms" type="ignored">
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
</physics>
The max_step_size
controls the maximum amount of time that is allowed to pass between interactions of the world and the systems in it (such as part of our robot). 0.001
corresponds to an interval of 1ms, so we have given our physics
tag a name="1ms"
to reflect this fact.
The real_time_factor
is the ratio of simulated time to real time. Because we have set it to 1.0
, our simulated time is 1:1
with real time. We could set higher or lower values to make the simulation faster or slower than real time, respectively.
Note how the type
of the <physics>
tag is set to "ignored"
. In the future we can use this property to choose a physics engine (such as Ode, Bullet, Simbody, or Dart). However, this tag is not yet supported which is why we have set it to "ignored"
.
Next, we define a few plugins that will help us manage our world and simulate its physics:
<plugin
filename="libignition-gazebo-physics-system.so"
name="ignition::gazebo::systems::Physics">
</plugin>
<plugin
filename="libignition-gazebo-user-commands-system.so"
name="ignition::gazebo::systems::UserCommands">
</plugin>
<plugin
filename="libignition-gazebo-scene-broadcaster-system.so"
name="ignition::gazebo::systems::SceneBroadcaster">
</plugin>
Physics
simulates the dynamics of the worldUserCommands
is used to create, move, and delete models (and handles many other user commands also)SceneBroadcaster
displays our world (so we can see what’s going on in the simulator)
GUI stuff Link to heading
Inside <world>
we can include a set of <gui></gui>
tags that allow us to define how the scene is rendered, what should be displayed, and so on. We can also control whether gazebo will start in full screen mode or not:
<gui fullscreen="0">
...
...
</gui>
Here, we have chosen not to start in full screen mode.
We can now define a set of GUI plugins for generating our 3D scene view. We need two plugins for this: MinimalScene
and GzSceneManager
:
<!-- 3D scene -->
<plugin filename="MinimalScene" name="3D View">
<gz-gui>
<title>3D View</title>
<property type="bool" key="showTitleBar">false</property>
<property type="string" key="state">docked</property>
</gz-gui>
<engine>ogre2</engine>
<scene>scene</scene>
<ambient_light>0.4 0.4 0.4</ambient_light>
<background_color>0.8 0.8 0.8</background_color>
<camera_pose>-6 0 6 0 0.5 0</camera_pose>
<camera_clip>
<near>0.25</near>
<far>25000</far>
</camera_clip>
</plugin>
<plugin filename="GzSceneManager" name="Scene Manager">
<gz-gui>
<property key="resizable" type="bool">false</property>
<property key="width" type="double">5</property>
<property key="height" type="double">5</property>
<property key="state" type="string">docked</property>
<property key="showTitleBar" type="bool">false</property>
</gz-gui>
</plugin>
Previous versions of Gazebo used a GzScene3D
plugin for this, but Fortress (and newer versions of Gazebo) split this job across two plugins, MinimalScene
and GzSceneManager
. Notice how we can set GUI properties inside the <gz-gui></gz-gui>
tag. Properties include things like whether a window should have a title bar:
<gz-gui>
<title>3D View</title>
<property type="bool" key="showTitleBar">false</property>
<property type="string" key="state">docked</property>
</gz-gui>
<title>
sets a window titleshowTitleBar
toggles whether that title is displayed in the GUI- The
state
property determines if a given Gazebo window is docked or free-floating (by setting it to eitherfloating
ordocked
)
We can also set which rendering engine is used to render the scene, and can set ambient light and camera pose properties to control how the scene will appear when we first open Gazebo:
<engine>ogre2</engine>
<scene>scene</scene>
<ambient_light>0.4 0.4 0.4</ambient_light>
<background_color>0.8 0.8 0.8</background_color>
<camera_pose>-6 0 6 0 0.5 0</camera_pose>
<camera_clip>
<near>0.25</near>
<far>25000</far>
</camera_clip>
Note: The 6 values for camera pose are [X, Y, Z, Roll, Pitch, Yaw]
We also include the WorldControl
plugin, which lets us do things like set which topic work stats like simulation time will be published to, and allows us to insert play/pause buttons:
<!-- World control -->
<plugin filename="WorldControl" name="World control">
<ignition-gui>
<title>World control</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="resizable">false</property>
<property type="double" key="height">72</property>
<property type="double" key="width">121</property>
<property type="double" key="z">1</property>
<property type="string" key="state">docked</property>
<anchors target="3D View">
<line own="left" target="left" />
<line own="bottom" target="bottom" />
</anchors>
</ignition-gui>
<play_pause>true</play_pause>
<step>true</step>
<start_paused>true</start_paused>
<service>/world/world_demo/control</service>
<stats_topic>/world/world_demo/stats</stats_topic>
</plugin>
Note that in the official tutorial the state is floating
. I have changed all my components to docked
because the Play/pause buttons did not work for me otherwise.
Next we’ve got WorldStats
, which lets us define what simulation statistics to output, and which topic to output them to:
<!-- World statistics -->
<plugin filename="WorldStats" name="World stats">
<ignition-gui>
<title>World stats</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="resizable">false</property>
<property type="double" key="height">110</property>
<property type="double" key="width">290</property>
<property type="double" key="z">1</property>
<property type="string" key="state">docked</property>
<anchors target="3D View">
<line own="right" target="right" />
<line own="bottom" target="bottom" />
</anchors>
</ignition-gui>
<sim_time>true</sim_time>
<real_time>true</real_time>
<real_time_factor>true</real_time_factor>
<iterations>true</iterations>
<topic>/world/world_demo/stats</topic>
</plugin>
Let’s run our world again and try monitoring the /world/world_demo/stats
topic:
ign gazebo world.sdf
And then, in a new terminal:
ign topic -e -t /world/world_demo/stats
Before hitting play, you’ll see messages like this:
real_time {
}
paused: true
sim_time {
}
real_time {
}
paused: true
After hitting play, you should start seeing messages like these:
iterations: 4034
real_time_factor: 0.9947
sim_time {
sec: 4
nsec: 133000000
}
real_time {
sec: 4
nsec: 165155216
}
iterations: 4133
real_time_factor: 0.9928
Cool! We can also add an “Entity tree” window, which will show all the entities (like our Coke can) that are part of the simulation:
<!-- Entity tree -->
<plugin filename="EntityTree" name="Entity tree">
</plugin>
That takes care of the GUI stuff! On to lights and models.
Lights Link to heading
We can add a simple direction light (our “sun”) like so. This does NOT go inside the <gui></gui>
tags. Rather, it goes after them, but still inside <world></world>
:
<light type="directional" name="sun">
<cast_shadows>true</cast_shadows>
<pose>0 0 10 0 0 0</pose>
<diffuse>0.8 0.8 0.8 1</diffuse>
<specular>0.2 0.2 0.2 1</specular>
<attenuation>
<range>1000</range>
<constant>0.9</constant>
<linear>0.01</linear>
<quadratic>0.001</quadratic>
</attenuation>
<direction>-0.5 0.1 -0.9</direction>
</light>
There are a bunch of cool settings here so we’ll break them down one-by-one:
type
is the type of light to create, and can bedirectional
,point
, orspot
<pose>
is the lights position, defined with the 6 values[X, Y, Z, Roll, Pitch, Yaw]
- `<cast_shadows> is a boolean that determines whether or not a light source should cast shadows
<diffuse>
and<specular>
set the diffuse and specular light colors. Not sure what that means? The (older) Gazebo docs have a nice breakdown on light and its interaction with various materials<attenuation>
describes how light should attenuate (dim) with distance from the light source. Its sub-properties are:<range>
which is the distance the light can travel<constant>
an attenuation factor between1
(no attenuation) and0
total attenuation<linear>
the linear attenuation factor, with1
meaning attenuate evenly over the distance<quadratic>
the quadratic attenuation factor, which adds “curvature” to the attenuation<direction>
the direction of the light (applies only to spot and directional lights)
Models Link to heading
Without adding any models, our world will be completely empty. We need code to include one or more models into the world. here, we pull in a Coke can directly from “fuel”, Gazebo’s handy model repository:
<include>
<uri>
https://fuel.gazebosim.org/1.0/OpenRobotics/models/Coke
</uri>
</include>
If you prefer, it’s possible to include a local model directly:
<include>
<uri>
model://Coke
</uri>
</include>
For this to work, you have to set your IGN_GAZEBO_RESOURCE_PATH
variable to include the parent folder where your models are stored. For example, if the Coke
model and world.sdf
were both stored in a folder called gazebo_tutorials
in our home directory, then we would set our path like this:
export IGN_GAZEBO_RESOURCE_PATH="$HOME/gazebo_tutorialss"
Note: I’m assuming a Linux environment, here
Awesome! Now you know how to create your own world, add models and lights, add plugins for physics simulation, and even define how the GUI will look on startup.
That’s it for this time.