#include "ViewportCamera.h" #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif namespace UsdLayerManager { // --------------------------------------------------------------------------- // Helper: rotation matrix around `axis` by `angleDeg` degrees // --------------------------------------------------------------------------- static pxr::GfMatrix4d RotMatrix(const pxr::GfVec3d& axis, double angleDeg) { return pxr::GfMatrix4d(1.0).SetRotate(pxr::GfRotation(axis, angleDeg)); } // =========================================================================== // Construction // =========================================================================== ViewportCamera::ViewportCamera() : m_cameraTransformDirty(true) , m_rotTheta(0.0) , m_rotPhi(0.0) , m_rotPsi(0.0) , m_center(0.0, 0.0, 0.0) , m_dist(100.0) , m_selSize(10.0) , m_isZUp(false) , m_YZUpMatrix(1.0) , m_YZUpInvMatrix(1.0) , m_hasClosestVisibleDist(false) , m_closestVisibleDist(0.0) , m_lastFramedDist(100.0) , m_lastFramedClosestDist(0.0) , m_overrideNear(-1.0) , m_overrideFar(-1.0) , m_mode(CameraMode::Free) { // Default: perspective camera, vertical FOV = 60°, square aspect ratio. m_camera.SetPerspectiveFromAspectRatioAndFieldOfView( 1.0f, 60.0f, pxr::GfCamera::FOVVertical); m_camera.SetFocusDistance(static_cast(m_dist)); ResetClippingPlanes(); } // =========================================================================== // Stage // =========================================================================== void ViewportCamera::SetStage(pxr::UsdStageRefPtr stage) { m_stage = stage; m_isZUp = stage && (pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z); if (m_isZUp) { // GfCamera.Y_UP_TO_Z_UP_MATRIX: rotate -90° around X axis m_YZUpMatrix = pxr::GfMatrix4d(1.0).SetRotate( pxr::GfRotation(pxr::GfVec3d::XAxis(), -90.0)); m_YZUpInvMatrix = m_YZUpMatrix.GetInverse(); } else { m_YZUpMatrix = pxr::GfMatrix4d(1.0); m_YZUpInvMatrix = pxr::GfMatrix4d(1.0); } m_cameraTransformDirty = true; } // =========================================================================== // Private: rebuild camera transform from orbital parameters // Mirrors FreeCamera._pushToCameraTransform() // =========================================================================== void ViewportCamera::PushToCameraTransform() { if (!m_cameraTransformDirty) return; // camera-to-world transform (same as FreeCamera._pushToCameraTransform): // T(dist*Z) * R(-psi,Z) * R(-phi,X) * R(-theta,Y) * YZUpInv * T(center) pxr::GfMatrix4d xform = pxr::GfMatrix4d(1.0).SetTranslate(pxr::GfVec3d::ZAxis() * m_dist) * RotMatrix(pxr::GfVec3d::ZAxis(), -m_rotPsi) * RotMatrix(pxr::GfVec3d::XAxis(), -m_rotPhi) * RotMatrix(pxr::GfVec3d::YAxis(), -m_rotTheta) * m_YZUpInvMatrix * pxr::GfMatrix4d(1.0).SetTranslate(m_center); m_camera.SetTransform(xform); m_camera.SetFocusDistance(static_cast(m_dist)); m_cameraTransformDirty = false; } // Mirrors FreeCamera._pullFromCameraTransform() void ViewportCamera::PullFromCameraTransform() { pxr::GfFrustum frustum = m_camera.GetFrustum(); m_dist = static_cast(m_camera.GetFocusDistance()); m_selSize = m_dist / 10.0; m_center = frustum.GetPosition() + m_dist * frustum.ComputeViewDirection(); pxr::GfMatrix4d camTransform = m_camera.GetTransform() * m_YZUpMatrix; camTransform.Orthonormalize(); pxr::GfRotation rotation = camTransform.ExtractRotation(); // Decompose: Y → theta, X → phi, Z → psi pxr::GfVec3d angles = rotation.Decompose( pxr::GfVec3d::YAxis(), pxr::GfVec3d::XAxis(), pxr::GfVec3d::ZAxis()); m_rotTheta = -angles[0]; m_rotPhi = -angles[1]; m_rotPsi = -angles[2]; m_cameraTransformDirty = true; } // =========================================================================== // Free Camera Operations // =========================================================================== void ViewportCamera::Tumble(double dTheta, double dPhi) { m_rotTheta += dTheta; m_rotPhi += dPhi; m_cameraTransformDirty = true; } void ViewportCamera::AdjustDistance(double scaleFactor) { // Mirrors FreeCamera.AdjustDistance(): prevents getting stuck near dist≈0. if (scaleFactor > 1.0 && m_dist < 2.0) { double selBasedIncr = m_selSize / 25.0; scaleFactor -= 1.0; m_dist += std::min(selBasedIncr, scaleFactor); } else { m_dist *= scaleFactor; } m_dist = std::max(m_dist, 0.001); // never let dist reach zero // Keep closest-visible-distance estimate in sync with new dist if (m_hasClosestVisibleDist) { if (m_dist > m_lastFramedDist) { m_closestVisibleDist = m_lastFramedClosestDist; } else { m_closestVisibleDist = m_lastFramedClosestDist - m_lastFramedDist + m_dist; } } m_cameraTransformDirty = true; } void ViewportCamera::Truck(double deltaRight, double deltaUp) { PushToCameraTransform(); pxr::GfFrustum frustum = m_camera.GetFrustum(); pxr::GfVec3d camUp = frustum.ComputeUpVector(); pxr::GfVec3d camRight = pxr::GfCross(frustum.ComputeViewDirection(), camUp); m_center += deltaRight * camRight + deltaUp * camUp; m_cameraTransformDirty = true; } double ViewportCamera::ComputePixelsToWorldFactor(double viewportHeight) { PushToCameraTransform(); pxr::GfFrustum frustum = m_camera.GetFrustum(); double frustumHeight = frustum.GetWindow().GetSize()[1]; return frustumHeight * m_dist / std::max(viewportHeight, 1.0); } void ViewportCamera::FrameSelection(const pxr::GfBBox3d& selBBox, double frameFit) { m_hasClosestVisibleDist = false; m_center = selBBox.ComputeCentroid(); pxr::GfRange3d selRange = selBBox.ComputeAlignedRange(); pxr::GfVec3d sz = selRange.GetSize(); m_selSize = std::max({ sz[0], sz[1], sz[2] }); // Distance calculation from FreeCamera.frameSelection() double fovRad = GetFOV() * M_PI / 180.0; double halfFovRad = std::max(fovRad * 0.5, 0.00872665); // at least ~0.5° double lengthToFit = m_selSize * frameFit * 0.5; m_dist = lengthToFit / std::tan(halfFovRad); // Prevent camera from intersecting the bounding box if (m_dist < kDefaultNear + m_selSize * 0.5) { m_dist = kDefaultNear + lengthToFit; } m_cameraTransformDirty = true; } // =========================================================================== // Clipping Planes // =========================================================================== std::pair ViewportCamera::RangeOfBoxAlongRay( const pxr::GfRay& camRay, const pxr::GfBBox3d& bbox) const { double maxDist = -1e38; double minDist = 1e38; const pxr::GfRange3d& boxRange = bbox.GetRange(); const pxr::GfMatrix4d& boxXform = bbox.GetMatrix(); for (int i = 0; i < 8; ++i) { pxr::GfVec3d corner = boxXform.Transform(boxRange.GetCorner(i)); double t = 0.0; camRay.FindClosestPoint(corner, &t); maxDist = std::max(maxDist, t); minDist = std::min(minDist, t); } minDist = (minDist < kDefaultNear) ? kDefaultNear : minDist * 0.99; maxDist *= 1.01; return { minDist, maxDist }; } void ViewportCamera::SetClippingPlanes(const pxr::GfBBox3d& stageBBox) { double computedNear, computedFar; if (stageBBox.GetRange().IsEmpty()) { computedNear = kDefaultNear; computedFar = kDefaultFar; } else { pxr::GfFrustum frustum = m_camera.GetFrustum(); pxr::GfVec3d camPos = frustum.GetPosition(); pxr::GfRay camRay(camPos, frustum.ComputeViewDirection()); auto boxRange = RangeOfBoxAlongRay(camRay, stageBBox); computedNear = boxRange.first; computedFar = boxRange.second; double precisionNear = computedFar / kMaxGoodZResolution; if (m_hasClosestVisibleDist) { double halfClose = m_closestVisibleDist / 2.0; if (m_closestVisibleDist < m_lastFramedClosestDist) { halfClose = std::max({ precisionNear, halfClose, computedNear }); } if (halfClose < computedNear) { computedNear = halfClose; } else if (precisionNear > computedNear) { computedNear = std::min((precisionNear + halfClose) / 2.0, halfClose); } } } double nearVal = (m_overrideNear > 0.0) ? m_overrideNear : computedNear; double farVal = (m_overrideFar > 0.0) ? m_overrideFar : computedFar; farVal = std::max(nearVal + 1.0, farVal); m_camera.SetClippingRange(pxr::GfRange1f( static_cast(nearVal), static_cast(farVal))); } void ViewportCamera::ResetClippingPlanes() { double nearVal = (m_overrideNear > 0.0) ? m_overrideNear : kDefaultNear; double farVal = (m_overrideFar > 0.0) ? m_overrideFar : kDefaultFar; m_camera.SetClippingRange(pxr::GfRange1f( static_cast(nearVal), static_cast(farVal))); } // =========================================================================== // Camera Resolution // =========================================================================== pxr::GfCamera ViewportCamera::ComputeGfCamera( const pxr::GfBBox3d& stageBBox, bool autoClip) { PushToCameraTransform(); if (autoClip) { SetClippingPlanes(stageBBox); } else { ResetClippingPlanes(); } return m_camera; } void ViewportCamera::SetClosestVisibleDistFromPoint(const pxr::GfVec3d& point) { PushToCameraTransform(); pxr::GfFrustum frustum = m_camera.GetFrustum(); pxr::GfVec3d camPos = frustum.GetPosition(); pxr::GfRay camRay(camPos, frustum.ComputeViewDirection()); double t = 0.0; camRay.FindClosestPoint(point, &t); m_closestVisibleDist = t; m_hasClosestVisibleDist = true; m_lastFramedDist = m_dist; m_lastFramedClosestDist = m_closestVisibleDist; } // =========================================================================== // Matrix Accessors // =========================================================================== pxr::GfMatrix4d ViewportCamera::GetViewMatrix() { PushToCameraTransform(); return m_camera.GetFrustum().ComputeViewMatrix(); } pxr::GfMatrix4d ViewportCamera::GetProjectionMatrix() { PushToCameraTransform(); return m_camera.GetFrustum().ComputeProjectionMatrix(); } // =========================================================================== // Compatibility Accessors // =========================================================================== pxr::GfVec3d ViewportCamera::GetEye() { PushToCameraTransform(); return m_camera.GetFrustum().GetPosition(); } // =========================================================================== // Camera Mode // =========================================================================== void ViewportCamera::SetUsdCamera(const pxr::SdfPath& cameraPath) { m_mode = CameraMode::UsdCamera; m_usdCameraPath = cameraPath; } void ViewportCamera::InitOrbitFromEyeAndCenter( const pxr::GfVec3d& eye, const pxr::GfVec3d& center, double dist) { m_center = center; m_dist = std::max(dist, 0.001); m_selSize = m_dist / 10.0; // Transform the eye-to-center offset into the Y-up orbital frame. // For Y-up stages m_YZUpMatrix is identity; for Z-up it's -90° around X. pxr::GfVec3d offset = m_YZUpMatrix.TransformDir(eye - center); double len = offset.GetLength(); if (len > 1e-6) { offset /= len; // In the Y-up orbital frame PushToCameraTransform places the eye at: // (-sin(theta)*cos(phi), sin(phi), cos(theta)*cos(phi)) m_rotPhi = std::asin(std::max(-1.0, std::min(1.0, offset[1]))) * (180.0 / M_PI); m_rotTheta = std::atan2(-offset[0], offset[2]) * (180.0 / M_PI); } m_rotPsi = 0.0; // zero roll — avoids gimbal weirdness on switch m_cameraTransformDirty = true; } void ViewportCamera::SwitchToFreeCamera(const pxr::GfCamera* lastGfCamera) { if (m_mode == CameraMode::Free) return; if (lastGfCamera) { // Initialize free-camera state from the last rendered GfCamera // (mirrors FreeCamera.FromGfCamera) m_camera = *lastGfCamera; PullFromCameraTransform(); } m_mode = CameraMode::Free; m_usdCameraPath = pxr::SdfPath(); m_cameraTransformDirty = true; } // =========================================================================== // Camera Settings // =========================================================================== double ViewportCamera::GetFOV() const { return static_cast( m_camera.GetFieldOfView(pxr::GfCamera::FOVVertical)); } void ViewportCamera::SetFOV(double fov) { m_camera.SetPerspectiveFromAspectRatioAndFieldOfView( m_camera.GetAspectRatio(), static_cast(fov), pxr::GfCamera::FOVVertical); } double ViewportCamera::GetAspectRatio() const { return static_cast(m_camera.GetAspectRatio()); } void ViewportCamera::SetAspectRatio(double aspect) { m_camera.SetPerspectiveFromAspectRatioAndFieldOfView( static_cast(aspect), m_camera.GetFieldOfView(pxr::GfCamera::FOVVertical), pxr::GfCamera::FOVVertical); } } // namespace UsdLayerManager