7d53eb18d4
Two issues caused the camera orientation to flip when tumbling a custom USD camera: 1. Write-back decomposed the camera matrix to XYZ-euler via Decompose(X,Y,Z) and re-authored it as a rotateXYZ op. That round-trip is lossy — the angles Decompose returns do not reconstruct the same matrix as a rotateXYZ op, so the orientation read back from the prim differed from the free-camera view shown during the drag. The view jumped every time a drag finished and snapped back on the next drag. Now author the full camera-to-world transform as a single matrix op, which round-trips exactly through UsdGeomCamera::GetCamera(). 2. Orbit init relied on PullFromCameraTransform's Euler decomposition for theta/phi, which is gimbal-affected. Added ViewportCamera::InitOrbitFromEyeAndCenter to derive theta/phi directly from the eye->center vector (zero roll), respecting the Z-up matrix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
192 lines
7.8 KiB
C++
192 lines
7.8 KiB
C++
#pragma once
|
|
|
|
#include <pxr/base/gf/camera.h>
|
|
#include <pxr/base/gf/matrix4d.h>
|
|
#include <pxr/base/gf/vec3d.h>
|
|
#include <pxr/base/gf/bbox3d.h>
|
|
#include <pxr/base/gf/range3d.h>
|
|
#include <pxr/base/gf/ray.h>
|
|
#include <pxr/usd/sdf/path.h>
|
|
#include <pxr/usd/usd/stage.h>
|
|
#include <utility>
|
|
|
|
namespace UsdLayerManager {
|
|
|
|
/// Free camera ported from Pixar's usdview FreeCamera (freeCamera.py).
|
|
/// Supports Tumble/Truck/AdjustDistance with Z-up stage handling,
|
|
/// automatic near/far clipping plane computation from scene bbox,
|
|
/// and USD prim-camera passthrough mode.
|
|
class ViewportCamera {
|
|
public:
|
|
enum class CameraMode { Free, UsdCamera };
|
|
|
|
static constexpr double kDefaultNear = 1.0;
|
|
static constexpr double kDefaultFar = 2000000.0;
|
|
static constexpr double kMaxSafeZResolution = 1e6;
|
|
static constexpr double kMaxGoodZResolution = 5e4;
|
|
|
|
ViewportCamera();
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Stage (sets Z-up flag and YZUp matrices)
|
|
// -----------------------------------------------------------------------
|
|
void SetStage(pxr::UsdStageRefPtr stage);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Free Camera Operations (mirrors FreeCamera.py)
|
|
// -----------------------------------------------------------------------
|
|
|
|
/// Orbit (tumble) around center by dTheta (horizontal) and dPhi (vertical) degrees.
|
|
void Tumble(double dTheta, double dPhi);
|
|
|
|
/// Scale distance from center by scaleFactor. Prevents getting stuck near zero.
|
|
void AdjustDistance(double scaleFactor);
|
|
|
|
/// Pan (truck) in camera-local right/up directions, world-unit deltas.
|
|
void Truck(double deltaRight, double deltaUp);
|
|
|
|
/// Returns pixels-to-world factor for correct Truck() scaling.
|
|
double ComputePixelsToWorldFactor(double viewportHeight);
|
|
|
|
/// Frame a bounding box. frameFit=1.1 gives ~10% margin (usdview default).
|
|
void FrameSelection(const pxr::GfBBox3d& selBBox, double frameFit = 1.1);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Camera Resolution: returns GfCamera with updated transform + clipping
|
|
// -----------------------------------------------------------------------
|
|
|
|
/// Returns the GfCamera with up-to-date transform.
|
|
/// If autoClip=true, near/far are computed from stageBBox for best precision.
|
|
pxr::GfCamera ComputeGfCamera(const pxr::GfBBox3d& stageBBox,
|
|
bool autoClip = false);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Convenience Matrix Accessors
|
|
// -----------------------------------------------------------------------
|
|
pxr::GfMatrix4d GetViewMatrix();
|
|
pxr::GfMatrix4d GetProjectionMatrix();
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Camera Mode
|
|
// -----------------------------------------------------------------------
|
|
CameraMode GetMode() const { return m_mode; }
|
|
const pxr::SdfPath& GetUsdCameraPath() const { return m_usdCameraPath; }
|
|
|
|
/// Switch to USD prim camera mode (renderer will call SetCameraPath).
|
|
void SetUsdCamera(const pxr::SdfPath& cameraPath);
|
|
|
|
/// Switch back to free camera, optionally initializing state from lastGfCamera.
|
|
void SwitchToFreeCamera(const pxr::GfCamera* lastGfCamera = nullptr);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Camera Settings
|
|
// -----------------------------------------------------------------------
|
|
double GetFOV() const; ///< Vertical FOV in degrees
|
|
void SetFOV(double fov);
|
|
double GetAspectRatio() const;
|
|
void SetAspectRatio(double aspect);
|
|
|
|
/// Hint for autoClip: the closest visible geometry point from pick result.
|
|
void SetClosestVisibleDistFromPoint(const pxr::GfVec3d& point);
|
|
|
|
// -----------------------------------------------------------------------
|
|
// State Queries
|
|
// -----------------------------------------------------------------------
|
|
double GetDist() const { return m_dist; }
|
|
bool IsZUp() const { return m_isZUp; }
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Compatibility accessors (for tests and legacy callers)
|
|
// -----------------------------------------------------------------------
|
|
|
|
/// Returns camera position in world space.
|
|
pxr::GfVec3d GetEye();
|
|
|
|
/// Returns the look-at focal point (center of orbit).
|
|
const pxr::GfVec3d& GetFocalPoint() const { return m_center; }
|
|
|
|
/// Set the look-at focal point directly.
|
|
/// Used by orthographic view panning to move the center in correct
|
|
/// view-space directions without going through the perspective Truck() path.
|
|
void SetFocalPoint(const pxr::GfVec3d& center) {
|
|
m_center = center;
|
|
m_cameraTransformDirty = true;
|
|
}
|
|
|
|
/// Set the orbit distance directly.
|
|
/// Used when initialising from a USD camera prim to override the camera's
|
|
/// authored focus distance (which is a DOF attribute, not an orbit radius).
|
|
void SetDist(double d) {
|
|
m_dist = d;
|
|
m_cameraTransformDirty = true;
|
|
}
|
|
|
|
/// Set orbital state from eye position and orbit center without Euler decomposition.
|
|
/// Computes theta/phi directly from the eye→center vector in the Y-up orbital frame;
|
|
/// zeroes roll. Use this after SwitchToFreeCamera() when initialising from a USD
|
|
/// camera prim to avoid gimbal-lock artifacts from the Decompose() path.
|
|
void InitOrbitFromEyeAndCenter(const pxr::GfVec3d& eye,
|
|
const pxr::GfVec3d& center,
|
|
double dist);
|
|
|
|
/// Returns the current near-clip distance.
|
|
double GetNearClip() const {
|
|
return static_cast<double>(m_camera.GetClippingRange().GetMin());
|
|
}
|
|
|
|
/// Returns the current far-clip distance.
|
|
double GetFarClip() const {
|
|
return static_cast<double>(m_camera.GetClippingRange().GetMax());
|
|
}
|
|
|
|
/// Legacy: frame a GfRange3d (wraps it in a unit-matrix GfBBox3d).
|
|
void FrameBoundingBox(const pxr::GfRange3d& range, double frameFit = 1.1) {
|
|
FrameSelection(pxr::GfBBox3d(range), frameFit);
|
|
}
|
|
|
|
/// Legacy: orbit alias (matches old Orbit(deltaYaw, deltaPitch) signature).
|
|
void Orbit(double deltaYaw, double deltaPitch) {
|
|
Tumble(deltaYaw, deltaPitch);
|
|
}
|
|
|
|
private:
|
|
void PushToCameraTransform();
|
|
void PullFromCameraTransform();
|
|
void SetClippingPlanes(const pxr::GfBBox3d& stageBBox);
|
|
void ResetClippingPlanes();
|
|
std::pair<double,double> RangeOfBoxAlongRay(
|
|
const pxr::GfRay& camRay, const pxr::GfBBox3d& bbox) const;
|
|
|
|
// Core camera object (owns aperture/projection/clipping/transform state)
|
|
pxr::GfCamera m_camera;
|
|
bool m_cameraTransformDirty;
|
|
|
|
// Orbital / tumble state
|
|
double m_rotTheta; // horizontal orbit (degrees, around Y)
|
|
double m_rotPhi; // vertical orbit (degrees, around X)
|
|
double m_rotPsi; // roll (degrees, usually 0)
|
|
pxr::GfVec3d m_center; // look-at center in world space
|
|
double m_dist; // distance camera → center
|
|
double m_selSize; // extent of last framed selection
|
|
|
|
// Stage up-axis handling
|
|
bool m_isZUp;
|
|
pxr::GfMatrix4d m_YZUpMatrix; // Y-up → Z-up (rotate -90° around X)
|
|
pxr::GfMatrix4d m_YZUpInvMatrix; // Z-up → Y-up (inverse)
|
|
|
|
// Auto-clip state
|
|
bool m_hasClosestVisibleDist;
|
|
double m_closestVisibleDist;
|
|
double m_lastFramedDist;
|
|
double m_lastFramedClosestDist;
|
|
double m_overrideNear; // ≤0 means "no override"
|
|
double m_overrideFar; // ≤0 means "no override"
|
|
|
|
// Camera mode
|
|
CameraMode m_mode;
|
|
pxr::SdfPath m_usdCameraPath;
|
|
pxr::UsdStageRefPtr m_stage;
|
|
};
|
|
|
|
} // namespace UsdLayerManager
|