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.
Just a quickie today. We’ll create a simple Gazebo world with a single scripted actor. Actors are a way to introduce moving elements into the Gazebo world, such as vehicles or people. This allows us to test our robots in dynamic environments where things are moving around.
This goes a long way towards making the simulated world more like the real world.
We’re following along with this tutorial from the official Gazebo docs.
Because this tutorial is short and straightfoward, I’ll provide a starter .sdf
file inline with the blog post, rather than as a separate file. Simply copy-paste into your own .sdf
file.
Basics Link to heading
In Gazebo, an animated model is called an actor. Essentially these are scripted animations which do not interact with the world (i.e. they are unaffected by the physics of the world…they can’t fall over or bump into things). However, actors
are visible to our robot’s sensors (such as cameras or LIDAR).
There are two types of animations:
- Skeleton animation: we define relative motion between the actor’s links (for example, to make a human model “walk”)
- Trajectory animation: we define a path which moves all of the actor’s links at once, along a fixed path
We can then combine these together, to achieve an effect such as “a person walking in a circle”. The “walking” is achieved using skeleton animation, and the “in a circle” motion is achieved using trajectory animation.
Again, actors don’t interact with the rest of the simulated environment. They have a physical “presence” (are detectable by sensors) but will not collide with or be acted on by any of the other elements in the simulation. From the docs:
Actors have the following properties:
- No forces are applied on them, be it from gravity or contact or anything else
- Actors support skeleton animation imported from COLLADA (.dae) and BVH (.bvh) files.
- Actors can have trajectories scripted directly in SDF.
More on that here
Creating our first actor Link to heading
We need to add an actor into an existing (mostly) empty world. The actor code is giong to look like this:
<actor name="actor_walking">
<skin>
<filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
<scale>1.0</scale>
</skin>
<animation name="walk">
<filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
</animation>
</actor>
Of course, that’s not enough all on its own. We need to insert that code into a more complete .sdf
file. Here’s an “empty” world containing nothing but a can of coke, into which our new <actor>
has been inserted:
<?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>
<actor name="actor_walking">
<skin>
<filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
<scale>1.0</scale>
</skin>
<animation name="walk">
<filename>https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
</animation>
</actor>
</world>
</sdf>
Copy-paste the code above into your own .sdf
file (I called mine roboguy.sdf
), then run the simulation with:
ign gazebo roboguy.sdf
You should see something like this:
Rotating and zooming in a bit, we can even see our little can of Coke:
Note: Our .sdf
file does NOT automatically add the “Interactive view control” plugin when it launches Gazebo, so you’ll need to search for (and add) that plugin manually using the search bar in the upper right hand corner of the Gazebo GUI (three little vertical dots). Only then will you be able to rotate the scene as I’ve done in the screenshot above.
Making the actor move (scripted trajectory) Link to heading
If you hit “Play”, you’ll see the actor automatically moving forward, even though we haven’t created a trajector yet. This is normal: some actors are defined such that they already have a displacement along one or more axes: these models will move even if we do not create a scripted trajectory for them.
Let’s add a specific path for our actor to move along. Add the following inside your <actor></actor>
tags in the .sdf
file:
<script>
<loop>true</loop>
<delay_start>0.000000</delay_start>
<auto_start>true</auto_start>
<trajectory id="0" type="walk" tension="0.6">
<waypoint>
<time>0</time>
<pose>0 0 1.0 0 0 0</pose>
</waypoint>
<waypoint>
<time>2</time>
<pose>2.0 0 1.0 0 0 0</pose>
</waypoint>
<waypoint>
<time>2.5</time>
<pose>2 0 1.0 0 0 1.57</pose>
</waypoint>
<waypoint>
<time>4</time>
<pose>2 2 1.0 0 0 1.57</pose>
</waypoint>
<waypoint>
<time>4.5</time>
<pose>2 2 1.0 0 0 3.142</pose>
</waypoint>
<waypoint>
<time>6</time>
<pose>0 2 1 0 0 3.142</pose>
</waypoint>
<waypoint>
<time>6.5</time>
<pose>0 2 1 0 0 -1.57</pose>
</waypoint>
<waypoint>
<time>8</time>
<pose>0 0 1.0 0 0 -1.57</pose>
</waypoint>
<waypoint>
<time>8.5</time>
<pose>0 0 1.0 0 0 0</pose>
</waypoint>
</trajectory>
</script>
We have now defined a series of waypoints for the actor to move along, in a loop, starting immediately when we click “Play”.
Note: for animations to loop smoothly, your starting and ending waypoints should be the same. Note that Gazebo will “interpolate” along the waypoints so that the motion of the actor is smooth (i.e. we do not need to specify every single point the actor moves through). We control how closely the waypoints are followed using <tension>
.
A quick note about how that is done, from the docs:
The default tension value is zero, which equates to a Catmull-Rom spline, which may cause the animation to overshoot waypoints. A tension value of one will cause the animation to stick to the waypoints. The tension value should be in the range 0 to 1.
Also note that each waypoint has an associated time. Setting different times (in seconds) between our waypoints lets us speed up or slowdown our agent on various sectiosn of the path. The agent’s speed does not need to be constant.
Ok, now let’s re-run the simulation with:
ign gazebo roboguy.sdf
Here’s a .gif
of our agent moving along its new fixed path:
Awesome! We added a scripted, moving agent to a Gazebo world.
Complete .sdf
file
Link to heading
Here’s the completed roboguy.sdf
file, for reference:
<?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>
<actor name="actor_walking">
<skin>
<filename>
https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
<scale>1.0</scale>
</skin>
<animation name="walk">
<filename>
https://fuel.gazebosim.org/1.0/Mingfei/models/actor/tip/files/meshes/walk.dae</filename>
</animation>
<script>
<loop>true</loop>
<delay_start>0.000000</delay_start>
<auto_start>true</auto_start>
<trajectory id="0" type="walk" tension="0.6">
<waypoint>
<time>0</time>
<pose>0 0 1.0 0 0 0</pose>
</waypoint>
<waypoint>
<time>2</time>
<pose>2.0 0 1.0 0 0 0</pose>
</waypoint>
<waypoint>
<time>2.5</time>
<pose>2 0 1.0 0 0 1.57</pose>
</waypoint>
<waypoint>
<time>4</time>
<pose>2 2 1.0 0 0 1.57</pose>
</waypoint>
<waypoint>
<time>4.5</time>
<pose>2 2 1.0 0 0 3.142</pose>
</waypoint>
<waypoint>
<time>6</time>
<pose>0 2 1 0 0 3.142</pose>
</waypoint>
<waypoint>
<time>6.5</time>
<pose>0 2 1 0 0 -1.57</pose>
</waypoint>
<waypoint>
<time>8</time>
<pose>0 0 1.0 0 0 -1.57</pose>
</waypoint>
<waypoint>
<time>8.5</time>
<pose>0 0 1.0 0 0 0</pose>
</waypoint>
</trajectory>
</script>
</actor>
</world>
</sdf>