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

71 lines
5.5 KiB
Markdown

## 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.