Skip to main content
Version: 3.5.x

Mount & Workspace Transforms

Two transforms position the device in application space. Understanding the pipeline helps you pick the right one.

Coordinate pipeline

StageWhat it doesHow to set itPersistent?
BasisRemaps axes (e.g., Z-up → Y-up)session.configure.basis — see tutorial 08 (HTTP remote config)Yes
Mount transformPhysical mounting offset of the device (arm position, rotation, scale)configure.mount or configure.preset — see tutorial 08 (HTTP remote config)Yes
Workspace transformCamera / scene navigation driftcommands.set_transform — see tutorial 09 (WebSocket per-tick streaming)Yes (sticky)

Mount transform

The mount describes where the device physically sits — its arm offset, orientation, and scale. It's set once (or when the user changes the physical setup) and rarely updated at runtime.

Presets (factory configurations)

Instead of computing the mount yourself, pick a named preset:

PresetOrigin placementArm facing
defaultsDevice baseForward (arm front)
arm_frontDevice baseForward
arm_front_centeredWorkspace centreForward
led_frontDevice baseLED side forward
led_front_centeredWorkspace centreLED side forward
customManual — set mount yourself

A preset sets the mount, basis, and workspace origin in one shot.

Setting the mount directly

{
"inverse3": [{
"device_id": "049D",
"configure": {
"mount": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}]
}
mount and preset are mutually exclusive

Sending both in the same message is ambiguous — the service rejects it. Choose one: a named preset (which sets the mount for you) or an explicit mount transform.

Command vs snapshot asymmetry

When sending a mount, wrap the transform: { "mount": { "transform": { … } } }. When reading a mount from the snapshot, it's flat: { "mount": { "position": …, "rotation": …, "scale": … } }. This is by design — commands use the unified command_data wrapper; snapshots serialize the core::transform directly.

Workspace transform

The workspace transform is a runtime drift applied on top of the mount. Its typical use case is scene navigation — panning, rotating, or zooming the application's view while the device stays physically in place.

Both mount and workspace transforms are persistent — the service remembers the last value you sent. The difference is purpose and performance:

  • Mount is designed to be set once and stay — it describes the physical setup and is optimised for infrequent updates.
  • Workspace transform is designed to be streamed at high frequency (e.g., every frame during camera navigation) — the internal pipeline is optimised for this update rate.
{
"inverse3": [{
"device_id": "049D",
"commands": {
"set_transform": {
"transform": {
"position": { "x": 1.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}]
}
Scaling affects force output — often in the way you want

Scaling the mount or workspace changes the force the device renders, because scaling affects the penetration depth used to compute the contact force. For example, a 2x scale roughly halves the penetration depth (the virtual scene is twice as large relative to the device's travel), so the resulting force is roughly half of what you'd feel at 1x. Conversely, a 0.5x scale roughly doubles the force.

This is usually the behavior you want: zooming out of a haptic scene naturally produces weaker contacts (you're "farther away"), and zooming in makes them stronger. If that's not what you want — e.g., you need stiffness to stay constant as the user zooms — compensate by scaling your force gains inversely.

Use the Navigation module for automated drift

If you need continuous locomotion (bubble navigation, rate-control drift), use the Navigation module instead of manually streaming set_transform every tick — it handles the physics, haptic feedback, and workspace bounding for you.