UsdLayerManager/openspec/changes/viewport-custom-camera/design.md

5.5 KiB

Context

The USD Layer Manager application currently has a viewport panel (ViewportPanel) driven by a built-in ViewportCamera class. The camera supports orbit, pan, zoom, and framing via bounding box. Input handling uses a direct LMB/MMB/scroll mapping without modifier keys. There is no concept of switching to a USD stage camera prim — the viewport always renders from the free camera's viewpoint.

The target users are Maya artists who expect Alt+button viewport navigation and the ability to view through authored cameras in the USD stage. The current input scheme is a friction point, and the inability to look through stage cameras limits creative preview workflows.

Current State

  • ViewportCamera stores eye, focal point, orbit angles (yaw/pitch), distance, FOV, clip planes, aspect ratio
  • ViewportPanel::HandleInput() maps: LMB=orbit, MMB=pan, scroll=zoom, Shift+LMB=pan
  • ViewportPanel::FrameScene() frames on the entire stage bounds
  • No USD camera prim integration; no per-prim framing; no hotkey support

Goals / Non-Goals

Goals:

  • Replace the viewport input mapping with Maya-style Alt+button navigation (Alt+LMB orbit, Alt+MMB pan, Alt+RMB dolly, scroll zoom)
  • Allow switching between the built-in free camera and any UsdCamera prim on the stage
  • Support framing the viewport on the selected prim's bounding box (F key) and the full stage (A key)
  • Add a camera selector dropdown in the viewport panel UI

Non-Goals:

  • Authoring or editing USD camera prims through the viewport (that belongs in the Property Panel)
  • Camera animation or sequencing (playblast, camera sequencer)
  • Multiple viewports or split-view layouts
  • Custom key binding configuration UI (hardcoded Maya-style for now)

Decisions

D1: Alt+button navigation with scroll exception

Decision: Use Alt+LMB for orbit, Alt+MMB for pan, Alt+RMB for dolly. Mouse scroll zooms without Alt (matching Maya behavior).

Rationale: This is the standard Maya viewport convention. Scroll zoom without Alt is expected because scroll has no other viewport conflict.

Alternative considered: Keep current LMB/MMB/scroll mapping as an option — rejected to avoid maintaining two input schemes and confusing users about the default.

D2: Dual-mode ViewportCamera (free vs USD camera)

Decision: Extend ViewportCamera with an enum mode (Free / UsdCamera). In UsdCamera mode, the view/projection matrices are derived from the UsdCamera prim's transforms and attributes; the orbit/pan/zoom controls are disabled. In Free mode, behavior is unchanged.

Rationale: Keeping both modes in one class avoids duplicating the projection math and makes switching seamless. Disabling manual navigation when looking through a USD camera matches Maya's behavior (you navigate the camera's transform instead, which is out of scope for now).

Alternative considered: Separate UsdCameraAdapter class that wraps a UsdCamera into the same interface — more complex, no clear benefit until camera manipulation is needed.

D3: Camera prim discovery and selection UI

Decision: Traverse the stage for all UsdCamera-typed prims on each frame (or on stage change). Present them in an ImGui combo dropdown above the viewport canvas. The first entry is always "Free Camera".

Rationale: Simple implementation; camera lists in typical USD scenes are small. Cache invalidation is handled by re-traversing on stage change notifications.

Alternative considered: Maintain a persistent camera registry — over-engineered for the current scope.

D4: Frame selected via SceneHierarchyPanel selection

Decision: The ViewportPanel holds a callback/string for the selected prim path. Application wires the SceneHierarchyPanel selection callback to update this path. Pressing F computes the selected prim's bounding box and calls FrameBoundingBox. Pressing A calls the existing FrameScene.

Rationale: Minimal coupling; ViewportPanel doesn't need to know about SceneHierarchyPanel directly. The Application layer already owns both panels and can wire them.

D5: Bounding box computation for selected prim

Decision: Use UsdGeomBBoxCache to compute the world-space bounding box of the selected prim and its descendants, then call ViewportCamera::FrameBoundingBox.

Rationale: This is the standard OpenUSD approach for bounding box queries and handles instancing, transforms, and purpose correctly.

Risks / Trade-offs

  • [Alt key conflicts with ImGui] → ImGui may consume Alt for menu activation. Mitigation: Set ImGuiConfigFlags_NoNav or handle Alt detection via GLFW directly before ImGui processes it. If ImGui captures Alt, fall back to detecting Alt via io.KeyAlt and consuming the input in the viewport's HandleInput().
  • [Camera prim traversal cost on large stages] → Re-traversing every frame is wasteful for stages with thousands of prims. Mitigation: Cache the camera list and invalidate on stage change only. For initial implementation, traversal per frame is acceptable; optimize later.
  • [USD camera attribute coverage] → Not all UsdCamera attributes (e.g., lens distortion, stereo) can be represented in the current ViewportCamera projection. Mitigation: Support core attributes (focalLength, horizontalAperture, clippingRange, projection type) and ignore unsupported attributes gracefully.
  • [Free camera state lost on switch] → When switching from USD camera back to free camera, the free camera's last position is restored. Mitigation: Store the free camera state separately and restore it on switch-back.