1c55907573
Exposes UsdGeomImageable purpose visibility as three per-viewport toggles in the gear popup (Purpose: Guide / Proxy / Render), wired to showGuides/showProxy/showRender on UsdImagingGLRenderParams. Defaults match the USD render params defaults (Proxy on, Guide and Render off). Settings persist per-viewport to viewport_settings.ini. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1487 lines
64 KiB
C++
1487 lines
64 KiB
C++
#include "ViewportTile.h"
|
|
#include "../utils/Logger.h"
|
|
|
|
#include <pxr/usd/usdGeom/camera.h>
|
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
|
#include <pxr/usd/usdGeom/xformable.h>
|
|
#include <pxr/usd/usdGeom/bboxCache.h>
|
|
#include <pxr/usd/usdGeom/xformCache.h>
|
|
#include <pxr/usd/usdGeom/tokens.h>
|
|
#include <pxr/usd/usd/primRange.h>
|
|
#include <pxr/base/gf/bbox3d.h>
|
|
#include <pxr/base/gf/frustum.h>
|
|
#include <pxr/base/gf/rotation.h>
|
|
#include <pxr/imaging/cameraUtil/conformWindow.h>
|
|
#include <imgui.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
namespace UsdLayerManager {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
ViewportTile::ViewportTile() = default;
|
|
ViewportTile::~ViewportTile() = default;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::SetCommandHistory(CommandHistory* h)
|
|
{
|
|
m_commandHistory = h;
|
|
// The TransformManipulator's command history is set by the container
|
|
// (ViewportPanel), not by individual tiles.
|
|
}
|
|
|
|
void ViewportTile::SetTimeCodes(pxr::UsdTimeCode displayTime,
|
|
pxr::UsdTimeCode editTime)
|
|
{
|
|
m_displayTime = displayTime;
|
|
m_editTime = editTime;
|
|
m_renderer.SetCurrentTimeCode(displayTime);
|
|
m_renderer.SetForceRefresh(true);
|
|
}
|
|
|
|
void ViewportTile::SetStage(pxr::UsdStageRefPtr stage)
|
|
{
|
|
m_stage = stage;
|
|
m_renderer.SetStage(stage);
|
|
m_camera.SetStage(stage);
|
|
m_selectedCameraIndex = 0;
|
|
m_cameraListDirty = true;
|
|
m_hasLastGfCamera = false;
|
|
m_hasSavedFreeCameraState = false;
|
|
m_isDrivingUsdCamPrim = false;
|
|
m_drivenUsdCamPath = pxr::SdfPath();
|
|
m_camera.SwitchToFreeCamera();
|
|
|
|
if (stage) {
|
|
pxr::TfTokenVector purposes = {
|
|
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
|
|
pxr::UsdGeomBBoxCache bboxCache(m_displayTime, purposes, true);
|
|
pxr::GfBBox3d stageBBox = bboxCache.ComputeWorldBound(stage->GetPseudoRoot());
|
|
if (!stageBBox.GetRange().IsEmpty())
|
|
m_camera.FrameSelection(stageBBox, 1.1);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::SetSelectedPaths(const pxr::SdfPathVector& paths,
|
|
const std::string& primaryPath)
|
|
{
|
|
m_selectedSdfPaths = paths;
|
|
m_selectedPrimPath = primaryPath;
|
|
m_renderer.SetSelectedPaths(paths);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::FrameScene()
|
|
{
|
|
if (!m_stage) return;
|
|
pxr::TfTokenVector purposes = {
|
|
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
|
|
pxr::UsdGeomBBoxCache bboxCache(m_displayTime, purposes, true);
|
|
pxr::GfBBox3d stageBBox = bboxCache.ComputeWorldBound(m_stage->GetPseudoRoot());
|
|
if (!stageBBox.GetRange().IsEmpty()) {
|
|
InitCameraNavigation();
|
|
m_camera.FrameSelection(stageBBox, 1.1);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
ViewportTileSettings ViewportTile::GetSettings() const
|
|
{
|
|
ViewportTileSettings s;
|
|
s.renderDelegate = m_renderer.GetCurrentRendererId().GetString();
|
|
s.showGrid = m_renderer.ShowGrid();
|
|
s.showCameraGuide = m_renderer.ShowCameraGuide();
|
|
s.aov = m_renderer.GetCurrentAov().GetString();
|
|
s.showGuides = m_renderer.ShowGuides();
|
|
s.showProxy = m_renderer.ShowProxy();
|
|
s.showRender = m_renderer.ShowRender();
|
|
s.aaEnabled = m_renderer.GetAAEnabled();
|
|
const pxr::GfVec3f& bg = m_renderer.GetBackgroundColor();
|
|
s.bgColorR = bg[0]; s.bgColorG = bg[1]; s.bgColorB = bg[2];
|
|
s.bboxMode = static_cast<int>(m_renderer.GetBBoxMode());
|
|
s.ambientLightOnly = m_renderer.GetAmbientLightOnly();
|
|
s.domeLightEnabled = m_renderer.GetDomeLightEnabled();
|
|
s.shadingMode = static_cast<int>(m_renderer.GetShadingMode());
|
|
return s;
|
|
}
|
|
|
|
void ViewportTile::ApplySettings(const ViewportTileSettings& s)
|
|
{
|
|
if (!s.renderDelegate.empty())
|
|
m_renderer.SetRendererPlugin(pxr::TfToken(s.renderDelegate));
|
|
m_renderer.SetShowGrid(s.showGrid);
|
|
m_renderer.SetShowCameraGuide(s.showCameraGuide);
|
|
if (!s.aov.empty())
|
|
m_renderer.SetCurrentAov(pxr::TfToken(s.aov));
|
|
m_renderer.SetShowGuides(s.showGuides);
|
|
m_renderer.SetShowProxy(s.showProxy);
|
|
m_renderer.SetShowRender(s.showRender);
|
|
m_renderer.SetAAEnabled(s.aaEnabled);
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(s.bgColorR, s.bgColorG, s.bgColorB));
|
|
m_renderer.SetBBoxMode(static_cast<BBoxMode>(s.bboxMode));
|
|
m_renderer.SetAmbientLightOnly(s.ambientLightOnly);
|
|
m_renderer.SetDomeLightEnabled(s.domeLightEnabled);
|
|
m_renderer.SetShadingMode(static_cast<ShadingMode>(s.shadingMode));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::RefreshCameraList()
|
|
{
|
|
m_cameraPaths.clear();
|
|
if (m_stage) {
|
|
for (pxr::UsdPrim prim : m_stage->Traverse())
|
|
if (prim.IsA<pxr::UsdGeomCamera>())
|
|
m_cameraPaths.push_back(prim.GetPath());
|
|
}
|
|
m_cameraListDirty = false;
|
|
}
|
|
|
|
void ViewportTile::TrySwitchToFreeCamera()
|
|
{
|
|
if (m_camera.GetMode() != ViewportCamera::CameraMode::UsdCamera) return;
|
|
m_selectedCameraIndex = 0;
|
|
m_isDrivingUsdCamPrim = false;
|
|
if (m_hasSavedFreeCameraState)
|
|
m_camera.SwitchToFreeCamera(&m_savedFreeCameraState);
|
|
else
|
|
m_camera.SwitchToFreeCamera(m_hasLastGfCamera ? &m_lastComputedGfCamera : nullptr);
|
|
}
|
|
|
|
void ViewportTile::InitCameraNavigation()
|
|
{
|
|
if (m_camera.GetMode() != ViewportCamera::CameraMode::UsdCamera) return;
|
|
pxr::SdfPath camPath = m_camera.GetUsdCameraPath();
|
|
if (!m_stage || camPath.IsEmpty()) { TrySwitchToFreeCamera(); return; }
|
|
pxr::UsdPrim prim = m_stage->GetPrimAtPath(camPath);
|
|
if (!prim || !prim.IsA<pxr::UsdGeomCamera>()) { TrySwitchToFreeCamera(); return; }
|
|
pxr::UsdGeomCamera usdCam(prim);
|
|
pxr::GfCamera gfCam = usdCam.GetCamera(m_displayTime);
|
|
m_camera.SwitchToFreeCamera(&gfCam);
|
|
|
|
// SwitchToFreeCamera calls PullFromCameraTransform, which extracts orbital
|
|
// theta/phi via Euler decomposition and uses GetFocusDistance() as m_dist.
|
|
// Both are wrong for a USD camera prim: the focus distance is a DOF attribute
|
|
// (defaults to 5 units) and the Euler decomposition produces gimbal-affected
|
|
// angles when the camera has roll or an unusual orientation.
|
|
// Fix: compute orbit center from the scene bbox, then call
|
|
// InitOrbitFromEyeAndCenter to derive theta/phi from geometry with zero roll.
|
|
{
|
|
pxr::TfTokenVector purposes = {
|
|
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
|
|
pxr::UsdGeomBBoxCache bboxCache(m_displayTime, purposes, true);
|
|
pxr::GfBBox3d stageBBox =
|
|
bboxCache.ComputeWorldBound(m_stage->GetPseudoRoot());
|
|
if (!stageBBox.GetRange().IsEmpty()) {
|
|
pxr::GfVec3d sceneCenter = stageBBox.ComputeCentroid();
|
|
pxr::GfVec3d camEye = gfCam.GetFrustum().GetPosition();
|
|
pxr::GfVec3d viewDir = gfCam.GetFrustum().ComputeViewDirection();
|
|
|
|
// Project scene centre onto the view ray for the orbit distance;
|
|
// keeps the camera at its authored position.
|
|
double dist = pxr::GfDot(sceneCenter - camEye, viewDir);
|
|
if (dist > 0.01)
|
|
m_camera.InitOrbitFromEyeAndCenter(
|
|
camEye, camEye + dist * viewDir, dist);
|
|
}
|
|
}
|
|
|
|
m_isDrivingUsdCamPrim = true;
|
|
m_drivenUsdCamPath = camPath;
|
|
}
|
|
|
|
pxr::GfVec3d ViewportTile::ComputeGizmoPivot() const
|
|
{
|
|
if (m_selectedSdfPaths.empty() || !m_stage) return pxr::GfVec3d(0.0);
|
|
pxr::UsdPrim prim = m_stage->GetPrimAtPath(m_selectedSdfPaths.front());
|
|
if (!prim) return pxr::GfVec3d(0.0);
|
|
|
|
pxr::UsdGeomXformCache xformCache(m_displayTime);
|
|
|
|
// Primary: use XformCommonAPI to read the authored translate and pivot op.
|
|
// The manipulator must sit at the pivot's world-space position:
|
|
// worldPivot = parentToWorld.Transform(translate + pivot)
|
|
// NOT at the bounding-box center (geometry centroid) and NOT at
|
|
// worldMatrix.ExtractTranslation() which folds in the pivot-inverse op
|
|
// and gives the geometry origin, not the authored pivot point.
|
|
pxr::UsdGeomXformCommonAPI api(prim);
|
|
pxr::GfVec3d translate;
|
|
pxr::GfVec3f rotation, scale, pivot;
|
|
pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder;
|
|
if (api.GetXformVectors(&translate, &rotation, &scale, &pivot,
|
|
&rotOrder, m_displayTime))
|
|
{
|
|
pxr::GfVec3d localPivot = translate + pxr::GfVec3d(pivot[0], pivot[1], pivot[2]);
|
|
pxr::UsdPrim parent = prim.GetParent();
|
|
if (parent && !parent.IsPseudoRoot()) {
|
|
pxr::GfMatrix4d p2w = xformCache.GetLocalToWorldTransform(parent);
|
|
return p2w.Transform(localPivot);
|
|
}
|
|
return localPivot; // root-level prim: parent space == world space
|
|
}
|
|
|
|
// Fallback for incompatible op stacks (e.g. xformOp:transform matrix ops):
|
|
// use the prim's world-space origin from the local-to-world matrix.
|
|
pxr::GfMatrix4d worldXform = xformCache.GetLocalToWorldTransform(prim);
|
|
return worldXform.ExtractTranslation();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// BuildOrthoCamera
|
|
// ---------------------------------------------------------------------------
|
|
// Camera-to-world rotation matrices (row-major GfMatrix4d: rows are cam axes).
|
|
// Right-hand rule verified: cross(cam_X, cam_Y) == cam_Z for every entry.
|
|
//
|
|
// Y-up (isZUp=false)
|
|
// Top cam_X=(1,0,0) cam_Y=(0,0,-1) cam_Z=(0,1,0) eye=c+(0,+d,0)
|
|
// Bottom cam_X=(1,0,0) cam_Y=(0,0,+1) cam_Z=(0,-1,0) eye=c+(0,-d,0)
|
|
// Front cam_X=(1,0,0) cam_Y=(0,1,0) cam_Z=(0,0,+1) eye=c+(0,0,+d)
|
|
// Back cam_X=(-1,0,0) cam_Y=(0,1,0) cam_Z=(0,0,-1) eye=c+(0,0,-d)
|
|
// Left cam_X=(0,0,+1) cam_Y=(0,1,0) cam_Z=(-1,0,0) eye=c+(-d,0,0)
|
|
// Right cam_X=(0,0,-1) cam_Y=(0,1,0) cam_Z=(+1,0,0) eye=c+(+d,0,0)
|
|
//
|
|
// Z-up (isZUp=true)
|
|
// Top cam_X=(1,0,0) cam_Y=(0,1,0) cam_Z=(0,0,+1) eye=c+(0,0,+d)
|
|
// Bottom cam_X=(1,0,0) cam_Y=(0,-1,0) cam_Z=(0,0,-1) eye=c+(0,0,-d)
|
|
// Front cam_X=(1,0,0) cam_Y=(0,0,+1) cam_Z=(0,-1,0) eye=c+(0,-d,0)
|
|
// Back cam_X=(-1,0,0) cam_Y=(0,0,+1) cam_Z=(0,+1,0) eye=c+(0,+d,0)
|
|
// Left cam_X=(0,-1,0) cam_Y=(0,0,+1) cam_Z=(-1,0,0) eye=c+(-d,0,0)
|
|
// Right cam_X=(0,+1,0) cam_Y=(0,0,+1) cam_Z=(+1,0,0) eye=c+(+d,0,0)
|
|
// ---------------------------------------------------------------------------
|
|
pxr::GfCamera ViewportTile::BuildOrthoCamera() const
|
|
{
|
|
const bool zUp = m_camera.IsZUp();
|
|
const double d = m_camera.GetDist();
|
|
const pxr::GfVec3d c = m_camera.GetFocalPoint();
|
|
const double aspect = (m_viewWidth > 0 && m_viewHeight > 0)
|
|
? static_cast<double>(m_viewWidth) / static_cast<double>(m_viewHeight)
|
|
: 1.0;
|
|
|
|
pxr::GfCamera cam;
|
|
|
|
// Visible vertical world span = d*2. Scales with zoom/framing naturally.
|
|
cam.SetOrthographicFromAspectRatioAndSize(
|
|
static_cast<float>(aspect),
|
|
static_cast<float>(d * 2.0),
|
|
pxr::GfCamera::FOVVertical);
|
|
|
|
// Large symmetric range: ortho cameras may need to see geometry behind
|
|
// the nominal camera position (e.g., geometry above a Top-view camera).
|
|
cam.SetClippingRange(pxr::GfRange1f(
|
|
static_cast<float>(-d * 10.0),
|
|
static_cast<float>( d * 100.0)));
|
|
|
|
// Build camera-to-world matrix from three cam-space axes + eye position.
|
|
auto M = [](
|
|
double r00, double r01, double r02,
|
|
double r10, double r11, double r12,
|
|
double r20, double r21, double r22,
|
|
const pxr::GfVec3d& e) -> pxr::GfMatrix4d
|
|
{
|
|
return pxr::GfMatrix4d(
|
|
r00, r01, r02, 0,
|
|
r10, r11, r12, 0,
|
|
r20, r21, r22, 0,
|
|
e[0], e[1], e[2], 1);
|
|
};
|
|
|
|
pxr::GfMatrix4d xform(1.0);
|
|
|
|
if (!zUp) {
|
|
switch (m_orthoView) {
|
|
case OrthoView::Top: xform = M( 1,0, 0, 0,0,-1, 0, 1, 0, c+pxr::GfVec3d( 0, d, 0)); break;
|
|
case OrthoView::Bottom: xform = M( 1,0, 0, 0,0, 1, 0,-1, 0, c+pxr::GfVec3d( 0,-d, 0)); break;
|
|
case OrthoView::Front: xform = M( 1,0, 0, 0,1, 0, 0, 0, 1, c+pxr::GfVec3d( 0, 0, d)); break;
|
|
case OrthoView::Back: xform = M(-1,0, 0, 0,1, 0, 0, 0,-1, c+pxr::GfVec3d( 0, 0,-d)); break;
|
|
case OrthoView::Left: xform = M( 0,0, 1, 0,1, 0, -1, 0, 0, c+pxr::GfVec3d(-d, 0, 0)); break;
|
|
case OrthoView::Right: xform = M( 0,0,-1, 0,1, 0, 1, 0, 0, c+pxr::GfVec3d( d, 0, 0)); break;
|
|
default: break;
|
|
}
|
|
} else {
|
|
switch (m_orthoView) {
|
|
case OrthoView::Top: xform = M( 1,0, 0, 0, 1, 0, 0, 0, 1, c+pxr::GfVec3d( 0, 0, d)); break;
|
|
case OrthoView::Bottom: xform = M( 1,0, 0, 0,-1, 0, 0, 0,-1, c+pxr::GfVec3d( 0, 0,-d)); break;
|
|
case OrthoView::Front: xform = M( 1,0, 0, 0, 0, 1, 0,-1, 0, c+pxr::GfVec3d( 0,-d, 0)); break;
|
|
case OrthoView::Back: xform = M(-1,0, 0, 0, 0, 1, 0, 1, 0, c+pxr::GfVec3d( 0, d, 0)); break;
|
|
case OrthoView::Left: xform = M( 0,-1,0, 0, 0, 1, -1, 0, 0, c+pxr::GfVec3d(-d, 0, 0)); break;
|
|
case OrthoView::Right: xform = M( 0, 1,0, 0, 0, 1, 1, 0, 0, c+pxr::GfVec3d( d, 0, 0)); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
cam.SetTransform(xform);
|
|
return cam;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ResolveCamera
|
|
// ---------------------------------------------------------------------------
|
|
pxr::GfCamera ViewportTile::ResolveCamera()
|
|
{
|
|
double aspect = static_cast<double>(m_viewWidth) /
|
|
static_cast<double>(m_viewHeight);
|
|
pxr::GfCamera gfCamera;
|
|
|
|
// -- Orthographic preset (short-circuit) ---------------------------------
|
|
if (m_orthoView != OrthoView::None) {
|
|
gfCamera = BuildOrthoCamera();
|
|
m_lastComputedGfCamera = gfCamera;
|
|
m_hasLastGfCamera = true;
|
|
m_renderer.SetCameraStateFromGfCamera(gfCamera);
|
|
return gfCamera;
|
|
}
|
|
|
|
if (m_camera.GetMode() == ViewportCamera::CameraMode::Free ||
|
|
m_isDrivingUsdCamPrim)
|
|
{
|
|
m_camera.SetAspectRatio(aspect);
|
|
pxr::GfBBox3d emptyBBox;
|
|
gfCamera = m_camera.ComputeGfCamera(emptyBBox, false);
|
|
pxr::CameraUtilConformWindow(
|
|
&gfCamera, pxr::CameraUtilMatchVertically, aspect);
|
|
|
|
m_lastComputedGfCamera = gfCamera;
|
|
m_hasLastGfCamera = true;
|
|
m_renderer.SetCameraStateFromGfCamera(gfCamera);
|
|
|
|
if (m_isDrivingUsdCamPrim) {
|
|
pxr::UsdPrim prim = m_stage->GetPrimAtPath(m_drivenUsdCamPath);
|
|
if (prim && prim.IsA<pxr::UsdGeomCamera>()) {
|
|
// Author the full camera-to-world matrix as a single transform
|
|
// op rather than decomposing to XYZ-euler translate/rotate.
|
|
// The euler round-trip (Decompose(X,Y,Z) -> rotateXYZ op) is
|
|
// lossy: the read-back orientation differs from what was
|
|
// written, which made the view jump every time a drag finished
|
|
// and the mode flipped back to reading the prim. A matrix op
|
|
// round-trips exactly through UsdGeomCamera::GetCamera().
|
|
pxr::UsdGeomXformable xformable(prim);
|
|
bool resets = false;
|
|
std::vector<pxr::UsdGeomXformOp> ops =
|
|
xformable.GetOrderedXformOps(&resets);
|
|
pxr::UsdGeomXformOp matrixOp;
|
|
if (ops.size() == 1 &&
|
|
ops[0].GetOpType() == pxr::UsdGeomXformOp::TypeTransform)
|
|
matrixOp = ops[0];
|
|
else
|
|
matrixOp = xformable.MakeMatrixXform();
|
|
if (matrixOp)
|
|
matrixOp.Set(gfCamera.GetTransform(), m_editTime);
|
|
}
|
|
if (!m_isOrbiting && !m_isPanning && !m_isDollying) {
|
|
m_isDrivingUsdCamPrim = false;
|
|
m_camera.SetUsdCamera(m_drivenUsdCamPath);
|
|
m_renderer.SetCameraPath(m_drivenUsdCamPath);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pxr::SdfPath camPath = m_camera.GetUsdCameraPath();
|
|
pxr::UsdPrim camPrim = m_stage->GetPrimAtPath(camPath);
|
|
if (camPrim && camPrim.IsA<pxr::UsdGeomCamera>()) {
|
|
pxr::UsdGeomCamera usdCam(camPrim);
|
|
gfCamera = usdCam.GetCamera(m_displayTime);
|
|
pxr::CameraUtilConformWindow(
|
|
&gfCamera, pxr::CameraUtilMatchVertically, aspect);
|
|
m_lastComputedGfCamera = gfCamera;
|
|
m_hasLastGfCamera = true;
|
|
m_renderer.SetCameraStateFromGfCamera(gfCamera);
|
|
}
|
|
}
|
|
|
|
return gfCamera;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HandleInput
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::HandleInput(bool isFocused, TransformManipulator& manipulator,
|
|
bool dividerActive)
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImVec2 mousePos = ImGui::GetMousePos();
|
|
// IsWindowHovered scopes to this child window
|
|
bool hovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
|
|
bool altHeld = io.KeyAlt;
|
|
|
|
// Track click for focus update ??any mouse button press in this tile
|
|
// makes it the focused (active) viewport.
|
|
if (hovered && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
|
|
ImGui::IsMouseClicked(ImGuiMouseButton_Middle) ||
|
|
ImGui::IsMouseClicked(ImGuiMouseButton_Right)))
|
|
m_wasClickedThisFrame = true;
|
|
|
|
// --- Manipulator input (only in focused tile) ---
|
|
bool manipConsumed = false;
|
|
if (isFocused &&
|
|
manipulator.GetMode() != ManipulatorMode::Select &&
|
|
!m_selectedSdfPaths.empty() && m_stage &&
|
|
m_viewWidth > 0 && m_viewHeight > 0 && m_hasLastGfCamera)
|
|
{
|
|
pxr::GfVec3d pivot = ComputeGizmoPivot();
|
|
pxr::GfVec3d camEye = m_lastComputedGfCamera.GetFrustum().GetPosition();
|
|
pxr::GfFrustum frustum = m_lastComputedGfCamera.GetFrustum();
|
|
pxr::GfMatrix4d viewProj =
|
|
frustum.ComputeViewMatrix() * frustum.ComputeProjectionMatrix();
|
|
|
|
manipConsumed = manipulator.HandleInput(
|
|
viewProj, pivot, camEye,
|
|
m_imageScreenPos,
|
|
m_viewWidth, m_viewHeight,
|
|
hovered);
|
|
}
|
|
|
|
// --- Start camera drags ---
|
|
if (hovered && !manipConsumed) {
|
|
if (altHeld && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
|
if (m_orthoView == OrthoView::None) {
|
|
// Perspective free camera: orbit
|
|
InitCameraNavigation();
|
|
m_isOrbiting = true;
|
|
} else {
|
|
// Ortho view: Alt+LMB pans (same as Alt+MMB in Maya)
|
|
m_isPanning = true;
|
|
}
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
if (altHeld && ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) {
|
|
InitCameraNavigation();
|
|
m_isPanning = true;
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
if (altHeld && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
InitCameraNavigation();
|
|
m_isDollying = true;
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
if (!altHeld && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
|
!dividerActive && // don't start rect-select while a split handle is active
|
|
m_stage && m_viewWidth > 0 && m_viewHeight > 0)
|
|
{
|
|
m_rectAnchor = mousePos;
|
|
m_rectCurrent = mousePos;
|
|
m_rectDragStarted = true;
|
|
m_isRectSelecting = false;
|
|
}
|
|
}
|
|
|
|
// --- Stop Alt-based camera drags when Alt is released ---
|
|
// Alt+LMB / Alt+MMB / Alt+RMB navigation should end as soon as Alt is
|
|
// released, even if the mouse button is still held.
|
|
if (!altHeld) {
|
|
m_isOrbiting = false;
|
|
m_isPanning = false;
|
|
m_isDollying = false;
|
|
}
|
|
|
|
// --- Process camera drags ---
|
|
if (m_isOrbiting) {
|
|
float dx = mousePos.x - m_lastMouseX;
|
|
float dy = mousePos.y - m_lastMouseY;
|
|
m_camera.Tumble(0.25 * dx, 0.25 * dy);
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
if (m_isPanning) {
|
|
float dx = mousePos.x - m_lastMouseX;
|
|
float dy = mousePos.y - m_lastMouseY;
|
|
|
|
if (m_orthoView != OrthoView::None) {
|
|
// Ortho pan: derive right/up from the ortho camera matrix so the
|
|
// movement direction always matches the view, regardless of whatever
|
|
// stale theta/phi the underlying free-camera orbital state carries.
|
|
// Scale: visible world height = dist*2, so 1 pixel = (dist*2)/viewH.
|
|
pxr::GfCamera orthoCam = BuildOrthoCamera();
|
|
pxr::GfMatrix4d xform = orthoCam.GetTransform();
|
|
// row0 = cam_X (screen right), row1 = cam_Y (screen up) in world space
|
|
pxr::GfVec3d screenRight(xform[0][0], xform[0][1], xform[0][2]);
|
|
pxr::GfVec3d screenUp (xform[1][0], xform[1][1], xform[1][2]);
|
|
|
|
double factor = m_camera.GetDist() * 2.0
|
|
/ static_cast<double>(std::max(m_viewHeight, 1));
|
|
|
|
pxr::GfVec3d newCenter = m_camera.GetFocalPoint()
|
|
- screenRight * (static_cast<double>(dx) * factor)
|
|
+ screenUp * (static_cast<double>(dy) * factor);
|
|
m_camera.SetFocalPoint(newCenter);
|
|
} else {
|
|
double factor = m_camera.ComputePixelsToWorldFactor(
|
|
static_cast<double>(std::max(m_viewHeight, 1)));
|
|
m_camera.Truck(static_cast<double>(-dx) * factor,
|
|
static_cast<double>( dy) * factor);
|
|
}
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
if (m_isDollying) {
|
|
float dx = mousePos.x - m_lastMouseX;
|
|
float dy = mousePos.y - m_lastMouseY;
|
|
double zoomDelta = -0.002 * (dx + dy);
|
|
m_camera.AdjustDistance(1.0 + zoomDelta);
|
|
m_lastMouseX = mousePos.x; m_lastMouseY = mousePos.y;
|
|
}
|
|
|
|
// --- Update rect selection while LMB held ---
|
|
if (m_rectDragStarted && !altHeld &&
|
|
ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
|
|
!m_isOrbiting && !m_isPanning && !m_isDollying &&
|
|
!manipulator.IsDragging())
|
|
{
|
|
m_rectCurrent = mousePos;
|
|
float dx = m_rectCurrent.x - m_rectAnchor.x;
|
|
float dy = m_rectCurrent.y - m_rectAnchor.y;
|
|
if (std::sqrt(dx * dx + dy * dy) > kRectDragThreshold)
|
|
m_isRectSelecting = true;
|
|
}
|
|
|
|
// --- LMB released ---
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
|
m_isOrbiting = false;
|
|
|
|
if (m_rectDragStarted && !altHeld &&
|
|
m_stage && m_viewWidth > 0 && m_viewHeight > 0)
|
|
{
|
|
if (hovered) {
|
|
int ax = static_cast<int>(m_rectAnchor.x - m_imageScreenPos.x);
|
|
int ay = static_cast<int>(m_rectAnchor.y - m_imageScreenPos.y);
|
|
int cx = static_cast<int>(m_rectCurrent.x - m_imageScreenPos.x);
|
|
int cy = static_cast<int>(m_rectCurrent.y - m_imageScreenPos.y);
|
|
|
|
if (m_isRectSelecting) {
|
|
pxr::SdfPathVector hitPaths;
|
|
if (m_renderer.PickObjectsInRect(
|
|
ax, ay, cx, cy,
|
|
m_viewWidth, m_viewHeight, &hitPaths))
|
|
{
|
|
if (io.KeyShift) {
|
|
for (const auto& hp : hitPaths)
|
|
if (std::find(m_selectedSdfPaths.begin(),
|
|
m_selectedSdfPaths.end(), hp)
|
|
== m_selectedSdfPaths.end())
|
|
m_selectedSdfPaths.push_back(hp);
|
|
} else {
|
|
m_selectedSdfPaths = hitPaths;
|
|
}
|
|
m_selectedPrimPath = m_selectedSdfPaths.empty()
|
|
? "" : m_selectedSdfPaths.front().GetString();
|
|
m_renderer.SetSelectedPaths(m_selectedSdfPaths);
|
|
|
|
if (OnPrimsPickedRect) {
|
|
std::vector<std::string> pathStrs;
|
|
pathStrs.reserve(m_selectedSdfPaths.size());
|
|
for (const auto& p : m_selectedSdfPaths)
|
|
pathStrs.push_back(p.GetString());
|
|
OnPrimsPickedRect(pathStrs);
|
|
}
|
|
} else if (!io.KeyShift) {
|
|
m_renderer.ClearSelected();
|
|
m_selectedPrimPath.clear();
|
|
m_selectedSdfPaths.clear();
|
|
if (OnPrimsPickedRect) OnPrimsPickedRect({});
|
|
}
|
|
} else {
|
|
// Single click pick
|
|
if (cx >= 0 && cy >= 0 && cx < m_viewWidth && cy < m_viewHeight) {
|
|
pxr::SdfPath camPickPath;
|
|
bool hitCamera = false;
|
|
if (m_hasLastGfCamera) {
|
|
pxr::GfFrustum fr = m_lastComputedGfCamera.GetFrustum();
|
|
pxr::GfMatrix4d vp = fr.ComputeViewMatrix()
|
|
* fr.ComputeProjectionMatrix();
|
|
hitCamera = m_renderer.PickCameraAtPoint(
|
|
m_stage, mousePos.x, mousePos.y, vp,
|
|
m_imageScreenPos.x, m_imageScreenPos.y,
|
|
m_viewWidth, m_viewHeight,
|
|
m_camera.GetDist(), &camPickPath);
|
|
}
|
|
|
|
pxr::SdfPath hitPath = hitCamera ? camPickPath : pxr::SdfPath();
|
|
bool hitGeom = false;
|
|
pxr::GfVec3d hitPoint;
|
|
if (!hitCamera)
|
|
hitGeom = m_renderer.PickObject(
|
|
cx, cy, m_viewWidth, m_viewHeight, &hitPoint, &hitPath);
|
|
|
|
if (hitCamera || hitGeom) {
|
|
if (io.KeyShift) {
|
|
auto it = std::find(m_selectedSdfPaths.begin(),
|
|
m_selectedSdfPaths.end(), hitPath);
|
|
if (it != m_selectedSdfPaths.end())
|
|
m_selectedSdfPaths.erase(it);
|
|
else
|
|
m_selectedSdfPaths.push_back(hitPath);
|
|
m_selectedPrimPath = m_selectedSdfPaths.empty()
|
|
? "" : m_selectedSdfPaths.back().GetString();
|
|
m_renderer.ClearSelected();
|
|
for (const auto& p : m_selectedSdfPaths)
|
|
m_renderer.AddSelected(p);
|
|
if (OnPrimsPickedRect) {
|
|
std::vector<std::string> pathStrs;
|
|
pathStrs.reserve(m_selectedSdfPaths.size());
|
|
for (const auto& p : m_selectedSdfPaths)
|
|
pathStrs.push_back(p.GetString());
|
|
OnPrimsPickedRect(pathStrs);
|
|
}
|
|
} else {
|
|
m_selectedPrimPath = hitPath.GetString();
|
|
m_selectedSdfPaths = { hitPath };
|
|
m_renderer.ClearSelected();
|
|
m_renderer.AddSelected(hitPath);
|
|
if (OnPrimPicked) OnPrimPicked(m_selectedPrimPath);
|
|
}
|
|
} else {
|
|
if (!io.KeyShift) {
|
|
m_renderer.ClearSelected();
|
|
m_selectedPrimPath.clear();
|
|
m_selectedSdfPaths.clear();
|
|
if (OnPrimPicked) OnPrimPicked("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_rectDragStarted = false;
|
|
m_isRectSelecting = false;
|
|
}
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle)) m_isPanning = false;
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) m_isDollying = false;
|
|
|
|
// --- Mouse wheel zoom ---
|
|
if (hovered && io.MouseWheel != 0.0f) {
|
|
InitCameraNavigation();
|
|
double delta = std::max(-0.5, std::min(0.5,
|
|
static_cast<double>(io.MouseWheel) * 0.12));
|
|
m_camera.AdjustDistance(1.0 - delta);
|
|
}
|
|
|
|
// --- Keyboard shortcuts ---
|
|
// All shortcuts are gated on `hovered` (mouse is over this tile) so that
|
|
// exactly one viewport responds per key press. This matches standard DCC
|
|
// behaviour: the viewport under the cursor is the "active" one for both
|
|
// mouse and keyboard interaction.
|
|
if (!io.WantTextInput && hovered) {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_F)) {
|
|
if (!m_selectedSdfPaths.empty() && m_stage) {
|
|
pxr::TfTokenVector purposes = {
|
|
pxr::UsdGeomTokens->default_, pxr::UsdGeomTokens->proxy };
|
|
pxr::UsdGeomBBoxCache bboxCache(
|
|
m_displayTime, purposes, true);
|
|
pxr::GfRange3d combined;
|
|
for (const auto& path : m_selectedSdfPaths) {
|
|
pxr::UsdPrim prim = m_stage->GetPrimAtPath(path);
|
|
if (!prim) continue;
|
|
pxr::GfBBox3d bbox = bboxCache.ComputeWorldBound(prim);
|
|
combined.UnionWith(bbox.ComputeAlignedRange());
|
|
}
|
|
if (combined.IsEmpty()) {
|
|
pxr::UsdGeomXformCache xformCache(m_displayTime);
|
|
for (const auto& path : m_selectedSdfPaths) {
|
|
pxr::UsdPrim prim = m_stage->GetPrimAtPath(path);
|
|
if (!prim) continue;
|
|
pxr::GfMatrix4d worldXform =
|
|
xformCache.GetLocalToWorldTransform(prim);
|
|
pxr::GfVec3d pos = worldXform.ExtractTranslation();
|
|
combined.UnionWith(pos - pxr::GfVec3d(1.0));
|
|
combined.UnionWith(pos + pxr::GfVec3d(1.0));
|
|
}
|
|
}
|
|
if (!combined.IsEmpty()) {
|
|
InitCameraNavigation();
|
|
m_camera.FrameSelection(pxr::GfBBox3d(combined), 1.1);
|
|
m_renderer.SetForceRefresh(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
|
|
FrameScene();
|
|
m_renderer.SetForceRefresh(true);
|
|
}
|
|
// Q/W/E/R tool mode and World/Object space are handled globally by
|
|
// ViewportPanel so they always work regardless of which tile is hovered.
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DrawSelectionRect
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::DrawSelectionRect()
|
|
{
|
|
if (!m_isRectSelecting || !m_rectDragStarted) return;
|
|
|
|
float vpMinX = m_imageScreenPos.x;
|
|
float vpMinY = m_imageScreenPos.y;
|
|
float vpMaxX = vpMinX + static_cast<float>(m_viewWidth);
|
|
float vpMaxY = vpMinY + static_cast<float>(m_viewHeight);
|
|
|
|
ImVec2 drawMin(
|
|
std::max(vpMinX, std::min(m_rectAnchor.x, m_rectCurrent.x)),
|
|
std::max(vpMinY, std::min(m_rectAnchor.y, m_rectCurrent.y)));
|
|
ImVec2 drawMax(
|
|
std::min(vpMaxX, std::max(m_rectAnchor.x, m_rectCurrent.x)),
|
|
std::min(vpMaxY, std::max(m_rectAnchor.y, m_rectCurrent.y)));
|
|
|
|
if (drawMin.x >= drawMax.x || drawMin.y >= drawMax.y) return;
|
|
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
dl->AddRectFilled(drawMin, drawMax, IM_COL32(100, 160, 255, 40));
|
|
dl->AddRect (drawMin, drawMax, IM_COL32(100, 160, 255, 220), 0.0f, 0, 1.5f);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RenderContextMenu
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::RenderContextMenu(int tileIndex)
|
|
{
|
|
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) &&
|
|
ImGui::IsMouseReleased(ImGuiMouseButton_Right) &&
|
|
!ImGui::GetIO().KeyAlt)
|
|
{
|
|
std::string popupId = "VPCtxMenu_" + std::to_string(tileIndex);
|
|
ImGui::OpenPopup(popupId.c_str());
|
|
}
|
|
|
|
std::string popupId = "VPCtxMenu_" + std::to_string(tileIndex);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8));
|
|
if (ImGui::BeginPopup(popupId.c_str())) {
|
|
bool showGrid = m_renderer.ShowGrid();
|
|
if (ImGui::MenuItem("Show Grid", nullptr, &showGrid))
|
|
m_renderer.SetShowGrid(showGrid);
|
|
|
|
bool showGuide = m_renderer.ShowCameraGuide();
|
|
if (ImGui::MenuItem("Camera Guide", nullptr, &showGuide))
|
|
m_renderer.SetShowCameraGuide(showGuide);
|
|
|
|
ImGui::Separator();
|
|
|
|
bool ambientOnly = m_renderer.GetAmbientLightOnly();
|
|
if (ImGui::MenuItem("Camera Light", nullptr, &ambientOnly))
|
|
m_renderer.SetAmbientLightOnly(ambientOnly);
|
|
|
|
bool domeLight = m_renderer.GetDomeLightEnabled();
|
|
if (ImGui::MenuItem("Dome Light", nullptr, &domeLight))
|
|
m_renderer.SetDomeLightEnabled(domeLight);
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginMenu("Bounding Box")) {
|
|
BBoxMode cur = m_renderer.GetBBoxMode();
|
|
if (ImGui::MenuItem("None", nullptr, cur == BBoxMode::None))
|
|
m_renderer.SetBBoxMode(BBoxMode::None);
|
|
if (ImGui::MenuItem("Per Object", nullptr, cur == BBoxMode::PerObject))
|
|
m_renderer.SetBBoxMode(BBoxMode::PerObject);
|
|
if (ImGui::MenuItem("All Selection",nullptr, cur == BBoxMode::AllSelection))
|
|
m_renderer.SetBBoxMode(BBoxMode::AllSelection);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginMenu("Background")) {
|
|
auto& bg = m_renderer.GetBackgroundColor();
|
|
if (ImGui::MenuItem("Dark Gray", nullptr,
|
|
bg == pxr::GfVec3f(0.15f, 0.15f, 0.15f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.15f, 0.15f, 0.15f));
|
|
if (ImGui::MenuItem("Black", nullptr,
|
|
bg == pxr::GfVec3f(0.0f, 0.0f, 0.0f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.0f, 0.0f, 0.0f));
|
|
if (ImGui::MenuItem("Light Gray", nullptr,
|
|
bg == pxr::GfVec3f(0.45f, 0.45f, 0.45f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.45f, 0.45f, 0.45f));
|
|
if (ImGui::MenuItem("Midnight Blue", nullptr,
|
|
bg == pxr::GfVec3f(0.1f, 0.1f, 0.2f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.1f, 0.1f, 0.2f));
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RenderCompactToolbar -- all per-tile controls in one compact row
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::RenderCompactToolbar(int tileIndex)
|
|
{
|
|
if (m_cameraListDirty) RefreshCameraList();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3, 3));
|
|
|
|
// -- Camera selector -----------------------------------------------------
|
|
ImGui::PushItemWidth(140.0f);
|
|
|
|
// Helper: ortho view display name
|
|
auto OrthoName = [](OrthoView v) -> const char* {
|
|
switch (v) {
|
|
case OrthoView::Top: return "Top";
|
|
case OrthoView::Bottom: return "Bottom";
|
|
case OrthoView::Front: return "Front";
|
|
case OrthoView::Back: return "Back";
|
|
case OrthoView::Left: return "Left";
|
|
case OrthoView::Right: return "Right";
|
|
default: return nullptr;
|
|
}
|
|
};
|
|
|
|
// Determine display label: ortho view name > USD cam prim path > "Free Camera"
|
|
const char* currentLabel = "Free Camera";
|
|
if (m_orthoView != OrthoView::None) {
|
|
currentLabel = OrthoName(m_orthoView);
|
|
} else if (m_selectedCameraIndex > 0 &&
|
|
static_cast<size_t>(m_selectedCameraIndex - 1) < m_cameraPaths.size()) {
|
|
currentLabel = m_cameraPaths[m_selectedCameraIndex - 1].GetText();
|
|
}
|
|
|
|
std::string camComboId = "##Cam_" + std::to_string(tileIndex);
|
|
if (ImGui::BeginCombo(camComboId.c_str(), currentLabel)) {
|
|
RefreshCameraList();
|
|
|
|
// -- Perspective free camera -----------------------------------------
|
|
bool freeSel = (m_orthoView == OrthoView::None && m_selectedCameraIndex == 0);
|
|
if (ImGui::Selectable("Free Camera", freeSel)) {
|
|
m_orthoView = OrthoView::None;
|
|
m_selectedCameraIndex = 0;
|
|
m_isDrivingUsdCamPrim = false;
|
|
if (m_hasSavedFreeCameraState)
|
|
m_camera.SwitchToFreeCamera(&m_savedFreeCameraState);
|
|
else
|
|
m_camera.SwitchToFreeCamera(
|
|
m_hasLastGfCamera ? &m_lastComputedGfCamera : nullptr);
|
|
}
|
|
|
|
// -- Orthographic presets -------------------------------------------
|
|
ImGui::Separator();
|
|
static const struct { OrthoView view; const char* label; } kOrtho[] = {
|
|
{ OrthoView::Top, "Top" },
|
|
{ OrthoView::Bottom, "Bottom" },
|
|
{ OrthoView::Front, "Front" },
|
|
{ OrthoView::Back, "Back" },
|
|
{ OrthoView::Left, "Left" },
|
|
{ OrthoView::Right, "Right" },
|
|
};
|
|
for (const auto& ov : kOrtho) {
|
|
bool isSel = (m_orthoView == ov.view);
|
|
if (ImGui::Selectable(ov.label, isSel)) {
|
|
m_orthoView = ov.view;
|
|
m_selectedCameraIndex = 0;
|
|
m_isDrivingUsdCamPrim = false;
|
|
// Ensure free-camera mode so center/dist are maintained
|
|
if (m_camera.GetMode() == ViewportCamera::CameraMode::UsdCamera) {
|
|
m_camera.SwitchToFreeCamera(
|
|
m_hasLastGfCamera ? &m_lastComputedGfCamera : nullptr);
|
|
}
|
|
}
|
|
if (isSel) ImGui::SetItemDefaultFocus();
|
|
}
|
|
|
|
// -- USD camera prims -----------------------------------------------
|
|
if (!m_cameraPaths.empty()) ImGui::Separator();
|
|
for (size_t i = 0; i < m_cameraPaths.size(); ++i) {
|
|
bool isSelected = (m_orthoView == OrthoView::None &&
|
|
m_selectedCameraIndex == static_cast<int>(i + 1));
|
|
if (ImGui::Selectable(m_cameraPaths[i].GetText(), isSelected)) {
|
|
if (m_camera.GetMode() == ViewportCamera::CameraMode::Free
|
|
&& m_hasLastGfCamera)
|
|
{
|
|
m_savedFreeCameraState = m_lastComputedGfCamera;
|
|
m_hasSavedFreeCameraState = true;
|
|
}
|
|
m_orthoView = OrthoView::None;
|
|
m_selectedCameraIndex = static_cast<int>(i + 1);
|
|
m_camera.SetUsdCamera(m_cameraPaths[i]);
|
|
m_renderer.SetCameraPath(m_cameraPaths[i]);
|
|
}
|
|
if (isSelected) ImGui::SetItemDefaultFocus();
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
if (m_orthoView != OrthoView::None)
|
|
ImGui::SetTooltip("Orthographic: %s\n"
|
|
"Alt+LMB / Alt+MMB: Pan\n"
|
|
"Alt+RMB / Scroll: Zoom\n"
|
|
"F: Frame selection A: Frame all",
|
|
OrthoName(m_orthoView));
|
|
else
|
|
ImGui::SetTooltip("Camera\n"
|
|
"Alt+LMB: Orbit Alt+MMB: Pan Alt+RMB: Dolly\n"
|
|
"Scroll: Zoom F: Frame sel A: Frame all");
|
|
}
|
|
ImGui::PopItemWidth();
|
|
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("|");
|
|
ImGui::SameLine();
|
|
|
|
// -- Render delegate (global) --------------------------------------------
|
|
{
|
|
pxr::TfToken currentId = m_renderer.GetCurrentRendererId();
|
|
std::string displayName = currentId.IsEmpty()
|
|
? "Rdr"
|
|
: UsdSceneRenderer::GetRendererDisplayName(currentId);
|
|
if (displayName.size() > 8) displayName = displayName.substr(0, 8);
|
|
|
|
std::string rdrPopupId = "RdrPopup_" + std::to_string(tileIndex);
|
|
ImGui::Button(displayName.c_str(), ImVec2(72.0f, 0.0f));
|
|
if (ImGui::IsItemHovered())
|
|
ImGui::SetTooltip("Render delegate");
|
|
|
|
if (ImGui::BeginPopupContextItem(rdrPopupId.c_str(),
|
|
ImGuiPopupFlags_MouseButtonLeft))
|
|
{
|
|
for (const auto& pluginId : UsdSceneRenderer::GetRendererPlugins()) {
|
|
std::string name = UsdSceneRenderer::GetRendererDisplayName(pluginId);
|
|
if (name.empty()) name = pluginId.GetString();
|
|
bool selected = (pluginId == currentId);
|
|
if (ImGui::MenuItem(name.c_str(), nullptr, selected) && !selected)
|
|
m_renderer.SetRendererPlugin(pluginId);
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("|");
|
|
ImGui::SameLine();
|
|
|
|
// -- View option toggles (icon buttons) ----------------------------------
|
|
auto iconToggle = [this](const char* id, const char* fallback,
|
|
Icon iconEnum, bool active,
|
|
const char* tooltip) -> bool
|
|
{
|
|
const ImVec4 kActive (0.26f, 0.59f, 0.98f, 1.00f);
|
|
const ImVec4 kActiveHv(0.36f, 0.69f, 1.00f, 1.00f);
|
|
if (active) {
|
|
ImGui::PushStyleColor(ImGuiCol_Button, kActive);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kActiveHv);
|
|
}
|
|
bool clicked = false;
|
|
if (m_iconManager) {
|
|
ImTextureID tex = m_iconManager->Get(iconEnum);
|
|
clicked = ImGui::ImageButton(id, ImTextureRef(tex), ImVec2(16.f, 16.f));
|
|
} else {
|
|
clicked = ImGui::Button(fallback);
|
|
}
|
|
if (active) ImGui::PopStyleColor(2);
|
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tooltip);
|
|
return clicked;
|
|
};
|
|
|
|
std::string gridId = "##grd_" + std::to_string(tileIndex);
|
|
std::string aaId = "##aa_" + std::to_string(tileIndex);
|
|
|
|
bool showGrid = m_renderer.ShowGrid();
|
|
if (iconToggle(gridId.c_str(), "Grd", Icon::Grid, showGrid, "Toggle grid"))
|
|
m_renderer.SetShowGrid(!showGrid);
|
|
|
|
ImGui::SameLine();
|
|
|
|
bool aa = m_renderer.GetAAEnabled();
|
|
if (iconToggle(aaId.c_str(), "AA", Icon::Antialias, aa, "Toggle anti-aliasing"))
|
|
m_renderer.SetAAEnabled(!aa);
|
|
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("|");
|
|
ImGui::SameLine();
|
|
|
|
// -- Gear / viewport options popup ---------------------------------------
|
|
{
|
|
std::string gearId = "##gear_" + std::to_string(tileIndex);
|
|
std::string gearPopupId = "GearPopup_" + std::to_string(tileIndex);
|
|
|
|
if (m_iconManager) {
|
|
ImTextureID tex = m_iconManager->Get(Icon::Settings);
|
|
ImGui::ImageButton(gearId.c_str(), ImTextureRef(tex), ImVec2(16.f, 16.f));
|
|
} else {
|
|
ImGui::Button("*");
|
|
}
|
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Viewport options");
|
|
|
|
if (ImGui::BeginPopupContextItem(gearPopupId.c_str(),
|
|
ImGuiPopupFlags_MouseButtonLeft))
|
|
{
|
|
// -- Shading mode ------------------------------------------------
|
|
if (ImGui::BeginMenu("Shading")) {
|
|
ShadingMode cur = m_renderer.GetShadingMode();
|
|
auto shadingItem = [&](const char* label, ShadingMode mode) {
|
|
if (ImGui::MenuItem(label, nullptr, cur == mode))
|
|
m_renderer.SetShadingMode(mode);
|
|
};
|
|
shadingItem("Smooth Shaded", ShadingMode::SmoothShaded);
|
|
shadingItem("Flat Shaded", ShadingMode::FlatShaded);
|
|
shadingItem("Wireframe on Surface", ShadingMode::WireframeOnSurface);
|
|
shadingItem("Wireframe", ShadingMode::Wireframe);
|
|
shadingItem("Unlit", ShadingMode::Unlit);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
// -- AOV ---------------------------------------------------------
|
|
{
|
|
pxr::TfTokenVector aovs = m_renderer.GetRendererAovs();
|
|
if (ImGui::BeginMenu("AOV", !aovs.empty())) {
|
|
pxr::TfToken currentAov = m_renderer.GetCurrentAov();
|
|
for (const auto& aov : aovs) {
|
|
bool checked = (aov == currentAov);
|
|
if (ImGui::MenuItem(aov.GetText(), nullptr, checked) && !checked)
|
|
m_renderer.SetCurrentAov(aov);
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
// -- Lighting ----------------------------------------------------
|
|
if (ImGui::BeginMenu("Lighting")) {
|
|
bool ambient = m_renderer.GetAmbientLightOnly();
|
|
if (ImGui::MenuItem("Camera Headlight", nullptr, ambient))
|
|
m_renderer.SetAmbientLightOnly(true);
|
|
if (ImGui::MenuItem("Scene Lights", nullptr, !ambient))
|
|
m_renderer.SetAmbientLightOnly(false);
|
|
ImGui::Separator();
|
|
bool dome = m_renderer.GetDomeLightEnabled();
|
|
if (ImGui::MenuItem("Dome Light", nullptr, dome))
|
|
m_renderer.SetDomeLightEnabled(!dome);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
// -- Display toggles ---------------------------------------------
|
|
{
|
|
bool grid = m_renderer.ShowGrid();
|
|
if (ImGui::MenuItem("Show Grid", nullptr, grid))
|
|
m_renderer.SetShowGrid(!grid);
|
|
|
|
bool guide = m_renderer.ShowCameraGuide();
|
|
if (ImGui::MenuItem("Camera Guide", nullptr, guide))
|
|
m_renderer.SetShowCameraGuide(!guide);
|
|
|
|
ImGui::Separator();
|
|
bool purGuide = m_renderer.ShowGuides();
|
|
if (ImGui::MenuItem("Purpose: Guide", nullptr, purGuide))
|
|
m_renderer.SetShowGuides(!purGuide);
|
|
bool purProxy = m_renderer.ShowProxy();
|
|
if (ImGui::MenuItem("Purpose: Proxy", nullptr, purProxy))
|
|
m_renderer.SetShowProxy(!purProxy);
|
|
bool purRender = m_renderer.ShowRender();
|
|
if (ImGui::MenuItem("Purpose: Render", nullptr, purRender))
|
|
m_renderer.SetShowRender(!purRender);
|
|
|
|
bool aaItem = m_renderer.GetAAEnabled();
|
|
if (ImGui::MenuItem("Anti-Aliasing", nullptr, aaItem))
|
|
m_renderer.SetAAEnabled(!aaItem);
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
// -- BBox --------------------------------------------------------
|
|
if (ImGui::BeginMenu("Bounding Box")) {
|
|
BBoxMode bbox = m_renderer.GetBBoxMode();
|
|
if (ImGui::MenuItem("None", nullptr, bbox == BBoxMode::None))
|
|
m_renderer.SetBBoxMode(BBoxMode::None);
|
|
if (ImGui::MenuItem("Per Object", nullptr, bbox == BBoxMode::PerObject))
|
|
m_renderer.SetBBoxMode(BBoxMode::PerObject);
|
|
if (ImGui::MenuItem("All Selection", nullptr, bbox == BBoxMode::AllSelection))
|
|
m_renderer.SetBBoxMode(BBoxMode::AllSelection);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
// -- Background --------------------------------------------------
|
|
if (ImGui::BeginMenu("Background")) {
|
|
auto& bg = m_renderer.GetBackgroundColor();
|
|
if (ImGui::MenuItem("Dark Gray", nullptr, bg == pxr::GfVec3f(0.15f, 0.15f, 0.15f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.15f, 0.15f, 0.15f));
|
|
if (ImGui::MenuItem("Black", nullptr, bg == pxr::GfVec3f(0.0f, 0.0f, 0.0f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.0f, 0.0f, 0.0f));
|
|
if (ImGui::MenuItem("Light Gray", nullptr, bg == pxr::GfVec3f(0.45f, 0.45f, 0.45f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.45f, 0.45f, 0.45f));
|
|
if (ImGui::MenuItem("Midnight Blue", nullptr, bg == pxr::GfVec3f(0.1f, 0.1f, 0.2f)))
|
|
m_renderer.SetBackgroundColor(pxr::GfVec3f(0.1f, 0.1f, 0.2f));
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem("Renderer Settings..."))
|
|
m_showRendererSettings = true;
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
ImGui::PopStyleVar(); // ItemSpacing
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RenderManipulatorOverlay -- vertical Q/W/E/R + space buttons on the image
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::RenderManipulatorOverlay(TransformManipulator& manipulator)
|
|
{
|
|
const float kButtonSize = 32.0f;
|
|
const float kIconPad = 4.0f;
|
|
const float kRounding = 4.0f;
|
|
const float kPadX = 10.0f;
|
|
const float kPadY = 10.0f;
|
|
const float kSpacing = 4.0f;
|
|
const ImVec2 kBtnSz(kButtonSize, kButtonSize);
|
|
|
|
ImVec2 origin(m_imageScreenPos.x + kPadX, m_imageScreenPos.y + kPadY);
|
|
|
|
struct ToolInfo {
|
|
ManipulatorMode mode;
|
|
Icon icon;
|
|
const char* fallbackLabel;
|
|
const char* tooltip;
|
|
const char* id;
|
|
};
|
|
static const ToolInfo tools[] = {
|
|
{ ManipulatorMode::Select, Icon::ToolSelect, "Q", "Select (Q)", "##ovl_q" },
|
|
{ ManipulatorMode::Move, Icon::ToolMove, "W", "Move (W)", "##ovl_w" },
|
|
{ ManipulatorMode::Rotate, Icon::ToolRotate, "E", "Rotate (E)", "##ovl_e" },
|
|
{ ManipulatorMode::Scale, Icon::ToolScale, "R", "Scale (R)", "##ovl_r" },
|
|
};
|
|
|
|
ManipulatorMode current = manipulator.GetMode();
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
const auto& t = tools[i];
|
|
bool active = (current == t.mode);
|
|
ImVec2 btnMin(origin.x, origin.y + i * (kButtonSize + kSpacing));
|
|
ImVec2 btnMax(btnMin.x + kButtonSize, btnMin.y + kButtonSize);
|
|
|
|
ImGui::SetCursorScreenPos(btnMin);
|
|
bool clicked = ImGui::InvisibleButton(t.id, kBtnSz);
|
|
bool hovBtn = ImGui::IsItemHovered();
|
|
|
|
ImU32 bgCol = active ? IM_COL32( 66, 150, 250, 230) :
|
|
hovBtn ? IM_COL32( 64, 64, 64, 220) :
|
|
IM_COL32( 26, 26, 26, 178);
|
|
dl->AddRectFilled(btnMin, btnMax, bgCol, kRounding);
|
|
|
|
if (m_iconManager) {
|
|
ImTextureID texId = m_iconManager->Get(t.icon);
|
|
dl->AddImage(ImTextureRef(texId),
|
|
ImVec2(btnMin.x + kIconPad, btnMin.y + kIconPad),
|
|
ImVec2(btnMax.x - kIconPad, btnMax.y - kIconPad));
|
|
} else {
|
|
float tx = btnMin.x + (kButtonSize - ImGui::CalcTextSize(t.fallbackLabel).x) * 0.5f;
|
|
float ty = btnMin.y + (kButtonSize - ImGui::GetTextLineHeight()) * 0.5f;
|
|
dl->AddText(ImVec2(tx, ty), IM_COL32(255,255,255,255), t.fallbackLabel);
|
|
}
|
|
|
|
if (active)
|
|
dl->AddRect(btnMin, btnMax, IM_COL32(100, 180, 255, 200), kRounding, 0, 1.5f);
|
|
if (clicked) manipulator.SetMode(t.mode);
|
|
if (hovBtn) ImGui::SetTooltip("%s", t.tooltip);
|
|
}
|
|
|
|
// World / Object space toggle
|
|
float spaceY = origin.y + 4 * (kButtonSize + kSpacing) + 4.0f;
|
|
ImVec2 spBtnMin(origin.x, spaceY);
|
|
ImVec2 spBtnMax(spBtnMin.x + kButtonSize, spBtnMin.y + kButtonSize);
|
|
bool isWorld = (manipulator.GetTransformSpace() == TransformSpace::World);
|
|
|
|
ImGui::SetCursorScreenPos(spBtnMin);
|
|
bool spClicked = ImGui::InvisibleButton("##ovl_space", kBtnSz);
|
|
bool spHovered = ImGui::IsItemHovered();
|
|
|
|
ImU32 spBgCol = isWorld ? IM_COL32( 66, 150, 250, 230) :
|
|
spHovered ? IM_COL32( 64, 64, 64, 220) :
|
|
IM_COL32( 26, 26, 26, 178);
|
|
dl->AddRectFilled(spBtnMin, spBtnMax, spBgCol, kRounding);
|
|
|
|
if (m_iconManager) {
|
|
ImTextureID spTex = m_iconManager->Get(isWorld ? Icon::WorldSpace : Icon::LocalSpace);
|
|
dl->AddImage(ImTextureRef(spTex),
|
|
ImVec2(spBtnMin.x + kIconPad, spBtnMin.y + kIconPad),
|
|
ImVec2(spBtnMax.x - kIconPad, spBtnMax.y - kIconPad));
|
|
} else {
|
|
const char* spLabel = isWorld ? "W" : "O";
|
|
float spTx = spBtnMin.x + (kButtonSize - ImGui::CalcTextSize(spLabel).x) * 0.5f;
|
|
float spTy = spBtnMin.y + (kButtonSize - ImGui::GetTextLineHeight()) * 0.5f;
|
|
dl->AddText(ImVec2(spTx, spTy), IM_COL32(255,255,255,255), spLabel);
|
|
}
|
|
if (isWorld)
|
|
dl->AddRect(spBtnMin, spBtnMax, IM_COL32(100,180,255,200), kRounding, 0, 1.5f);
|
|
if (spClicked)
|
|
manipulator.SetTransformSpace(isWorld ? TransformSpace::Object : TransformSpace::World);
|
|
if (spHovered)
|
|
ImGui::SetTooltip(isWorld
|
|
? "World space\nClick to switch to Object space"
|
|
: "Object space\nClick to switch to World space");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Render
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::Render(int tileIndex, ImVec2 pos, ImVec2 size,
|
|
bool isFocused, TransformManipulator& manipulator,
|
|
bool dividerActive)
|
|
{
|
|
m_wasClickedThisFrame = false;
|
|
m_wasHoveredThisFrame = false;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
|
ImGui::SetCursorScreenPos(pos);
|
|
|
|
std::string childId = "##vptile_" + std::to_string(tileIndex);
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollbar
|
|
| ImGuiWindowFlags_NoScrollWithMouse
|
|
| ImGuiWindowFlags_NoBackground;
|
|
|
|
bool childOpen = ImGui::BeginChild(childId.c_str(), size, false, flags);
|
|
ImGui::PopStyleVar(); // WindowPadding
|
|
|
|
if (!childOpen) {
|
|
ImGui::EndChild();
|
|
return;
|
|
}
|
|
|
|
// Hover detection for the whole child (used by Space maximize in container)
|
|
m_wasHoveredThisFrame = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
|
|
|
|
// NOTE: Border is drawn AFTER EndChild (see below) so it is not clipped
|
|
// by this child window's scissor rect.
|
|
|
|
// -- Compact toolbar ------------------------------------------------------
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 2));
|
|
ImGui::SetCursorPos(ImVec2(4, 2));
|
|
RenderCompactToolbar(tileIndex);
|
|
ImGui::PopStyleVar();
|
|
ImGui::Separator();
|
|
|
|
// -- Scene image area -----------------------------------------------------
|
|
ImVec2 avail = ImGui::GetContentRegionAvail();
|
|
m_viewWidth = static_cast<int>(avail.x);
|
|
m_viewHeight = static_cast<int>(avail.y);
|
|
|
|
if (m_viewWidth > 0 && m_viewHeight > 0 && m_stage) {
|
|
m_imageScreenPos = ImGui::GetCursorScreenPos();
|
|
|
|
HandleInput(isFocused, manipulator, dividerActive);
|
|
|
|
pxr::GfCamera gfCamera = ResolveCamera();
|
|
|
|
// Render scene into FBO
|
|
m_renderer.Render(m_viewWidth, m_viewHeight);
|
|
|
|
// Overlays into FBO
|
|
if (m_hasLastGfCamera) {
|
|
pxr::GfFrustum frustum = gfCamera.GetFrustum();
|
|
pxr::GfMatrix4d viewProj =
|
|
frustum.ComputeViewMatrix() * frustum.ComputeProjectionMatrix();
|
|
m_renderer.DrawAxis(viewProj, m_camera.GetDist());
|
|
m_renderer.DrawBoundingBoxes(m_selectedSdfPaths, viewProj);
|
|
m_renderer.DrawCameraWireframes(
|
|
m_stage, m_selectedSdfPaths,
|
|
m_camera.GetUsdCameraPath(),
|
|
viewProj, m_camera.GetDist());
|
|
m_renderer.DrawLightWireframes(
|
|
m_stage, m_selectedSdfPaths,
|
|
viewProj, m_camera.GetDist());
|
|
}
|
|
|
|
// Display FBO texture (flip UV Y: OpenGL is bottom-up)
|
|
uint32_t texID = m_renderer.GetColorTextureID();
|
|
if (texID != 0) {
|
|
ImGui::Image(
|
|
ImTextureID(static_cast<ImU64>(texID)),
|
|
ImVec2(static_cast<float>(m_viewWidth),
|
|
static_cast<float>(m_viewHeight)),
|
|
ImVec2(0, 1), ImVec2(1, 0));
|
|
} else {
|
|
ImGui::TextColored(ImVec4(1,0,0,1),
|
|
"Viewport: No texture (renderer not initialised)");
|
|
}
|
|
|
|
// -- 2-D ImGui overlays on top of the image --------------------------
|
|
DrawSelectionRect();
|
|
|
|
// Gizmo -- only in focused tile
|
|
if (isFocused && m_hasLastGfCamera &&
|
|
manipulator.GetMode() != ManipulatorMode::Select &&
|
|
!m_selectedSdfPaths.empty())
|
|
{
|
|
pxr::GfFrustum frustum = gfCamera.GetFrustum();
|
|
pxr::GfMatrix4d viewProj =
|
|
frustum.ComputeViewMatrix() * frustum.ComputeProjectionMatrix();
|
|
pxr::GfVec3d pivot = ComputeGizmoPivot();
|
|
pxr::GfVec3d camEye = frustum.GetPosition();
|
|
|
|
manipulator.Render(
|
|
ImGui::GetWindowDrawList(),
|
|
viewProj, pivot, camEye,
|
|
m_imageScreenPos,
|
|
m_viewWidth, m_viewHeight);
|
|
}
|
|
|
|
} else {
|
|
if (!m_stage)
|
|
ImGui::TextDisabled("No stage loaded");
|
|
else
|
|
ImGui::TextDisabled("Viewport too small (%dx%d)", m_viewWidth, m_viewHeight);
|
|
}
|
|
|
|
// -- Focus / hover border -------------------------------------------------
|
|
// Drawn as the LAST item inside the child window so it sits above the scene
|
|
// texture in the draw list sequence. Because it is inside the child window
|
|
// it is automatically clipped to this tile's bounds and cannot bleed over
|
|
// floating panels or menus positioned outside this tile.
|
|
// A 1 px inset ensures the full 2 px focused line stays within the clip rect.
|
|
{
|
|
ImVec2 bMin(pos.x + 1.f, pos.y + 1.f);
|
|
ImVec2 bMax(pos.x + size.x - 1.f, pos.y + size.y - 1.f);
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
|
|
if (isFocused) {
|
|
dl->AddRect(bMin, bMax, IM_COL32(66, 150, 250, 230), 0.f, 0, 2.0f);
|
|
} else if (m_wasHoveredThisFrame) {
|
|
dl->AddRect(bMin, bMax, IM_COL32(220, 220, 220, 110), 0.f, 0, 1.0f);
|
|
}
|
|
}
|
|
|
|
RenderContextMenu(tileIndex);
|
|
ImGui::EndChild();
|
|
|
|
if (m_showRendererSettings)
|
|
RenderRendererSettingsDialog(tileIndex);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RenderRendererSettingsDialog
|
|
// ---------------------------------------------------------------------------
|
|
void ViewportTile::RenderRendererSettingsDialog(int tileIndex)
|
|
{
|
|
using namespace pxr;
|
|
std::string title = "Renderer Settings [" +
|
|
UsdSceneRenderer::GetRendererDisplayName(m_renderer.GetCurrentRendererId()) +
|
|
"]###RendererSettingsDlg_" + std::to_string(tileIndex);
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(520, 460), ImGuiCond_FirstUseEver);
|
|
// WindowPadding is captured by Begin(); pop it immediately after.
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.f, 12.f));
|
|
bool open = ImGui::Begin(title.c_str(), &m_showRendererSettings,
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking);
|
|
ImGui::PopStyleVar();
|
|
if (!open) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// ItemSpacing and FramePadding are read dynamically; push for content, pop before End.
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 6.f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.f, 4.f));
|
|
|
|
// Cache the settings list per delegate — GetRendererSettingsList() calls TF_WARN
|
|
// for every unsupported setting type, so querying it every frame floods the log.
|
|
pxr::TfToken curId = m_renderer.GetCurrentRendererId();
|
|
if (curId != m_cachedSettingsRendererId) {
|
|
m_cachedRendererSettings = m_renderer.GetRendererSettingsList();
|
|
m_cachedSettingsRendererId = curId;
|
|
}
|
|
const auto& settings = m_cachedRendererSettings;
|
|
if (settings.empty()) {
|
|
ImVec2 sz = ImGui::GetContentRegionAvail();
|
|
ImGui::SetCursorPos(ImVec2(sz.x * 0.5f - 120.f, sz.y * 0.5f - 8.f));
|
|
ImGui::TextDisabled("This renderer exposes no settings.");
|
|
ImGui::PopStyleVar(2);
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Group settings by namespace prefix (split key at first ':').
|
|
// Keys without ':' go into "General". std::map keeps alphabetical order.
|
|
std::map<std::string, std::vector<const UsdImagingGLRendererSetting*>> groups;
|
|
for (const auto& s : settings) {
|
|
std::string k = s.key.GetString();
|
|
auto colon = k.find(':');
|
|
std::string cat = (colon != std::string::npos) ? k.substr(0, colon) : "General";
|
|
if (!cat.empty()) cat[0] = static_cast<char>(toupper(static_cast<unsigned char>(cat[0])));
|
|
groups[cat].push_back(&s);
|
|
}
|
|
|
|
bool singleGroup = (groups.size() == 1);
|
|
bool tabBarOpen = false;
|
|
if (!singleGroup)
|
|
tabBarOpen = ImGui::BeginTabBar("##rdrsettings_tabs");
|
|
|
|
auto renderGroup = [&](const std::vector<const UsdImagingGLRendererSetting*>& items) {
|
|
float tableH = ImGui::GetContentRegionAvail().y - 40.f;
|
|
if (tableH < 20.f) tableH = 20.f;
|
|
if (ImGui::BeginTable("##srows", 2,
|
|
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH |
|
|
ImGuiTableFlags_ScrollY, ImVec2(0, tableH)))
|
|
{
|
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.55f);
|
|
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.45f);
|
|
for (const auto* sp : items) {
|
|
const auto& s = *sp;
|
|
VtValue cur = m_renderer.GetRendererSetting(s.key);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::AlignTextToFramePadding();
|
|
ImGui::TextUnformatted(s.name.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::PushItemWidth(-1.f);
|
|
std::string wid = "##" + s.key.GetString();
|
|
if (s.type == UsdImagingGLRendererSetting::TYPE_FLAG) {
|
|
bool v = cur.GetWithDefault<bool>(false);
|
|
if (ImGui::Checkbox(wid.c_str(), &v))
|
|
m_renderer.SetRendererSetting(s.key, VtValue(v));
|
|
} else if (s.type == UsdImagingGLRendererSetting::TYPE_INT) {
|
|
int v = cur.GetWithDefault<int>(0);
|
|
if (ImGui::DragInt(wid.c_str(), &v))
|
|
m_renderer.SetRendererSetting(s.key, VtValue(v));
|
|
} else if (s.type == UsdImagingGLRendererSetting::TYPE_FLOAT) {
|
|
float v = cur.GetWithDefault<float>(0.f);
|
|
if (ImGui::DragFloat(wid.c_str(), &v, 0.01f))
|
|
m_renderer.SetRendererSetting(s.key, VtValue(v));
|
|
} else if (s.type == UsdImagingGLRendererSetting::TYPE_STRING) {
|
|
std::string sv = cur.GetWithDefault<std::string>("");
|
|
char buf[256];
|
|
strncpy(buf, sv.c_str(), sizeof(buf) - 1);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
if (ImGui::InputText(wid.c_str(), buf, sizeof(buf)) &&
|
|
ImGui::IsItemDeactivatedAfterEdit())
|
|
m_renderer.SetRendererSetting(s.key, VtValue(std::string(buf)));
|
|
}
|
|
ImGui::PopItemWidth();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
};
|
|
|
|
if (singleGroup) {
|
|
renderGroup(groups.begin()->second);
|
|
} else if (tabBarOpen) {
|
|
// Non-"General" tabs first (alphabetical via std::map), then "General" last
|
|
for (const auto& [cat, items] : groups) {
|
|
if (cat == "General") continue;
|
|
if (ImGui::BeginTabItem(cat.c_str())) {
|
|
renderGroup(items);
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
auto genIt = groups.find("General");
|
|
if (genIt != groups.end() && ImGui::BeginTabItem("General")) {
|
|
renderGroup(genIt->second);
|
|
ImGui::EndTabItem();
|
|
}
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Reset Defaults")) {
|
|
for (const auto& s : settings)
|
|
m_renderer.SetRendererSetting(s.key, s.defValue);
|
|
}
|
|
|
|
ImGui::PopStyleVar(2);
|
|
ImGui::End();
|
|
}
|
|
|
|
} // namespace UsdLayerManager
|