Configuration

Configuration is a way of representing any bot in an universal manner. It contains all building-blocks of the RoFI platform such as various modules and joints. Using these blocks, you can specify every rofibot and work with it. Configuration computes positions of every component within the bot and provides useful functions for manipulation with such rofibot, including some validity checks (e.g., collision checks).

Using the configuration is pretty straightforward. The main class is Rofibot for which you create an instance and add all the modules you have within one bot. You have to connect them appropriately and if you want to use absolute positions, you have to fix one of the bot’s components in space (otherwise, the configuration cannot figure out its coordinates because everything is kept relative).

For example, let us create a small bot with two universal modules.

#include <configuration/rofibot.hpp>

// ...

Rofibot bot;
// add universal module with id 42 in the default state
auto& m1 = bot.insert( UniversalModule( 42, 0_deg, 0_deg, 0_deg ) );
// add universal module with id 42 with beta set to 45 degrees and gamma to 90 degrees
auto& m2 = bot.insert( UniversalModule( 66, 0_deg, 45_deg, 90_deg ) );

// connect A+X of the universal module with id = 42 to A-X of UM with id = 66
connect( m1.connectors()[ 2 ], m2.connectors()[ 0 ], Orientation::North );
// fix the position of the `shoe A` in { 0, 0, 0 }
connect< RigidJoint >( m1.bodies()[ 0 ], { 0, 0, 0 }, identity );

With the bot in hand, you can then prepare it (i.e., compute its positions) and check for validity.

bot.prepare();
auto [ ok, err ] = bot.isValid( SimpleCollision() );
if ( !ok )
    std::cerr << "invalid configuration: " << err << "\n";

// or you can shorten the above to
auto [ ok, err ] = bot.validate( SimpleCollision() );

// also, the SimpleCollision model is the default one, so you can ommit it too and get
auto [ ok, err ] = bot.validate();

Types and Constants

using rofi::configuration::ModuleId = int

ModuleId.

enum class rofi::configuration::ComponentType

Values:

enumerator UmShoe
enumerator UmBody
enumerator Roficom
enum class rofi::configuration::ModuleType

Values:

enumerator Unknown
enumerator Universal
enumerator Pad
enum class rofi::configuration::roficom::Orientation

Values:

enumerator North
enumerator East
enumerator South
enumerator West

Classes

class rofi::configuration::Rofibot

RoFI bot.

The rofibot is composed out of modules.

Public Types

using ModuleInfoHandle = atoms::HandleSet<ModuleInfo>::handle_type
using RoficomJointHandle = atoms::HandleSet<RoficomJoint>::handle_type
using SpaceJointHandle = atoms::HandleSet<SpaceJoint>::handle_type

Public Functions

Rofibot() = default
inline Rofibot(const Rofibot &other)
inline Rofibot(Rofibot &&other)
inline Rofibot &operator=(Rofibot other)
inline void swap(Rofibot &other)
inline Module &insert(const Module &m)

Insert a module from the Rofibot.

The module position is not specified. You should connect the module to other modules via connect(). The module is assigned a unique id within the rofibot.

Returns a reference to the newly created module.

template<std::derived_from<Module> ModuleT>
inline ModuleT &insert(const ModuleT &m)

Insert a module from the Rofibot.

The module position is not specified. You should connect the module to other modules via connect(). The module is assigned a unique id within the rofibot.

Returns a reference to the newly created module.

inline Module *getModule(ModuleId id) const

Get pointer to module with given id within the Rofibot.

inline Module *getModule(ModuleInfoHandle h) const

Get pointer to module with given id within the Rofibot.

inline const auto &modules() const

Get a container of ModuleInfo.

inline const auto &roficomConnections() const

Get a container of RoficomJoint.

inline const auto &referencePoints() const
inline void remove(ModuleId id)

Remove a module from the Rofibot.

inline bool isPrepared() const

Return true if the configuration is prepared.

Configuration can be prepared with prepare.

template<typename Collision = SimpleCollision>
inline std::pair<bool, std::string> isValid(Collision collisionModel = Collision()) const

Decide whether the configuration is valid given the collision model.

Returns

A pair - first item indicates the validity, the second one gives textual description of the reason for invalidity

