90 lines
6.3 KiB
Markdown
90 lines
6.3 KiB
Markdown
## Context
|
|
|
|
The renderer (`UsdSceneRenderer`) already has a working pattern for GL line overlays drawn into the FBO: a shared GLSL program (`#version 130`, `in vec3 position; uniform mat4 mvpMatrix; uniform vec4 color`) is compiled once and used by both `DrawAxis` and `DrawBoundingBoxes`. The FBO can be re-bound at any time via `BindDrawTarget()`/`UnbindDrawTarget()`. The `TransformManipulator` already selects and edits any `Xformable` prim — no changes to it are needed.
|
|
|
|
`UsdGeomCamera` inherits from `UsdGeomXformable`. Every camera prim carries lens data (`focalLength`, `horizontalAperture`, `verticalAperture`, `clippingRange`) and an `Xformable` world transform. `GfFrustum::ComputeCorners()` returns the 8 world-space corners of a frustum given a `GfCamera`.
|
|
|
|
`ViewportPanel::Render` already has two FBO overlay call sites (`DrawAxis`, `DrawBoundingBoxes`) and two ImGui DrawList overlay call sites (gizmo, selection rect). The camera wireframe follows the FBO overlay pattern.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
- Frustum wireframe drawn in 3D world space inside the FBO for every `UsdGeomCamera` prim present in the current stage at time `UsdTimeCode::Default()`.
|
|
- Three visual states: inactive (muted grey), active/viewport-driving (cyan), selected (yellow/orange matching selection accent).
|
|
- Click in the viewport selects a camera prim when the mouse is within a pick threshold of a projected frustum segment (tested before the geometry pick).
|
|
- Selected camera integrates with the existing `TransformManipulator` (translate/rotate).
|
|
|
|
**Non-Goals:**
|
|
- No near/far clip plane rectangle beyond a fixed display depth (far clip could be millions of units; showing a visualisation cap is acceptable).
|
|
- No billboard label/text annotation.
|
|
- No custom manipulator handles specific to camera FOV adjustment.
|
|
- No frustum change when dragging the manipulator (camera shape updates next frame via normal USD dirty propagation).
|
|
|
|
## Decisions
|
|
|
|
### D1 — GL overlay into FBO, not ImGui DrawList
|
|
|
|
**Decision**: Draw camera wireframes using the existing GLSL line program and `BindDrawTarget`/`UnbindDrawTarget`, the same as `DrawAxis` and `DrawBoundingBoxes`.
|
|
|
|
**Rationale**: 3D GL lines drawn into the FBO participate in the depth buffer and are occluded by geometry correctly. ImGui screen-space lines would always appear on top of everything. The existing infrastructure (VAO, VBO, GLSL program, FBO helpers) already supports this pattern at zero additional complexity.
|
|
|
|
---
|
|
|
|
### D2 — Frustum geometry: near-plane pyramid + body box
|
|
|
|
**Decision**: The camera wireframe consists of two parts:
|
|
1. **Frustum pyramid**: the camera apex (position) connected by 4 lines to the 4 corners of a display near-plane quad. The near clip value from the `UsdGeomCamera` is used but clamped to a minimum of `0.01` to keep it visible.
|
|
2. **Body box**: a small fixed-size world-space box (side ≈ `frustumScale * 0.15`) centred at the camera position, representing the camera body. This gives a tangible handle at the camera origin that is easy to click.
|
|
3. **Up arrow**: a short line from the body box top face indicating the camera's up direction, matching the camera local Y axis.
|
|
|
|
The far frustum rectangle is **not drawn** — far clip distances are often enormous and would produce confusing off-screen lines.
|
|
|
|
**Rationale**: Matches Houdini/Blender's standard camera shape. The body box ensures a reliable click target even at distance.
|
|
|
|
---
|
|
|
|
### D3 — Frustum scale proportional to camera distance
|
|
|
|
**Decision**: The overall wireframe scale is `dist * 0.12` where `dist` is the current viewport camera's look-at distance (from `ViewportCamera::GetDist()`). This matches the `screenFactor` approach used by the `TransformManipulator`.
|
|
|
|
**Rationale**: A camera at distance 100 should not appear tiny compared to one at distance 5. Constant apparent size improves usability.
|
|
|
|
---
|
|
|
|
### D4 — Camera picking via screen-space segment proximity
|
|
|
|
**Decision**: `PickCameraAtPoint` iterates all `UsdGeomCamera` prims, projects each wireframe segment to screen space, and returns the prim path of the closest camera whose minimum segment distance to the mouse is `< kCameraPickRadius` (10 pixels). If multiple cameras qualify, the nearest in screen space wins.
|
|
|
|
**Rationale**: `UsdImagingGLEngine::TestIntersection` only hits rendered hydra geometry — cameras have no rendered prims in Storm. Custom 2D proximity testing is the only option. 10 px matches the manipulator's pick radius for consistency.
|
|
|
|
---
|
|
|
|
### D5 — Camera pick tested before geometry pick
|
|
|
|
**Decision**: In `ViewportPanel::HandleInput`, the single-click path calls `PickCameraAtPoint` first. If a camera path is returned, that prim is selected and the geometry pick is skipped. If no camera is hit, the existing `PickObject` path runs as before.
|
|
|
|
**Rationale**: Camera wireframes are thin and can overlap rendered geometry behind them. Giving camera wireframes priority matches Houdini/Maya behaviour where camera icons are always selectable.
|
|
|
|
---
|
|
|
|
### D6 — Active camera path passed through from ViewportPanel
|
|
|
|
**Decision**: `DrawCameraWireframes` and `PickCameraAtPoint` receive `m_selectedCameraPath` from `ViewportPanel` (the path of whichever USD camera is currently active in the toolbar, or `SdfPath()` for Free Camera). This allows the cyan highlight without coupling `UsdSceneRenderer` to `ViewportCamera`.
|
|
|
|
**Rationale**: `UsdSceneRenderer` should not hold a reference to `ViewportCamera`. The path is a cheap `SdfPath` copy per frame.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- **[Risk] Frustum scale is view-dependent** — when the viewport camera is inside a USD camera's frustum and zoomed in close, the scale might get very large.
|
|
→ Mitigation: clamp `frustumScale` to a maximum of 50 world units.
|
|
|
|
- **[Risk] Performance** — iterating all `UsdGeomCamera` prims every frame could be slow for large scenes.
|
|
→ Mitigation: cache the list of camera prim paths; invalidate on stage change via `SetForceRefresh`.
|
|
|
|
- **[Risk] Camera behind the viewer** — `WorldToScreen` returns false for behind-near-plane points; those line endpoints are simply skipped.
|
|
→ Acceptable; only the visible portion of the wireframe is drawn.
|
|
|
|
## Migration Plan
|
|
|
|
Additive changes only — no existing API is removed or broken. `DrawCameraWireframes` and `PickCameraAtPoint` are new methods; their call sites in `ViewportPanel` are inside existing per-frame code blocks.
|