Tutorial 5: Kinematic Trees
In this tutorial you will learn to work with coordsys_cutoff —
a special node that lets you freeze, snapshot, or re-attach parts of a coordinate system hierarchy at runtime.
The Setup
A coordinate system tree can grow arbitrarily deep.
Sometimes you need to temporarily isolate a subtree from the rest of the hierarchy,
for example to capture a snapshot while the rest of the tree updates,
or to re-root a kinematic chain onto a different parent.
coordsys_cutoff provides exactly this capability.
The Code
#include <print>
import ninbot;
int main()
{
using namespace nin;
using namespace nin::R3;
position_coordsys_child CS_A {WCS, { {2_m, 0_m, 0_m}, {} }};
coordsys_cutoff<position_coordsys_traits> cutoff {CS_A};
position_coordsys_child CS_D {cutoff, { {0_m, 0_m, 1_m}, {} }};
point P = {CS_D, {1_m, 0_m, 0_m}};
std::println("P in WCS (cutoff unlocked): {}", P.map_to(WCS));
cutoff.lock();
std::println("P in WCS (cutoff locked): {}", P.map_to(WCS));
cutoff.unlock();
position_coordsys_child CS_E {WCS, { {10_m, 0_m, 0_m}, {} }};
cutoff.set_parent(CS_E);
std::println("P in WCS (cutoff at CS_E): {}", P.map_to(WCS));
return 0;
}
Building the code
Save the example as kinematic-trees.c++ alongside this CMakeLists.txt:
cmake_minimum_required(VERSION 4.0)
project(kinematic-trees LANGUAGES CXX)
find_package(Ninbot REQUIRED)
add_executable(kinematic-trees kinematic-trees.c++)
target_link_libraries(kinematic-trees PRIVATE ninbot::ninbot)
Then configure and build:
cmake -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/ninbot/build
cmake --build build
./build/kinematic-trees
Walkthrough
Creating a cutoff node
position_coordsys_child CS_A {WCS, { {2_m, 0_m, 0_m}, {} }};
coordsys_cutoff<position_coordsys_traits> cutoff {CS_A};
coordsys_cutoff is a special node that introduces a re-attachable root in the coordinate tree.
Here it is inserted as a child of CS_A.
When unlocked (the default), it propagates transformations to its actual parent as usual.
Building a subtree below the cutoff
position_coordsys_child CS_D {cutoff, { {0_m, 0_m, 1_m}, {} }};
point P = {CS_D, {1_m, 0_m, 0_m}};
CS_D is a child of the cutoff node. P is a point in CS_D. While unlocked, the full transformation chain CS_D → cutoff → CS_A → WCS is used:
std::println("P in WCS (cutoff unlocked): {}", P.map_to(WCS));
Locking the cutoff
cutoff.lock();
std::println("P in WCS (cutoff locked): {}", P.map_to(WCS));
Locking the cutoff freezes everything above it in the tree. The cutoff now acts as a local root — CS_D and all its children are expressed only relative to the cutoff position, ignoring CS_A and everything above it.
cutoff.unlock();
Unlocking restores the full chain.
Re-attaching a cutoff node
position_coordsys_child CS_E {WCS, { {10_m, 0_m, 0_m}, {} }};
cutoff.set_parent(CS_E);
std::println("P in WCS (cutoff at CS_E): {}", P.map_to(WCS));
set_parent() changes the cutoff’s parent from CS_A to CS_E.
This effectively moves the entire subtree rooted at the cutoff to a different location in the tree.