template<typename Collision = SimpleCollision>
inline std::pair<bool, std::string> validate(Collision collisionModel = Collision())

Prepare configuration if needed and decide whether it is valid with given collision model.

Returns

A pair - first item indicates the validity, the second one gives textual description of the reason for invalidity

void prepare()

Precompute position of all the modules in the configuration.

Raises std::runtime_error if the configuration is inconsistent, and, therefore, it cannot be

void setSpaceJointPositions(SpaceJointHandle jointId, std::span<const float> p)

Set position of a space joints specified by its id.

inline Matrix getModulePosition(ModuleId id)

Get position of a module specified by its id.

void disconnect(RoficomJointHandle h)
void disconnect(SpaceJointHandle h)
class rofi::configuration::Module

RoFI module.

The module is composed out of components.

Subclassed by rofi::configuration::Pad, rofi::configuration::UniversalModule, rofi::configuration::UnknownModule

Public Functions

inline Module(ModuleType type, std::vector<Component> components, int connectorCount, std::vector<ComponentJoint> joints, ModuleId id, std::optional<int> rootComponent = std::nullopt)

Construct module.

Note that components contain all module components out of which first connectorCount are module connectors.

The user can optionally specify a root component (e.g, in case of cyclic joints). If no root component is specified, component with no ingoing joints is selected.

ATOMS_CLONEABLE_BASE(Module)
virtual ~Module() = default
inline ModuleId getId() const
bool setId(ModuleId newId)

Set ID of the module to the new value.

Checks if the new value is not used within its parental rofibot, if it’s in use, the ID is left the same and the method returns False. Otherwise the ID is changed. Returns True on success.

void setJointPositions(int idx, std::span<const float> p)
inline Matrix getComponentRelativePosition(int idx)

Get a component position relative to module origin.

Raises std::logic_error if the components are inconsistent

inline Matrix getComponentRelativePosition(int idx) const

Get a component position relative to module origin.

Raises std::logic_error if the components are not prepared

void clearComponentPositions()
inline std::vector<Matrix> getOccupiedRelativePositions() const

Get a vector of occupied positions relative to module origin.

Raises std::logic_error if the components are not prepared

inline void prepare()

Precompute component relative positions.

Raises std::logic_error if the components are inconsistent

inline auto configurableJoints()
inline auto configurableJoints() const
inline std::span<const Component> components() const

Get read-only view of the components.

inline std::span<Component> components()
inline std::span<const Component> bodies() const

Get read-only view of the bodies.

inline std::span<Component> bodies()
inline std::span<const ComponentJoint> joints() const
inline std::span<const Component> connectors() const

Get read-only view of the connectors.

inline std::span<Component> connectors()
inline int componentIdx(const Component &c) const

Get index of a component.

Public Members

ModuleType type

module type

Rofibot *parent

pointer to parenting Rofibot

struct rofi::configuration::Joint : public atoms::VisitableBase<Joint, JointVisitor>

Joint between two coordinate systems.

Joints represents a parametric description of positional relationship between two coordinate systems (e.g., component origins). This class in abstract. See below for concrete instances.

Each joint has Joint::paramCount() parameters (real value), which are stored in Joint::position. These params are, e.g., angle for rotation joint.

Subclassed by rofi::configuration::RoficomJoint

Public Functions

inline explicit Joint(std::vector<std::pair<float, float>> jointLimits)
virtual ~Joint() = default
ATOMS_CLONEABLE_BASE(Joint)
inline std::span<const std::pair<float, float>> jointLimits() const
inline std::span<const float> positions() const
inline void setPositions(std::span<const float> pos)
virtual Matrix sourceToDest() const = 0
inline virtual Matrix destToSource() const

Friends

friend std::ostream &operator<<(std::ostream &out, Joint &j)

Provides human readable text description of the joint.

struct rofi::configuration::RigidJoint : public atoms::Visitable<Joint, RigidJoint>

Public Functions

inline RigidJoint(const Matrix &sToDest)

Construct a rigid joint based on transformation between source and dest.

Example usage: RigidJoint( rotate( M_PI/2, { 1, 0, 0, 1 } ) * translate( { 20, 0, 0 } ) )

