Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tutorial implement a custom feature #131

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tutorials.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively.
5. \subpage pluginloading "Loading a Physics Plugin"

7. \subpage createphysicsplugin "Implement a physics plugin"
8. \subpage createcustomfeature "Implement a custom feature"

## License

Expand Down
302 changes: 302 additions & 0 deletions tutorials/08-implementing-a-custom-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
\page createcustomfeature "Implement a custom feature"

## 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.

## Implement a custom feature in DART plugin

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 this tutorial, we will look
deeper into the structure of a physics engine plugin, for example, the available
[DART](https://github.com/ignitionrobotics/ign-physics/tree/master/dartsim)
physics engine in `ign-physics` repository and how to define a custom
\ref ignition::physics::Feature "Feature" for the plugin.

### Folder structure of the plugins

Below is the general structure of the `ign-physics` repository:

```
ign-physics
├── dartsim Files for dartsim plugin component.
├── tpe Files for tpe plugin component.
├── include/ignition/physics Header files.
├── mesh Files for mesh component.
├── resources Model and mesh resource files used by tests.
├── sdf Files for sdf component.
├── src Source files and unit tests.
├── test
├── tutorials Tutorials, written in markdown.
├── Changelog.md Changelog.
└── CMakeLists.txt CMake build script.
```

As shown above, there are two physics engines available (more detail
in \ref physicsplugin "Physics plugin tutorial"):
- **DART**: `ignition-physics-dartsim-plugin`.
- **TPE**: `ignition-physics-tpe-plugin`.

and their plugin folders are placed just below the top level of `ign-physics`.

Looking closer to a plugin folder, for example, the `dartsim` (DART) plugin:

```
dartsim
├── worlds Example SDF files for testing dartsim plugin functionalities.
├── src Main implementation files of the plugin features interfacing the physics engines API
├── include/ignition/physics/dartsim Header files for the plugin features.
└── CMakeLists.txt CMake plugin build script.
```

Basically, new implementation of \ref ignition::physics::Feature "Feature" or
\ref ignition::physics::FeatureList "FeatureList", which is corresponded to a
functionality of the external physics engine can be defined as a header in
`include/ignition/physics/<plugin_name>` folder. The custom feature could
be added in a \ref ignition::physics::FeatureList "FeatureList"
and implemented its functionalities in `src` folder.

The `dartsim` plugin's \ref ignition::physics::FeatureList "FeatureList" could be
found in \ref physicsplugin "Understanding the Physics Plugin" tutorial.

### Plugin and feature requirements

In general, the minimum set of features that any physics engine plugin must
implement to be supported by Ignition Gazebo is as below:
- \ref ignition::physics::FindFreeGroupFeature "FindFreeGroupFeature"
- \ref ignition::physics::SetFreeGroupWorldPose "SetFreeGroupWorldPose"
- \ref ignition::physics::FreeGroupFrameSemantics "FreeGroupFrameSemantics"
- \ref ignition::physics::LinkFrameSemantics "LinkFrameSemantics"
- \ref ignition::physics::ForwardStep "ForwardStep"
- \ref ignition::physics::RemoveEntities "RemoveEntities"
- \ref ignition::physics::sdf::ConstructSdfLink "ConstructSdfLink"
- \ref ignition::physics::sdf::ConstructSdfModel "ConstructSdfModel"
- \ref ignition::physics::sdf::ConstructSdfWorld "ConstructSdfWorld"

This list defines the minimum requirements for the simulation capability of a
physics engine plugin and also maintains backward compatibility with
downstream physics plugins.

For custom feature requirements, there are two main component classes
in the general structure of a custom feature:
- \ref ignition::physics::Entity "Entity" corresponds to the "proxy object" that
the \ref ignition::physics::Feature "Feature" is implemented. These are the most
common "proxy objects" that are inherited from `Entity` class:
- \ref ignition::physics::Feature::Engine "Engine": Placeholder class for the
Engine API. This class serves metadata for the physics engine (for example
the \ref ignition::physics::GetEngineInfo "GetEngineInfo" feature).
Every Engine feature **must** inherit this class.
- \ref ignition::physics::Feature::Joint "Joint": defines physics concept
`Joint` behaviors (for example the
\ref ignition::physics::GetBasicJointState "GetBasicJointState" feature).
- \ref ignition::physics::Feature::Link "Link": defines physics concept `Link`
structure.
- \ref ignition::physics::Feature::Model "Model": defines physics concept
`Model` structure (for example the
\ref ignition::physics::GetLinkFromModel "GetLinkFromModel" feature
including both `Link` and `Model` objects).
- \ref ignition::physics::Feature::Shape "Shape": defines physics concept
`Shape` structure (for example the
\ref ignition::physics::GetShapeKinematicProperties "GetShapeKinematicProperties"
feature).
- \ref ignition::physics::Feature::World "World": defines physics concept
`Shape` structure (for example
the \ref ignition::physics::dartsim::RetrieveWorld "RetrieveWorld" feature
in `dartsim` plugin).

Note that these object classes are not mutually exclusive and could be defined
in conjunction together to describe the `Feature`. There are also other
uncommon objects defined depending on feature functionality, for example, the
\ref ignition::physics::SetFreeGroupWorldPose::FreeGroup "FreeGroup"
object in `SetFreeGroupWorldPose` feature. For more information about the
physics concepts, please refer to
\ref physicsconcepts "Ignition Physics simulation concepts" tutorial.
- \ref ignition::physics::Feature::Implementation "Implementation" interfaces
the actual physics engines API for the custom feature. It has
\ref ignition::physics::Feature::Implementation::InitiateEngine "InitiateEngine"
to trigger physics engine initiation to provide the required functionalities.

Moreover, we can define dependencies between custom `Features`:
- By default, a blank feature will not require any other features.
If the custom feature does require some other set of features,
then it should be inherited from
\ref ignition::physics::FeatureWithRequirements "FeatureWithRequirements" class,
and provided a list of the `Features` required.
- By default, a blank feature will not conflict with any other features. If
the custom feature does conflict with some other set of features, then it should
be inherited from
\ref ignition::physics::FeatureWithConflicts "FeatureWithConflicts" class,
and provided a list of the conflicting `Features`. The conflicting `Features`
will not run at the same time when requested.

### Define the custom feature

With the requirements and restrictions above, we will implement an example
custom `Feature` that retrieves a simulation world from `dartsim` physics engine.
For example, we name it as [World.hh](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/include/ignition/physics/dartsim/World.hh)
and the its content is as follow:

```cpp
#include <dart/simulation/World.hpp>
#include <ignition/physics/FeatureList.hh>

namespace ignition {
namespace physics {
namespace dartsim {

/////////////////////////////////////////////////
class RetrieveWorld : public virtual Feature
{
public: template <typename PolicyT, typename FeaturesT>
class World : public virtual Feature::World<PolicyT, FeaturesT>
{
/// \brief Get the underlying dartsim world for this World object.
public: dart::simulation::WorldPtr GetDartsimWorld();
};

public: template <typename PolicyT>
class Implementation : public virtual Feature::Implementation<PolicyT>
{
public: virtual dart::simulation::WorldPtr GetDartsimWorld(
const Identity &_worldID) = 0;
};
};

/////////////////////////////////////////////////
template <typename PolicyT, typename FeaturesT>
dart::simulation::WorldPtr RetrieveWorld::World<PolicyT, FeaturesT>
::GetDartsimWorld()
{
return this->template Interface<RetrieveWorld>()
->GetDartsimWorld(this->identity);
}

}
}
}
```

The new defined feature file is placed in `dartsim/include/ignition/physics/dartsim`:
```
dartsim
├── worlds
├── src
├── include/ignition/physics/dartsim
│ ├── World.hh
└── CMakeLists.txt
```

As seen above, after including the necessary library of `dartsim` and `ign-physics`,
we define the `RetrieveWorld` custom feature inherited from the base
\ref ignition::physics::Feature "Feature".

As defined, the `RetrieveWorld` feature retrieves
world pointer from physics engine, so it is natural to define `World` entity inherited
from \ref ignition::physics::Feature::World "Feature::World" and declare the
necessary member function `GetDartsimWorld`. Then we define the `Implementation`
class having virtual member function for overriding in the actual implementation of
the custom feature `RetrieveWorld` later.

Finally, we implement the `World`
entity's member function `GetDartsimWorld` to call the `Implementation`
class's member function `GetDartsimWorld` via
\ref ignition::physics::Feature::Entity::Interface "Entity::Interface"
convenience function for querying the feature `Implementation` object.

### Implement the custom feature

After defining the custom feature, please look into where it is added to a
\ref ignition::physics::FeatureList "FeatureList" in
[CustomFeatures.hh](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/src/CustomFeatures.hh)
and implemented in [CustomFeatures.cc](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/src/CustomFeatures.cc).
These files are place as follows:
```
dartsim
├── worlds
├── src
│ ├── CustomFeatures.hh
│ ├── CustomFeatures.cc
│ ├── ...
├── include/ignition/physics/dartsim
└── CMakeLists.txt
```

We display them here for convenience:

- `CustomFeatures.hh`:

```cpp
#include <ignition/physics/Implements.hh>
#include <ignition/physics/dartsim/World.hh>
#include "Base.hh"

namespace ignition {
namespace physics {
namespace dartsim {

using CustomFeatureList = FeatureList<
RetrieveWorld
>;

class CustomFeatures :
public virtual Base,
public virtual Implements3d<CustomFeatureList>
{
public: dart::simulation::WorldPtr GetDartsimWorld(
const Identity &_worldID) override;
};

}
}
}

```

The custom feature `RetrieveWorld` is added to `CustomFeatureList`, other custom
features could also be added here.
The `CustomFeatures` "FeatureList" here inherits from:
- [Base](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/src/Base.hh)
class for foundation definitions of Models, Joints, Links, and Shapes objects
of `dartsim` interfacing to Ignition Physics API.
- \ref ignition::physics::Implements3d "Implements3d" for implementing the
custom feature with \ref ignition::physics::FeaturePolicy3d "FeaturePolicy3d"
("FeaturePolicy" of 3 dimensions and scalar type `double`).

Based on the `CustomFeatureList`, we will override the corresponding member functions
declared in each of the custom features. In this case, we have only the custom
feature `RetrieveWorld` and its corresponding `Implementation::GetDartsimWorld`
member function.

- `CustomFeatures.cc`:

```cpp
#include "CustomFeatures.hh"

namespace ignition {
namespace physics {
namespace dartsim {

/////////////////////////////////////////////////
dart::simulation::WorldPtr CustomFeatures::GetDartsimWorld(
const Identity &_worldID)
{
return this->worlds.at(_worldID);
}

}
}
}
```

Here we simply implement the actual behavior of `GetDartsimWorld` to return the
world pointer from `EntityStorage` object storing world pointers of `dartsim` in
[Base](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/src/Base.hh) class.

Finally, we add the implemented `CustomFeatures` "FeatureList" together with
other \ref ignition::physics::FeatureList "FeatureList" to final `DartsimFeatures`
"FeatureList" as in [dartsim/src/plugin.cc](https://github.com/ignitionrobotics/ign-physics/blob/main/dartsim/src/plugin.cc)
(please see \ref createphysicsplugin "Implement a physics plugin" for
registering the plugin to Ignition Physics).