Skip to content

Commit

Permalink
Advanced tutorial: Implement physics engine as a plugin (#144)
Browse files Browse the repository at this point in the history
* Initial draft implementing feature in TPE
* Revised tutorial 9
* Fix code in example feature, tested compiling

Co-authored-by: anindex <an.thai.le97@gmail.com>
Co-authored-by: Claire Wang <22240514+claireyywang@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 7, 2021
1 parent b7181e6 commit 3ac5fdc
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 0 deletions.
2 changes: 2 additions & 0 deletions tutorials.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively.
7. \subpage createphysicsplugin "Implement a physics plugin"
8. \subpage createcustomfeature "Implement a custom feature"

9. \subpage setupphysicsenginetpe "Use custom engine with Ignition Physics"

## License

The code associated with this documentation is licensed under an [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0).
Expand Down
283 changes: 283 additions & 0 deletions tutorials/09-set-up-physics-engine-tpe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
\page setupphysicsenginetpe "Use custom engine with Ignition Physics"

## Prerequisites

In the previous tutorial \ref installation "Installation", you have installed
the Ignition Physics corresponding to the desired Ignition release. Note that
the recommended Ignition release is Dome.

## How to adapt a physics engine as a plugin in Ignition Physics

In the last \ref createphysicsplugin "Implement a physics plugin" tutorial, we
know how to implement a dummy physics engine as a plugin and load it using
\ref ignition::physics "Ignition Physics API". In
\ref createcustomfeature "Implement a custom feature" tutorial, we know how to
define and implement a custom feature in an existing
[DART](https://github.com/ignitionrobotics/ign-physics/tree/master/dartsim) physics
plugin. This tutorial will explain step-by-step how to use any physics engine
with Ignition Physics. We will use [TPE](https://github.com/ignitionrobotics/ign-physics/tree/main/tpe)
as an example for this tutorial.

### General structure of a physics plugin

As described in \ref createcustomfeature "Implement a custom feature" tutorial,
the plugin folder is placed just below the top-level folder of `ign-physics`.
In general, any physics plugin folder will have the following structure, which
is commented in detail:

```
tpe
├── lib Main physics engine implementation.
│ ├── src
│ ├── CMakeLists.txt CMake build script for physics engine implementation.
├── plugin Main implementation of the plugin features interfacing the physics engines API
│ ├── src
│ | ├── plugin.cc Main file for the plugin declaration and plugin registering.
│ | ├── <FEATURES>.hh The FeatureList header file.
│ | ├── <FEATURES>.cc Implementation of the FeatureList, which adapts to functions implemented in lib used in the FeatureList.
│ | ├── <FEATURES_TEST>.cc Test cases for the FeatureList.
│ ├── worlds Example SDF files for testing plugin functionalities.
│ ├── CMakeLists.txt CMake build script for the plugin features.
└── CMakeLists.txt CMake build script for the plugin.
```

Note that the `lib` folder is optionally placed inside the physics plugin folder
depending on the design target, we can link the external physics engine library
in `CMakeLists.txt` of the plugin, assuming the physics engine library is
already installed. In our case, [TPE](https://github.com/ignitionrobotics/ign-physics/tree/main/tpe)
is natively developed inside Ignition Physics and hence the `lib` folder.

We declare and implement the \ref ignition::physics::FeatureList "FeatureList"
interfacing with the physics engine API inside `plugin\src` folder
(please see \ref createcustomfeature "Implement a custom feature"
for the plugin feature requirements). Depending on design target, a \ref ignition::physics::FeatureList "FeatureList"
is generally a packing of related \ref ignition::physics::Feature "Features".
For example in TPE's [EntityManagementFeatures](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/src/EntityManagementFeatures.hh)
, there are \ref ignition::physics::GetEngineInfo "GetEngineInfo",
\ref ignition::physics::GetWorldFromEngine "GetWorldFromEngine", etc. features
defined in the "FeatureList" structure for entity management purpose.

Conventionally, a \ref ignition::physics::FeatureList "FeatureList" can be
implemented as:
- `<FEATURES>.hh` for the "FeatureList" declaration.
- `<FEATURES>.cc` for the "FeatureList" implementation corresponding to each of
the \ref ignition::physics::Feature "Features" member functions, using the
physics engine API to realize the feature behavior. For a list of common
pre-defined features in Ignition Physics, please refer to
\ref physicsplugin "Understanding the Physics Plugin" tutorial.
- `<FEATURES_TEST>.cc` for unit tests of the "FeatureList".

### Main plugin.cc file

In this tutorial, we will show how to construct a simple simulation world using
[TPE](https://github.com/ignitionrobotics/ign-physics/tree/main/tpe) physics
engine. For this purpose, we will implement the pre-defined
\ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature"
and include this feature into an empty \ref ignition::physics::FeatureList "FeatureList"
named `EntityManagementFeatureList` defined in `EntityManagementFeatures.hh`.
We first include the `EntityManagementFeatureList` in `plugin.cc` main file
and register the example TPE physics plugin as follow:

```cpp
#include <ignition/physics/Register.hh>

#include "Base.hh"

#include "EntityManagementFeatures.hh"

namespace ignition {
namespace physics {
namespace tpeplugin {

struct TpePluginFeatures : FeatureList<
EntityManagementFeatureList
> { };

class Plugin :
public virtual Implements3d<TpePluginFeatures>,
public virtual Base,
public virtual EntityManagementFeatures { };

IGN_PHYSICS_ADD_PLUGIN(Plugin, FeaturePolicy3d, TpePluginFeatures)

}
}
}
```

In general, there are 3 steps for `plugin.cc`:
- Define the final \ref ignition::physics::FeatureList "FeatureList" including
all required "FeatureLists". In TPE case, it is `TpePluginFeatures`.
- Define an empty class inherited all "FeatureLists" class, [Base](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/src/Base.hh)
class (optionally depending on software design)
and \ref ignition::physics::Implements "Implements" class implementing
\ref ignition::physics::FeaturePolicy "FeaturePolicy" 2D or 3D and different
scalar type.
- Register the physics plugin using `IGN_PHYSICS_ADD_PLUGIN` macro (See
\ref createphysicsplugin "Implement a physics plugin" for more detail).

### Implement a feature using TPE's API

Now we assume that we have not implemented any \ref ignition::physics::Feature "Feature"
for `TPE`. In the `plugin` folder, we will create two files `EntityManagementFeatures.hh` and
`EntityManagementFeatures.cc` to implement \ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature"
in `EntityManagementFeatures` "FeatureList". The unit test for this feature is also
worth implemented in `EntityManagement_TEST.cc`. Please download the example
[CMakeLists.txt](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/CMakeLists.txt)
and [Base.hh](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/src/Base.hh)
into `plugin` folder by:

```bash
wget https://raw.githubusercontent.com/ignitionrobotics/ign-physics/main/tpe/plugin/CMakeLists.txt -P <path-to-ign-physics>/tpe/plugin/
wget https://raw.githubusercontent.com/ignitionrobotics/ign-physics/main/tpe/plugin/src/Base.hh -P <path-to-ign-physics>/tpe/plugin/src
```

Now the folder structure looks like this:

```
tpe
├── lib
├── plugin
│ ├── src
│ | ├── plugin.cc
│ | ├── Base.hh
│ | ├── EntityManagementFeatures.hh
│ | ├── EntityManagementFeatures.cc
│ | ├── EntityManagement_TEST.cc
│ ├── CMakeLists.txt
└── CMakeLists.txt
```

Basically, \ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature"
has a subclass \ref ignition::physics::Feature::Engine "Engine" defining
`ConstructEmptyWorld` member function. The feature implementation is shown as
below:

##### EntityManagementFeatures.hh:

```cpp
#ifndef IGNITION_PHYSICS_TPE_PLUGIN_SRC_GETENTITIESFEATURE_HH_
#define IGNITION_PHYSICS_TPE_PLUGIN_SRC_GETENTITIESFEATURE_HH_

#include <string>
#include <ignition/physics/ConstructEmpty.hh>
#include <ignition/physics/Shape.hh>
#include <ignition/physics/GetEntities.hh>
#include <ignition/physics/RemoveEntities.hh>
#include <ignition/physics/Implements.hh>

#include "Base.hh" // optionally depending on software design

namespace ignition {
namespace physics {
namespace tpeplugin {

struct EntityManagementFeatureList : FeatureList<
ConstructEmptyWorldFeature
> { };
class EntityManagementFeatures :
public virtual Base,
public virtual Implements3d<EntityManagementFeatureList>
{
// ----- Construct empty entities -----
public: Identity ConstructEmptyWorld(
const Identity &_engineID, const std::string &_name) override;
};

}
}
}

#endif
```

Together with other (if existing) \ref ignition::physics::Feature "Features",
the \ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature"
is included in `EntityManagementFeatureList` "FeatureList" to declare the related
features for entity management purpose.

The `EntityManagementFeatures` "FeatureList" here inherits from:
- (optionally) \ref ignition::physics::tpelib::Base "Base"
class for foundation metadata definitions of Models, Joints, Links, and Shapes objects
of TPE to provide easy access to [tpelib](https://github.com/ignitionrobotics/ign-physics/tree/main/tpe/lib)
structures in the TPE library. Note that we mention `Base` class here for
completeness, `Base` class is not necessarily needed if there is a straightforward
way to interface external physics engine class objects with `ign-physics` class objects.
- \ref ignition::physics::Implements3d "Implements3d" for implementing the
custom feature with \ref ignition::physics::FeaturePolicy3d "FeaturePolicy3d"
("FeaturePolicy" of 3 dimensions and scalar type `double`).

##### EntityManagementFeatures.cc:

```cpp
#include <string>
#include "EntityManagementFeatures.hh"

using namespace ignition;
using namespace physics;
using namespace tpeplugin;

/////////////////////////////////////////////////
Identity EntityManagementFeatures::ConstructEmptyWorld(
const Identity &, const std::string &_name)
{
auto world = std::make_shared<tpelib::World>();
world->SetName(_name);
return this->AddWorld(world);
}
```
Here we show the overriding of `ConstructEmptyWorld` member function of
\ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature",
this is where we use the physics engine API to implement this member function.
We simply instantiate \ref ignition::physics::tpelib::World "World" object, set
the world name and call \ref ignition::physics::tpelib::Base::AddWorld "AddWorld"
function which was defined in [Base.hh](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/src/Base.hh).
##### EntityManagement_TEST.cc:
Simple unit tests are good practice for sanity checks.
While we won't go into detail, here is an example to test our new
\ref ignition::physics::ConstructEmptyWorldFeature "ConstructEmptyWorldFeature":
```cpp
#include <gtest/gtest.h>
#include <ignition/plugin/Loader.hh>
#include <ignition/physics/RequestEngine.hh>
#include "EntityManagementFeatures.hh"
struct TestFeatureList : ignition::physics::FeatureList<
ignition::physics::tpeplugin::EntityManagementFeatureList
> { };
TEST(EntityManagement_TEST, ConstructEmptyWorld)
{
ignition::plugin::Loader loader;
loader.LoadLib(tpe_plugin_LIB);
ignition::plugin::PluginPtr tpe_plugin =
loader.Instantiate("ignition::physics::tpeplugin::Plugin");
auto engine =
ignition::physics::RequestEngine3d<TestFeatureList>::From(tpe_plugin);
auto world = engine->ConstructEmptyWorld("empty world");
ASSERT_NE(nullptr, world);
}
int main(int argc, char *argv[])
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
```

Please take a look at [EntityManagement_TEST.cc](https://github.com/ignitionrobotics/ign-physics/blob/main/tpe/plugin/src/EntityManagement_TEST.cc)
for more comprehensive unit tests.

## Build the custom physics plugin

Please follow the previous tutorial \ref installation "Installation" to build
`ign-physics` from source again for our new feature to be compiled.

Now we can load the new physics plugin named `ignition-physics-tpe-plugin`
to test it on Ignition Gazebo by following this \ref switchphysicsengines "Switching physics engines" tutorial.

0 comments on commit 3ac5fdc

Please sign in to comment.