inline Matrix sourceToDest() const override
inline Matrix destToSource() const override
ATOMS_CLONEABLE(RigidJoint)
struct rofi::configuration::RotationJoint : public atoms::Visitable<Joint, RotationJoint>

Public Functions

inline RotationJoint(Vector sourceOrigin, Vector sourceAxis, Vector destOrigin, Vector desAxis, Angle min, Angle max)

Construct rotation joint by specifing the same axis and origin point in source coordinate system and destination coordinate system.

inline RotationJoint(Matrix pre, Vector axis, Matrix post, Angle min, Angle max)

Construct rotation joint by specifying transformation before rotation, followed by rotation axis with origin in (0, 0, 0), followed by another transformation.

inline Matrix sourceToDest() const override
inline Matrix destToSource() const override
inline Angle position() const
inline void setPosition(Angle pos)
inline std::pair<Angle, Angle> jointLimit() const
inline const Matrix pre() const
inline const Matrix post() const
inline const Vector axis() const
ATOMS_CLONEABLE(RotationJoint)

Friends

friend std::ostream &operator<<(std::ostream &out, Joint &j)

Provides human readable text description of the joint.

struct rofi::configuration::RoficomJoint : public rofi::configuration::Joint

Joint between two modules.

The joint is created between two components of two modules specified by module ids and corresponding component index.

Public Functions

inline RoficomJoint(roficom::Orientation o, Rofibot::ModuleInfoHandle sourceModule, Rofibot::ModuleInfoHandle destModule, int sourceConnector, int destConnector)
inline virtual Matrix sourceToDest() const override
ATOMS_CLONEABLE(RoficomJoint)
inline Module &getSourceModule(Rofibot &rofibot) const
inline const Module &getSourceModule(const Rofibot &rofibot) const
inline Module &getDestModule(Rofibot &rofibot) const
inline const Module &getDestModule(const Rofibot &rofibot) const

Public Members

roficom::Orientation orientation
Rofibot::ModuleInfoHandle sourceModule
Rofibot::ModuleInfoHandle destModule
int sourceConnector
int destConnector
struct rofi::configuration::ComponentJoint

Joint between two components of the same module.

Public Functions

inline ComponentJoint(atoms::ValuePtr<Joint> joint, int source, int dest)
ComponentJoint(ComponentJoint&&) = default
ComponentJoint &operator=(ComponentJoint&&) = default
inline ComponentJoint(const ComponentJoint &o)
inline ComponentJoint &operator=(const ComponentJoint &o)

Public Members

atoms::ValuePtr<Joint> joint
int sourceComponent
int destinationComponent
struct rofi::configuration::Component

Single body of a module.

Public Types

using JointId = int

Public Functions

inline Component(ComponentType type, std::vector<JointId> inJoints, std::vector<JointId> outJoints, Module *parent = nullptr)
inline explicit Component(ComponentType type)
bool operator==(const Component &o) const = default
int getIndexInParent() const

Get the index of component in parent.

Matrix getPosition() const

Get the absolute component position.

Raises std::logic_error if the rofibot is not prepared

std::optional<std::pair<const Component&, roficom::Orientation>> getNearConnector() const

Find a connector of another module in the same Rofibot that can be connected to.

Returns the connector and orientation Returns nullopt if no such connector exists

Raises std::logic_error if the rofibot is not prepared

Public Members

ComponentType type
std::vector<JointId> inJoints
std::vector<JointId> outJoints
Module *parent = nullptr
struct rofi::configuration::SpaceJoint

Joint between a fixed point in space and a module.

Public Functions

inline SpaceJoint(atoms::ValuePtr<Joint> joint, Vector refPoint, Rofibot::ModuleInfoHandle destModule, int destComponent)
SpaceJoint(SpaceJoint&&) = default
SpaceJoint &operator=(SpaceJoint&&) = default
inline SpaceJoint(const SpaceJoint &o)
inline SpaceJoint &operator=(const SpaceJoint &o)

Public Members

atoms::ValuePtr<Joint> joint
Vector refPoint
Rofibot::ModuleInfoHandle destModule
int destComponent
class rofi::configuration::NoCollision

Collision model ignoring collision.

Public Functions

inline bool operator()(const Module&, const Module&, Matrix, Matrix)

Decide if two modules collide.

class rofi::configuration::SimpleCollision

Collision model taking into account only spherical collisions of the shoes.

Public Functions

inline bool operator()(const Module &a, const Module &b, Matrix posA, Matrix posB)

Decide if two modules collide.

Modules

class rofi::configuration::Pad : public rofi::configuration::Module

Public Functions

inline Pad(ModuleId id, int width, int height)
inline explicit Pad(ModuleId id, int size)
ATOMS_CLONEABLE(Pad)

Public Members

const int width = 0
const int height = 0
class rofi::configuration::UniversalModule : public rofi::configuration::Module

Public Types

enum UmParts

Values:

enumerator UmBodyA
enumerator UmBodyB
enumerator UmShoeA
enumerator UmShoeB

Public Functions

ATOMS_CLONEABLE(UniversalModule)
inline explicit UniversalModule(int id)
inline UniversalModule(int id, Angle a, Angle b, Angle g)
inline Angle getAlpha() const
inline Angle getBeta() const
inline Angle getGamma() const
inline void setAlpha(Angle a)
inline void setBeta(Angle a)
inline void setGamma(Angle a)
inline const auto &getConnector(const std::string &cStr) const

Public Static Functions

static int translateComponent(const std::string &cStr)
static std::string translateComponent(int c)
class rofi::configuration::UnknownModule : public rofi::configuration::Module

Public Functions

inline UnknownModule(std::vector<Component> components, int connectorCount, std::vector<ComponentJoint> joints, ModuleId id, std::optional<int> rootComponent = std::nullopt)
ATOMS_CLONEABLE(UnknownModule)

Functions

Rofibot::RoficomJointHandle rofi::configuration::connect(const Component &c1, const Component &c2, roficom::Orientation o)

Connect two modules via connector.

Requires that both modules belong to the same Rofibot, otherwise std::logic_error is thrown.

template<typename JointT, typename ...Args>
Rofibot::SpaceJointHandle rofi::configuration::connect(const Component &c, Vector refpoint, Args&&... args)

Connect a module’s component to a point in space via given joint type.

First argument specified the component, the rest of the arguments are forwarded

Returns ID of the joint which can be used for setting parameters of the joint

template<typename JointT, typename ...Args>
ComponentJoint rofi::configuration::makeComponentJoint(int source, int dest, Args&&... args)

Serialization

Configuration also supports serialization to and from json format via functions toJSON and fromJSON respectively, so that you can save your configuration into a file and load it as needed. For the json itself we use nlohman::json library.

The configuration description consists of three main parts: modules, moduleJoints, and spaceJoints.

The minimal configuration looks like this.

#include <configuration/serialization.hpp>

// the json library supports string literals
auto js = "{ \"modules\" : [], \"spaceJoints\" : [], \"moduleJoints\" : [] }"_json;
Rofibot bot = fromJSON( js );
// and we can continue as before

If we were to represent the configuration with two universal modules shown above, we could do it with this json

{
    "modules" : [
        {
            "id" : 42,
            "type" : "universal",
            "alpha" : 0,
            "beta"  : 0,
            "gamma" : 0
        },
        {
            "id" : 66,
            "type" : "universal",
            "alpha" : 0,
            "beta"  : 45,
            "gamma" : 90
        }
    ],

    "moduleJoints" : [
        {
            "orientation" : "East",
            "from" : { "id" : 66, "connector" : "A+X" },
            "to" :   { "id" : 42, "connector" : "A-X" }
        }
    ],

    "spaceJoints" : [
        {
            "point" : [ 0, 0, 0 ],
            "to" : {
                     "id" : 42,
                     "component" : 6
            },
            "joint" : {
                        "type" : "rigid",
                        "sourceToDestination" : [ [1, 0, 0, 0]
                                                , [0, 1, 0, 0]
                                                , [0, 0, 1, 0]
                                                , [0, 0, 0, 1] ]
                    }
        }
    ]
}

You are not limited to universal modules only, currently we support a module Pad representing a 5x4 pad of RoFICoMs which can be represented as

{
    "id" : 66,
    "type"   : "pad",
    "width"  : 5,
    "height" : 4
}

and there is also a representation of an arbitrary module corresponding to the UnknownModule. Its attributes mirror the class

{
    "id" : 66,
    "components" : [ < array of components > ],
    "joints"     : [ < array of joints >     ]
}

where the component has three possible values

[
    {
        "type" : "roficom"
    },
    {
        "type" : "UM shoe"
    },
    {
        "type" : "UM body"
    }
]

and joint is represented as

{
    "from" : < component >,
    "destination" : < component >,
    "sourceToDestination" : < matrix >,
    "joint" : < joint >
}

where possible values of the joint are either RigidJoint represented as

{
    "type" : "rigid"
}

or the RotationJoint which requires appropriate matrices

{
    "type" : "rotational",
    "axis" : < 4-dimensional array >,
    "preMatrix"  : < matrix >,
    "postMatrix" : < matrix >,
    "min" : < lower-limit – number >,
    "max" : < upper-limit – number >
}

Matrices are, as shown above, represented by 4x4 dimensional array. Or, for the identity matrix, you can use a string representation, just write “identity” instead of [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]].

template<typename Callback>
inline nlohmann::json rofi::configuration::serialization::toJSON(const Rofibot &bot, Callback attrCb)

Serialize given Rofibot to json.

Callback for attributes has to cover all of these possible arguments:

  • callback for a module gets: const reference to the Module itself

  • callback for a joint gets: const reference to the Joint itself and its index within the module

  • callback for a component gets: const reference to the Component or ComponentJoint and its index

  • callback for a SpaceJoint or a RoficomJoint gets a const reference to the appropriate joint

Parameters

attrCb – a suitable function or Callable object that returns a nlohmann::json which is then stored into appropriate "attributes" field

The callback is optional. It provides you with the ability to extend the json representation with an “attributes” property, which can be added to any object within the json. It can contain some metadata you might use when working with and sharing the configuration description. For details, see the section below.

inline nlohmann::json rofi::configuration::serialization::toJSON(const Rofibot &bot)
template<typename Callback>
inline Rofibot rofi::configuration::serialization::fromJSON(const nlohmann::json &j, Callback attrCb)

Load a Rofibot from given json.

Callback for attributes has to cover all of these possible arguments:

  • callback for a Module gets: const reference for the json[ “attributes” ] and const reference to the Module itself

  • callback for a Joint gets: const reference for the json[ “attributes” ] and const reference to the Joint itself and its index within the module

  • callback for a Component and ComponentJoint gets: const reference for the json[ “attributes” ] and a const reference to the Component itself and its index within the module

  • callback for a SpaceJoint or a RoficomJoint gets: const reference for the json[ “attributes” ] and a handle for given connection

Parameters
  • j – json with a Rofibot

  • attrCb – a suitable function or Callable object that process appropriate "attributes" fields

Here you can provide a callback function, that is used for parsing the optional “attributes” field. If no callback is provided, the field, if present, is ignored. The callback is written in the same way as for toJSON.

inline Rofibot rofi::configuration::serialization::fromJSON(const nlohmann::json &j)

Attributes callback

You can extend the json description of a configuration with “attributes” field. This field can be present in any object within the configuration, so the callback function has to be able to accept every corresponding type. The possible callback for toJSON that stores a ModuleId to “attributes” looks like

overload{ []( const Module& m ) { return nlohmann::json( m.getId() ); },
          []( const ComponentJoint&, int jointIndex ) { return nlohmann::json{}; },
          []( const Component&, int componentIndex  ) { return nlohmann::json{}; },
          []( const RoficomJoint& ) { return nlohmann::json{}; },
          []( const SpaceJoint&   ) { return nlohmann::json{}; }
};

You can see that every function returns a nlohman::json which is then stored to appropriate “attributes” field.

To collect these attributes you can then use this callback

std::vector< ModuleId > ids;

overload{ [ &ids ]( const nlohmann::json& j, const Module& m ) {
                    ids.push_back( j );
          },
          []( const nlohmann::json&, const ComponentJoint&, int jointIndex )  { return; },
          []( const nlohmann::json&, const Component&, int componentIndex  )  { return; },
          []( const nlohmann::json&, Rofibot::RoficomJointHandle ) { return; },
          []( const nlohmann::json&, Rofibot::SpaceJointHandle )   { return; },
};

See, that the main difference is in the arguments – callback given to fromJSON takes a json that is the content of the respective “attributes” field.