Skip to content

Picking and pivots

A pivot is the point around which navigation rotates. Rotatrix chooses it using scene information supplied by the application: a pick locates geometry under a screen position, and bounds describe the extent of a model or selection.

The demos answer those geometry queries and display the pivot returned by Rotatrix. The integration supplies facts; Rotatrix’s navigation policy decides which fact to use.

Run the demo, press D, then navigate over a cube and empty space. Click to select a cube and repeat. Compare reported hits and bounds with the scene. A green marker shows the server-supplied pivot using the shared disc appearance.

C++ demo diagnostics showing a 2D crosshair at the picked screen position, a 3D crosshair at the surface hit on a green cube, and the matching yellow pick.cursor result row.

In this C++ demo capture, the 2D crosshair marks the screen position picked, while the 3D crosshair marks the surface hit. The matching yellow pick.cursor diagnostic row reports the hit’s world-space position and the hit object’s bounds, and identifies it as the returned candidate. pick.viewport_center is skipped because an earlier candidate supplied a result. These crosshairs visualize the pick; the server-chosen pivot is displayed separately as a green disc.

Repeat with the cursor outside the viewport and with tall and wide windows. Diagnostics identify evaluated alternatives; a skipped alternative needs a separate query or resolver test.

NavigationSession calls the adapter’s query-capture method on the application thread. The capture saves camera, viewport size and cursor state. The SDK resolves requested facts lazily, stopping ordered alternatives at the first available result.

Capture live state on the thread required by the application API. Let the SDK complete the correlated response; do not send a second response from the adapter.

Resolve a pick only when requested and return an unavailable result when there is no hit. This lets Rotatrix try another source for the pivot. See the query result and completion contract for exact result values and low-level listener behavior.

The NavigationSession calls MyQueryCapture.resolve(self, name) with a requested fact name. The integration guide’s query overview shows how its constructor saves self.app, self.camera, viewport dimensions and cursor. The resolver uses that saved state to answer each request. bounds_value() converts application bounds to minimum and maximum world-space points. UNAVAILABLE indicates that a requested fact cannot be supplied:

if name in ('model.bounds', 'selection.bounds'):
return bounds_value(self.app.get_bounds(name == 'selection.bounds'))
if name in ('pick.cursor', 'pick.cursor.selection',
'pick.viewport_center', 'pick.viewport_center.selection'):
pixel = self.cursor if name.startswith('pick.cursor') else (self.width/2, self.height/2)
if (pixel is None or not (0 <= pixel[0] < self.width and 0 <= pixel[1] < self.height)
or (name.endswith('.selection') and self.app.selected is None)):
return UNAVAILABLE
hit = self.app.pick(pixel, selection_only=name.endswith('.selection'))
if hit is not None:
_, point, box = hit
return {'point': point, 'bounds': bounds_value((box.minimum, box.maximum)), 'markerPosition': pixel}
return {'markerPosition': pixel}
return UNAVAILABLE
QueryApplication operation
pick.cursorPick through the cursor, provided it is inside the viewport.
pick.viewport_centerPick through the viewport center independently of the cursor.
Either name with .selectionRestrict the pick to selected geometry.
model.bounds / selection.boundsReturn scene or selection world-space bounds.

Perform picking only when the resolver requests it. An unavailable cursor, selection or hit lets the server try its next candidate. Viewport cursor values use normalized [-1, 1] coordinates, with Y up; the native picker uses its own pixel coordinates.

MyApplication.pick() in application.py uses Panda3D collision picking and returns a world-space point and transformed bounds. A tested miss returns only markerPosition; a skipped test returns UNAVAILABLE. The SDK collects the marker from the result and omits it from the wire response.

Use the application’s native hit-test API, a geometric raycast, or an equivalent operation that produces the requested world-space surface hit. When supported, visible bounded construction or datum planes and an active sketch plane can participate. Within the applicable candidate set, prevent back faces of closed solids from winning through foreground geometry. Selection-only queries first restrict that candidate set to selected geometry; unselected geometry does not occlude those hits.

Keep each result tied to its query snapshot. Do not recompute the selected fact during a gesture unless the server queries it again. Returned optional bounds describe the hit object or body, not an unrelated object or the entire selection.

The marker shows the pivot Rotatrix actually chose, which can differ from the geometry under the cursor. NavigationSession passes that point to the adapter for display in the captured viewport. It later passes no point to clear the marker when navigation is cleaned up.

The NavigationSession calls MyNavigationAdapter.show_pivot() with the captured MyApplication as context, and a world-space point or None as point:

def show_pivot(self, context, point):
context.set_pivot_marker(point)

The application’s set_pivot_marker() displays the point; None clears it.

See pivot display guidance when adapting the display to your application.

Check cursor/center independence, selection filtering, transformed bounds and picks, both projections, and portrait/landscape aspect ratios. Validate native display scaling and split viewports in your target application. A behind-eye pivot in orthographic view is currently rejected by the server.

For automated commands and coverage, use the demo READMEs: Python, C#, TypeScript, and C++.