Navigation
The Navigation module lets an Inverse3 cursor move the application's workspace — sometimes called rate-control locomotion or "gamepad-like drift". Instead of the cursor hitting a hard wall at the edge of the physical workspace, it enters a soft shell where its distance from a virtual centre is mapped to a velocity that slides the whole workspace. The further the cursor pushes, the faster the scene scrolls.
The primary (and currently only) behaviour is Bubble Navigation. The bubble shape is defined using an SDF primitive — see What is an SDF? for the concept.
Bubble Navigation — concept
A virtual bubble is anchored around a centre point in the device's mount space. The cursor experiences three concentric zones:
┌──────────────────────────────────────────┐
│ WALL ZONE │ cursor beyond outer shell
│ ┌────────────────────────────────────┐ │
│ │ VELOCITY ZONE │ │ soft shell → scene moves
│ │ ┌──────────────────────────────┐ │ │
│ │ │ DEAD ZONE (inside) │ │ │ no scene movement
│ │ │ │ │ │
│ │ │ ● centre │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ bubble surface │ │
│ └────────────────────────────────────┘ │
│ outer boundary │
└──────────────────────────────────────────┘
| Zone | Cursor position | Behaviour |
|---|---|---|
| Dead zone | Inside the bubble | No navigation. Light damping. You can manipulate the scene freely. |
| Velocity zone | Between the surface and the outer boundary | Distance → velocity curve moves the workspace in the cursor's direction. |
| Wall zone | Past the outer boundary | A hard spring pushes the cursor back in and navigation velocity saturates. |
A spring-damper haptic force is applied across all three zones — you feel the surface, the drift shell, and the outer wall.
Quick start — enable bubble navigation
Navigation is a persistent, one-shot configuration — send it once and it stays active until you explicitly stop it, restart the service, or close the session.
Start (minimal — spherical bubble with default sizing)
- WebSocket
- HTTP
{
"inverse3": [
{
"device_id": "04C3",
"configure": {
"navigation": { "mode": "bubble" }
}
}
]
}
curl -X POST "http://localhost:10001/inverse3/04C3/config/navigation?session=:0" \
-H "Content-Type: application/json" \
-d '{"mode": "bubble"}'
Stop
- WebSocket
- HTTP
{
"inverse3": [
{
"device_id": "04C3",
"configure": {
"navigation": { "mode": "disabled" }
}
}
]
}
curl -X DELETE "http://localhost:10001/inverse3/04C3/config/navigation?session=:0"
Other HTTP routes
| Method | Path | Purpose |
|---|---|---|
GET | /{type}/{id}/config/navigation?session=<expr> | Current navigation config + state |
POST | /{type}/{id}/config/navigation?session=<expr> | Start or update navigation |
DELETE | /{type}/{id}/config/navigation?session=<expr> | Stop navigation |
Bubble shape catalogue
The bubble's dead zone is described by a signed-distance-function (SDF) shape. Different shapes give different navigation feels — a sphere for isotropic drift, an ellipsoid or box to favour certain axes, a capsule for corridors.
Sphere (default)
{
"shape": {
"primitive": "sphere",
"parameters": { "r": 0.05 }
}
}
Ellipsoid — wider in X/Z than Y
Use when horizontal drift should feel looser than vertical:
{
"shape": {
"primitive": "ellipsoid",
"parameters": { "a": { "x": 0.06, "y": 0.03, "z": 0.06 } }
}
}
Rounded box — rectangular dead zone with soft corners
{
"shape": {
"primitive": "rounded_box",
"parameters": { "b": { "x": 0.04, "y": 0.02, "z": 0.04 }, "r": 0.008 }
}
}
Capsule — elongated corridor
Two end points a/b plus a radius r:
{
"shape": {
"primitive": "capsule",
"parameters": {
"a": { "x": 0.0, "y": -0.03, "z": 0.0 },
"b": { "x": 0.0, "y": 0.03, "z": 0.0 },
"r": 0.04
}
}
}
Full example with custom sizing and motion feel:
{
"inverse3": [
{
"device_id": "04C3",
"configure": {
"navigation": {
"mode": "bubble",
"bubble": {
"shape": {
"primitive": "ellipsoid",
"parameters": { "a": { "x": 0.06, "y": 0.03, "z": 0.06 } }
},
"velocity_zone_width": 0.025,
"max_velocity": 1.0,
"velocity_ease": "quadratic_in",
"workspace_bounded": true
}
}
}
}
]
}
Velocity curve
The velocity-zone distance is mapped through an easing curve before being turned into workspace velocity. Pick the easing that matches how you want the scene to accelerate as the user pushes further into the shell.
velocity_ease | Feel | When to use |
|---|---|---|
linear | Constant ramp | Predictable, good default |
quadratic_in | Slow start, faster push | Precise near the surface, fast long travel |
cubic_in | Very slow start | Very precise, long travel ramps up |
sine_out | Smooth fast start, plateaus near outer | Responsive, caps gently |
quadratic_out | Fast at entry, comfortable long-distance | Fast nudge, comfortable cruising |
{ "velocity_ease": "quadratic_in", "max_velocity": 1.5 }
Centre modes
The bubble's centre can follow the cursor, snap to it, or stay fixed in mount space.
center_mode | Behaviour | Use case |
|---|---|---|
auto_follow (default) | Drifts slowly toward the cursor at center_drift_speed when the cursor is inside the bubble | General-purpose navigation — the centre "relaxes" back under the cursor between pushes |
fixed | Centre never moves automatically | Anchored navigation around a specific reference point |
track_cursor | Centre snaps to the cursor each tick | Teleport-style movement — the cursor is always at the bubble centre |
{ "center_mode": "fixed" }
In every mode: if the cursor pushes past the outer boundary, the centre tracks the cursor to keep the bubble around it and prevent the cursor from escaping.
Workspace bounding
Most devices have a limited physical reach. When workspace_bounded is set,
the bubble centre is kept inside the device's physical workspace so the
resulting navigation doesn't drive the cursor outside of its reachable volume.
{ "workspace_bounded": true }
If the initial centre lies outside the workspace, the module eases it back
inside over time (workspace_transition_speed / workspace_transition_ease)
instead of snapping — avoiding a haptic jerk on start.
Avatar boundaries
When avatar_boundary_enabled is set, the final avatar world position
(workspace + cursor) is clamped inside an arbitrary SDF shape. Useful for
containing the user inside a room, corridor, vehicle cockpit, etc., without
scripting per-axis clamps.
{
"avatar_boundary_enabled": true,
"avatar_boundary": {
"primitive": "box",
"parameters": { "b": { "x": 5.0, "y": 3.0, "z": 5.0 } }
}
}
The avatar_boundary shape accepts the same primitives as the bubble shape
(sphere, ellipsoid, rounded_box, capsule, box, ...).
Collision response
The bubble reacts to external cursor forces exceeding collision_threshold:
- The velocity zone temporarily inflates (up to
collision_inflate_scaletimesvelocity_zone_width) to give the user more room to manoeuvre around the obstacle. - With
stop_at_collision, the bubble centre refuses to drift in the direction of the collision force, preventing the user from dragging the workspace into a hard wall.
{ "stop_at_collision": true, "collision_threshold": 0.5, "collision_inflate_scale": 2.0 }
Parameters reference
Most commonly tuned
| Parameter | Default | Description |
|---|---|---|
shape | {sphere, r=0.05} | SDF shape defining the dead zone |
velocity_zone_width | 0.03 m | Thickness of the rate-control shell |
max_velocity | 1.0 m/s | Maximum navigation velocity |
velocity_ease | "linear" | Distance → velocity easing curve |
center_mode | "auto_follow" | How the centre tracks the cursor |
workspace_bounded | false | Clamp centre to device physical workspace |
Full parameter list
| Field | Type | Default | Description |
|---|---|---|---|
center | vec3 | (0,0,0) | Initial bubble centre in mount space |
shape | shape | {sphere, r=0.05} | Dead-zone SDF shape |
velocity_zone_width | float | 0.03 | Width of the rate-control shell (m) |
max_velocity | float | 1.0 | Max navigation velocity (m/s) |
velocity_ease | string | "linear" | Distance→velocity easing |
reset_velocity_on_entry | bool | true | Zero the accumulated speed when the cursor re-enters the velocity zone |
bump_width | float | 0.003 | Tactile bump at the surface (m) |
bump_stiffness | float | 500.0 | Surface bump spring constant |
spring_inner | float | 4.0 | Spring at the bubble centre |
spring_surface | float | 7.0 | Spring at the surface |
spring_outer | float | 12.0 | Spring at the outer boundary |
wall_stiffness | float | 700.0 | Hard-wall spring past the outer boundary |
damping_inner | float | 0.2 | Damping at the centre |
damping_surface | float | 0.7 | Damping at the surface |
damping_outer | float | 5.0 | Damping at the outer boundary |
rotation_enabled | bool | false | Apply the workspace rotation to the navigation direction |
scale_enabled | bool | false | Apply the workspace scale to the navigation velocity |
center_mode | enum | "auto_follow" | "auto_follow" / "fixed" / "track_cursor" |
center_drift_speed | float | 0.03 | Drift speed in auto_follow mode (m/s) |
workspace_bounded | bool | false | Clamp bubble centre to the device workspace |
workspace_transition_speed | float | 1.2 | Speed of the initial clamp transition |
workspace_transition_ease | string | "quadratic_in_out" | Easing of the initial clamp transition |
stop_at_collision | bool | false | Block navigation in the collision direction |
collision_threshold | float | 0.001 | External force threshold to detect a collision (N) |
collision_inflate_scale | float | 2.0 | Multiplier for the velocity-zone width during a collision |
avatar_boundary_enabled | bool | false | Activate avatar-boundary clamping |
avatar_boundary | shape | {sphere, r=0.1} | SDF shape for the avatar-position constraint |
Validation rules
velocity_zone_width > 00 ≤ bump_width < velocity_zone_widthmax_velocity > 0spring_inner ≤ spring_surface ≤ spring_outerdamping_inner ≤ damping_surface ≤ damping_outercollision_threshold > 0,collision_inflate_scale ≥ 1.0- All speed values are non-negative
A POST or configure.navigation with invalid parameters is rejected and an
invalid-value event is emitted; the previous configuration remains active.
Events
| Event name | Fired when |
|---|---|
navigation-started | Navigation is activated on a device |
navigation-updated | Navigation config is updated while already active |
navigation-stopped | Navigation is stopped (explicit disable, DELETE, or session close) |
invalid-value | A navigation config is rejected by validation |
Known limitations
- Non-uniform scale + rotation: when both
rotation_enabledandscale_enabledare on, the velocity direction is slightly inaccurate — rotation is not applied to the scale axes. - Workspace-bounding transition: the smooth clamp-back transition only runs at initialisation. If the bubble centre drifts outside the workspace during normal use, the clamp snaps without easing.
- Per-device size scaling: bubble sizes (radius, zone width) are not scaled by the device's physical scale factor — Minverse and Inverse3 use the same absolute sizes, which may feel different on each.