-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Advanced tutorial: Implement physics engine as a plugin (#144)
* 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
1 parent
b7181e6
commit 3ac5fdc
Showing
2 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |