How to implement a custom backend
You have a source of pose data — a sensor driver, a physics engine, an interpolation formula, a calibration table — and you want to expose it as a first-class coordinate system that participates in the kinematic tree alongside the built-in ones.
This guide is the short recipe. It assumes you know what a coordinate system is and
have already used the built-in coordsys_child. If you are still learning the
backend interface from the ground up, follow
Tutorial 202 first — it walks through one full backend
end to end. For the formal contract of the classes mentioned below, jump to
coordsys_backend,
coordsys_generic or
coordsys_child.
The recipe
A custom coordinate system always comes in two layers. Write the backend, then choose a wrapper.
-
Derive a backend from
coordsys_backend<CT>and implement two virtuals:-
offset(unsigned hop_limit) const override— return the transformation toward WCS; -
clone() const override— return ashared_ptrto an independent copy.
-
-
Choose a wrapper that exposes the backend as a
coordsys<CT>:-
coordsys_generic<BE>— when the backend is self-contained (no kinematic-tree parent, or it manages its own traversal); -
coordsys_child<BE>— when the backend follows the parent-relative pattern and you derived it fromcoordsys_child_backend<CT>instead. The wrapper then inherits ready-made(parent, tf_data_to_parent)and(WCS, tf_data_to_parent)constructors for free.
-
The wrapper inherits from coordsys<CT>, so the resulting type is accepted anywhere
a coordinate system is.
Step 1 — Write the backend
template <nin::coordsys_traits CT>
class my_backend : public nin::coordsys_backend<CT>
{
nin::coordsys<CT> parent_cs; // optional, stored as linked_copy()
// ... whatever else the backend needs (sensor handle, calibration, …)
public:
explicit my_backend(nin::coordsys<CT> const& parent)
: parent_cs { parent.linked_copy() }
{ }
CT::tf_data_type offset(unsigned hop_limit) const override
{
// Local transformation from *this* coordinate system to parent_cs.
CT::tf_data_type local = /* … */;
// Compose with the parent's transformation toward WCS. Forward
// hop_limit unchanged: coordsys::tf_to_WCS() decrements and checks it.
return CT::compose_tf(parent_cs.tf_to_WCS(hop_limit), local);
}
std::shared_ptr<nin::coordsys_backend<CT>> clone() const override
{
return std::make_shared<my_backend>(parent_cs);
}
};
Three rules survive every backend you will ever write:
- Store referenced coordinate systems as
linked_copy() -
A linked copy shares the backend pointer, so when the user moves the parent, your backend sees the new pose without anyone having to refresh the link.
- Never throw
hop_limit_exceeded_erroryourself -
The public
coordsys::tf_to_WCS()already decrementshop_limitbefore every call intooffset()and throws when the counter reaches zero. Youroffset()only has to forwardhop_limitto whatever furthertf_to_WCS()calls it makes. - Make
clone()a deep copy -
Two coordinate systems that compare unequal must not share mutable state. The only way to legitimately share a backend is through
linked_copy().
Step 2 — Pair it with a coordsys
If the backend needs no extra members on the coordinate system, a using alias is
all you need:
template <nin::coordsys_traits CT>
using my_coordsys = nin::coordsys_generic<my_backend<CT>>;
If the coordinate system itself needs more API — for example lock()/unlock() to
satisfy the Lockable named requirement — wrap it in a dedicated class:
template <nin::coordsys_traits CT>
class my_coordsys : public nin::coordsys_generic<my_backend<CT>>
{
using Base = nin::coordsys_generic<my_backend<CT>>;
public:
using Base::Base; // inherits the perfect-forwarding constructor
// domain-specific helpers, forwarding through operator-> if needed:
void recalibrate() { this->operator->()->recalibrate(); }
};
For the parent-relative pattern, swap coordsys_generic for coordsys_child and
derive the backend from coordsys_child_backend<CT> instead. The construction syntax
your users see (my_coordsys cs {parent, tf}) then matches every other child
coordinate system in the library.
Worked examples to copy from
-
Tutorial 202 —
coordsys_cutoff, a backend that short-circuits tree traversal when locked, wrapped in acoordsys_genericthat addslock()/unlock(). -
In-tree:
coordsys_mujoco_backendinsrc/pos/mujoco.c++— reads pose from a MuJoCo simulation, used throughcoordsys_generic.