Perception view
The operator's live 3D window into what a robot swarm perceives — the mapped structure, the areas of interest within it, and how fresh each part of the picture is. RC3 frames the surface and ships a reference renderer; an upstream system does the fusion.
Live · replay
A small sample scene replaying in-browser — a real renderer drawing real geometry, not invented telemetry. A swarm of platforms feeds one consolidated map of a mid-rise building: its window grid is structure (a mesh), with point clouds and a voxel floor plan over it, and AOIs highlighting specific windows. Drag to orbit; toggle layers; select an area of interest. Watch platforms drop out on a rolling cycle — their points fade and their source flips to `STALE` rather than holding a crisp last frame.
Bundled sample · replay
Feed posture
The `status` prop governs the whole view. `live` draws normally. When the consolidated feed stops, `holding` dims the scene and raises a banner with the age of the last good frame — a held picture can never read as live. `lost` replaces the scene with an unmistakable overlay. This is invariant 5 applied to space.
All sources fresh.
Feed paused — last good frame, clearly held.
Link down — no scene, no ambiguity.
What RC3 owns — and what it doesn't
Perception view is the trustworthy screen, not the map-builder. RC3 does not fuse, register, or run SLAM — it assumes an upstream system consolidates the swarm's sensors into one clean scene, the same way the Controller interface frames a physical gamepad. RC3 owns the input contract, the operator chrome, and a reference renderer; a vendor engine can take over the drawing through `renderDelegate` while keeping both.
- The input data contract — ROS 2 / Foxglove-shaped layers.
- Operator chrome — layer legend, AOI registry, per-source provenance, freshness.
- A dependency-free reference renderer: points, voxels, mesh, markers, live transforms.
- The AOI schema — the one piece RC3 defines natively.
- Sensor fusion, registration, SLAM — the clean scene arrives ready to display.
- High-scale rendering — LOD, octree streaming, mesh reconstruction (via `renderDelegate`).
- glTF / GLB / 3D Tiles binary decoding — a delegate concern.
- The coordinate frame's truth — RC3 pins one and trusts the declared transforms.
Anatomy
A spatial canvas in the centre with chrome pinned to the edges. The geometry comes from the scene; the operator semantics come from the chrome.
The top-left chip names the canonical frame (ENU / NED / z-up / y-up). Sources declare their transform into it; mismatch is the number-one integration failure, so the frame is always on screen.
Per-layer toggles with a kind swatch and element count. Points, occupancy voxels, and structural mesh switch independently — the operator declutters without losing the others.
Areas of interest with kind tone (objective / hazard / inspect / marker) and confidence. Selecting one rings it in Ember in the canvas. The one schema RC3 defines natively.
Every contributing platform, its identity colour, and its freshness — LIVE or STALE with age. The picture is never anonymous; the operator can see who saw what, when.
Geometry from a source that stops reporting fades and desaturates rather than vanishing or staying crisp. Stale data is visible as stale — invariant 5 applied to space.
When the consolidated feed pauses or drops, the whole view raises an unmistakable overlay with the age of the last good frame. A frozen scene can never pass for live.
A dependency-free canvas-2D projector: points, voxels, mesh wireframe, AOI markers, live per-source transforms. Lean by design — heavy rendering belongs to a delegate.
An escape hatch: pass `renderDelegate` to plug a vendor 3D engine into the centre. RC3 keeps drawing the chrome around it, so the contract and operator semantics are unchanged.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| scene | PerceptionScene | — | The consolidated scene from upstream — frame, sources, layers, and AOIs. RC3 displays it; it does not fuse or register. |
| status | "live" | "holding" | "lost" | "live" | Overall feed posture. holding dims the scene and raises a last-good-frame banner; lost replaces it with an overlay. Honours invariant 5. |
| hiddenLayerIds | string[] | — | Controlled — layer ids the operator has toggled off. Pair with onToggleLayer. |
| onToggleLayer | (layerId: string) => void | — | Fired when a legend entry is tapped. The consumer owns the hidden set. |
| selectedAoiId | string | null | null | Controlled — the focused AOI. The selected marker is ringed in Ember on the canvas. |
| onSelectAoi | (aoiId: string | null) => void | — | Fired when an AOI is tapped. Passes null when the active AOI is tapped again. |
| staleAfterSeconds | number | 3 | Source age beyond which geometry visibly decays and provenance flips to STALE. Tune to the feed's expected cadence. |
| autoRotate | boolean | false | Slowly orbit the reference camera. Respects prefers-reduced-motion and pauses while the operator drags. Ignored when a delegate renders. |
| renderDelegate | (ctx: PerceptionRenderContext) => ReactNode | — | Escape hatch — render the geometry with a vendor 3D engine instead of the built-in renderer. RC3 still draws the chrome. |
| bareChrome | boolean | false | Drop the layer legend, AOI registry, and provenance panels — e.g. for a compact tile. |
| className | string | — | Forwarded to the root container. |
Types
PerceptionSceneThe whole consolidated scene. The renderer auto-fits the camera to its bounds.
interface PerceptionScene {
frame: CoordinateFrame; // "ENU" | "NED" | "z-up" | "y-up"
sources: PerceptionSource[];
layers: SceneLayer[];
aois?: AreaOfInterest[];
}SceneLayerThe three geometry layers, each tagged with an optional `sourceId` so freshness can decay it independently.
type SceneLayer = PointCloudLayer | OccupancyLayer | MeshLayer;
// points → sensor_msgs/PointCloud2 · Foxglove PointCloud
// occupancy → nav_msgs/OccupancyGrid · OctoMap · Foxglove Grid
// mesh → pre-tessellated glTF / 3D Tiles backdropPerceptionSourceA contributing platform and how fresh its contribution is. `ageSeconds` drives decay and the holding state.
interface PerceptionSource {
id: string; // "UGV-04"
label?: string;
ageSeconds: number; // since last fresh geometry
transform?: FrameTransform; // into the scene frame
}AreaOfInterestThe operator-semantic schema RC3 defines natively. Layered over the geometry.
interface AreaOfInterest {
id: string;
label: string;
kind: "objective" | "hazard" | "inspect" | "marker";
position: Vec3;
sourceId?: string;
confidence?: number; // 0–1
ageSeconds?: number;
}FrameTransformA source's pose in the canonical frame. The reference renderer honours translation + yaw; a delegate may use the full pose.
interface FrameTransform {
translation: Vec3; // metres, into the scene frame
yaw?: number; // radians about the up axis
}PerceptionRenderContextHanded to a `renderDelegate` so a vendor engine has everything the chrome knows.
interface PerceptionRenderContext {
scene: PerceptionScene;
hiddenLayerIds: Set<string>;
selectedAoiId: string | null;
staleAfterSeconds: number;
status: PerceptionStatus;
}Wiring
Subscribe to the upstream scene, hold the hidden-layer set and the selected AOI, and feed each source's age from its last update. When a source stops, let its age climb rather than freezing the picture; when the whole feed stops, raise `status`.
// Live scene from an upstream perception subscription
<PerceptionView
scene={{
frame: "ENU",
sources: feed.platforms.map((p) => ({
id: p.id,
ageSeconds: secondsSince(p.lastFrameAt),
transform: { translation: p.position, yaw: p.yaw },
})),
layers: feed.layers, // points / occupancy / mesh
aois: feed.areasOfInterest,
}}
status={feed.connected ? "live" : "holding"}
hiddenLayerIds={hidden}
onToggleLayer={toggleLayer}
selectedAoiId={selectedAoi}
onSelectAoi={setSelectedAoi}
/>// Hand the drawing to a vendor 3D engine, keep RC3's chrome
<PerceptionView
scene={scene}
renderDelegate={({ scene, hiddenLayerIds, selectedAoiId }) => (
<YourEngine
scene={scene}
hidden={hiddenLayerIds}
focus={selectedAoiId}
/>
)}
/>Behavioural rule
Telemetry never silently stale
Perception is telemetry with a shape. Geometry from a source that stops reporting fades rather than staying crisp; per-source freshness is always on screen; and a held or lost feed raises an unmistakable overlay. A frozen scene can never pass for a live one.
Accessibility
| Figure role | The view carries `role="figure"` labelled by the coordinate-frame chip, so assistive tech announces a named spatial figure rather than a bare canvas. |
|---|---|
| Operable chrome | Layer toggles and AOI entries are real buttons with `aria-pressed` state — reachable, focusable, and operable without the canvas. The 3D canvas is a visual amplifier, not the only path. |
| Freshness not colour-only | Stale sources and AOIs carry literal `STALE {age}` / age text alongside the dimmed treatment. Degradation is legible without relying on the danger colour. |
| Reduced motion | `autoRotate` checks `prefers-reduced-motion` and does not animate the camera when the operator has asked the system to reduce motion. |
| Held state is explicit | Holding and lost states render text — `HOLDING · last good Ns ago`, `FEED LOST` — not just a colour wash, so the posture is unambiguous to every operator. |
JavaFX
Ships in the PRIZM JavaFX library for thick-client C3 apps as Rc3PerceptionView (extends StackPane). Run the gallery to see it natively.
import design.prizm.fx.rc3.Rc3PerceptionView;
Rc3PerceptionView()| Member | Type | Default | Description |
|---|---|---|---|
| CoordinateFrame | enum | — | ENU / NED / Z_UP / Y_UP — the scene frame, shown in the top chip. |
| Vec3 / FrameTransform / PerceptionSource | record | — | Scene-frame point, rigid transform, and a contributing platform with freshness. |
| SceneLayer | sealed interface | — | PointCloudLayer / OccupancyLayer / MeshLayer — the geometry the renderer draws. |
| AreaOfInterest / AoiKind | record / enum | — | Operator semantics over the geometry (objective / hazard / inspect / marker). |
| PerceptionScene | record(frame, sources, layers, aois) | — | The consolidated scene from upstream. |
| PerceptionStatus | enum | — | LIVE / HOLDING / LOST — a held or lost feed raises an unmistakable overlay. |
| setScene | (PerceptionScene) → void | — | The scene to render. |
| setStatus / setStaleAfterSeconds / setAutoRotate / setBareChrome | → void | — | Feed posture, decay threshold, orbit toggle, and hide-panels mode. |
| setSelectedAoiId / setOnSelectAoi / setOnToggleLayer | → void | — | Selection + layer-toggle state and optional listeners (interactive by default). |
| RenderDelegate | @FunctionalInterface | — | Drop in a vendor 3D engine instead of the reference renderer; RC3 keeps drawing the chrome. |
A JavaFX Canvas reference renderer projects the scene with an orbit camera (drag / scroll-zoom / auto-rotate); chrome mirrors the web (frame chip, layer legend, source provenance, AOI registry, holding / lost overlay). Honours invariant 5 — stale geometry decays and a frozen scene never reads as live. JavaFX divergence: the renderer's neutral colours are dark-tuned constants (the web reads live CSS vars). Mirrors components/rc3/perception-view.tsx.
Usage
Reach for the Perception view when the operator needs to see — and command through — what the swarm perceives in space. Feed it a consolidated scene from upstream; do not expect RC3 to fuse or register. Keep each source's `ageSeconds` honest so decay and the holding state work; raise `status` to `holding` or `lost` when the feed itself degrades. Start with the reference renderer; reach for `renderDelegate` only when scale demands a dedicated engine — and keep the chrome either way.