Fix rotate issue
This commit is contained in:
parent
d4b46b438c
commit
3b0250b882
|
|
@ -1,9 +1,42 @@
|
|||
#include "CreatePrimCommand.h"
|
||||
#include "../../utils/Logger.h"
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ApplyCameraDefaultOrientation
|
||||
//
|
||||
// USD cameras always use +Y-up / look-down-−Z in their own local frame.
|
||||
// For a Y-up stage that is already the standard world forward direction, so
|
||||
// the identity transform is fine.
|
||||
//
|
||||
// For a Z-up stage the identity transform would point the camera straight
|
||||
// DOWN (world −Z = −up), which produces wrong orientation when the viewport
|
||||
// switches to the prim, and wrong tumble/roll directions when navigating.
|
||||
//
|
||||
// Fix: rotate the camera +90° around its local X axis so that:
|
||||
// camera local +Y (up) → world +Z (stage up axis) ✓
|
||||
// camera local −Z (view) → world +Y (horizontal "forward") ✓
|
||||
// ---------------------------------------------------------------------------
|
||||
static void ApplyCameraDefaultOrientation(pxr::UsdPrim& prim,
|
||||
pxr::UsdStageRefPtr stage)
|
||||
{
|
||||
if (!prim.IsValid() || !stage) return;
|
||||
|
||||
bool isZUp = (pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z);
|
||||
if (!isZUp) return; // Y-up default (identity) is already correct
|
||||
|
||||
pxr::UsdGeomXformCommonAPI xformAPI(prim);
|
||||
// +90° around X: camera up aligns with world Z, view direction becomes +Y
|
||||
xformAPI.SetRotate(pxr::GfVec3f(90.f, 0.f, 0.f),
|
||||
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
|
||||
pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
CreatePrimCommand::CreatePrimCommand(pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPath& primPath,
|
||||
const pxr::TfToken& typeName)
|
||||
|
|
@ -18,8 +51,16 @@ void CreatePrimCommand::Execute() {
|
|||
if (!m_stage) return;
|
||||
try {
|
||||
pxr::UsdPrim prim = m_stage->DefinePrim(m_primPath, m_typeName);
|
||||
if (!prim.IsValid())
|
||||
if (!prim.IsValid()) {
|
||||
LOG_ERROR("CreatePrimCommand: failed to define prim " + m_primPath.GetString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Camera-specific initialisation: orient the prim so the camera looks
|
||||
// in the correct "forward" direction for the stage's up-axis.
|
||||
if (m_typeName == pxr::TfToken("Camera"))
|
||||
ApplyCameraDefaultOrientation(prim, m_stage);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(std::string("CreatePrimCommand::Execute error: ") + e.what());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdGeom/xform.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/sdf/layer.h>
|
||||
#include <pxr/usd/sdf/reference.h>
|
||||
#include <pxr/usd/sdf/primSpec.h>
|
||||
|
|
@ -202,6 +203,17 @@ void SceneHierarchyPanel::Render() {
|
|||
try {
|
||||
UsdPrim newPrim = m_stage->DefinePrim(primPath, TfToken(typeName));
|
||||
if (newPrim.IsValid()) {
|
||||
// Set correct camera orientation for the current up-axis
|
||||
if (std::string(typeName) == "Camera") {
|
||||
bool isZUp = (UsdGeomGetStageUpAxis(m_stage) == UsdGeomTokens->z);
|
||||
if (isZUp) {
|
||||
pxr::UsdGeomXformCommonAPI xformAPI(newPrim);
|
||||
xformAPI.SetRotate(
|
||||
pxr::GfVec3f(90.f, 0.f, 0.f),
|
||||
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
|
||||
pxr::UsdTimeCode::Default());
|
||||
}
|
||||
}
|
||||
LOG_INFO("Created prim '" + primPath.GetString() + "' of type " + baseName);
|
||||
SetSelectedPathFromClick(primPath.GetString());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@
|
|||
#include <pxr/usd/usd/editContext.h>
|
||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||
#include <pxr/usd/usdGeom/xformCache.h>
|
||||
#include <pxr/usd/usdGeom/xformOp.h>
|
||||
#include <pxr/base/gf/matrix4d.h>
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/base/gf/vec3d.h>
|
||||
#include <pxr/base/gf/rotation.h>
|
||||
#include <pxr/base/gf/math.h>
|
||||
#include <pxr/base/vt/value.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
|
@ -566,6 +570,14 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& viewProj,
|
|||
consumed = true;
|
||||
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
// Defensive bounds check — m_dragAxis is always set to 0–2 at drag-start,
|
||||
// but guard here to prevent OOB on axes[3] if the invariant is ever broken.
|
||||
if (m_dragAxis < 0 || m_dragAxis > 2) {
|
||||
m_isDragging = false;
|
||||
m_dragAxis = -1;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
ImVec2 delta = { mouse.x - m_dragLastPos.x,
|
||||
mouse.y - m_dragLastPos.y };
|
||||
|
||||
|
|
@ -729,9 +741,114 @@ void TransformManipulator::ApplyRotateDelta(int axisIndex, float angleDeg)
|
|||
pxr::UsdEditContext ec(m_stage, m_stage->GetEditTarget());
|
||||
pxr::UsdGeomXformCommonAPI api(prim);
|
||||
|
||||
m_dragStartRotate[axisIndex] += angleDeg;
|
||||
api.SetRotate(m_dragStartRotate,
|
||||
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
|
||||
// ── Correct, gimbal-lock-free rotation (mirrors usdtweak RotationManipulator) ──
|
||||
//
|
||||
// The previous approach manually reconstructed the rotation matrix from
|
||||
// Euler angles using what turned out to be an incorrect axis-order
|
||||
// convention (GfRotation quaternion multiplication reverses the matrix
|
||||
// order relative to row-vector convention).
|
||||
//
|
||||
// The correct approach avoids all manual convention handling:
|
||||
//
|
||||
// Step 1 Use UsdGeomXformOp::GetOpTransform — USD builds the exact
|
||||
// matrix that SetRotate would produce, with no guessing.
|
||||
//
|
||||
// Step 2 Build the world-space delta as a GfRotation around the
|
||||
// gizmo axis and pre-multiply in parent space.
|
||||
//
|
||||
// Step 3 Decompose the result with GfRotation::DecomposeRotation
|
||||
// passing the INITIAL rotation axes (from drag start) as the
|
||||
// reference frame and the current angles as hints. Using the
|
||||
// same fixed axes every frame (not the current frame's axes)
|
||||
// keeps hint-based disambiguation stable — same technique as
|
||||
// usdtweak's RotationManipulator::OnUpdate.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Step 1: exact current rotation matrix via USD's own op encoding
|
||||
pxr::UsdGeomXformOp::Type opType =
|
||||
pxr::UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(m_dragOriginalRotOrder);
|
||||
pxr::GfMatrix4d curRotMat =
|
||||
pxr::UsdGeomXformOp::GetOpTransform(opType, pxr::VtValue(m_dragStartRotate));
|
||||
|
||||
// Step 2: world-space delta rotation around the gizmo axis
|
||||
pxr::GfVec3d axes[3];
|
||||
GetGizmoAxes(axes);
|
||||
pxr::GfMatrix4d deltaWorld =
|
||||
pxr::GfMatrix4d(1.0).SetRotate(
|
||||
pxr::GfRotation(axes[axisIndex], static_cast<double>(angleDeg)));
|
||||
|
||||
// Convert to parent space: new_local = (P^-1 * deltaWorld * P) * curLocal
|
||||
pxr::UsdGeomXformCache xformCache(pxr::UsdTimeCode::Default());
|
||||
pxr::UsdPrim parent = prim.GetParent();
|
||||
pxr::GfMatrix4d parentRotOnly(1.0);
|
||||
if (parent && !parent.IsPseudoRoot()) {
|
||||
pxr::GfMatrix4d p2w = xformCache.GetLocalToWorldTransform(parent);
|
||||
parentRotOnly = pxr::GfMatrix4d(1.0).SetRotate(p2w.ExtractRotation());
|
||||
}
|
||||
double det = 0.0;
|
||||
pxr::GfMatrix4d parentRotInv = parentRotOnly.GetInverse(&det);
|
||||
if (std::abs(det) < 1e-9) return;
|
||||
|
||||
pxr::GfMatrix4d newRotMat =
|
||||
curRotMat * parentRotOnly * deltaWorld * parentRotInv;
|
||||
newRotMat.Orthonormalize();
|
||||
|
||||
// Step 3: decompose newRotMat (which is in local-to-parent space) back into
|
||||
// Euler angles matching m_dragOriginalRotOrder.
|
||||
//
|
||||
// IMPORTANT: The reference axes for GfRotation::DecomposeRotation must be the
|
||||
// canonical parent-space unit vectors in the order dictated by the rotation
|
||||
// order — NOT rows of initRotMat (which are the rotated local axes in world
|
||||
// space). Using rotated rows causes the decomposition to solve a completely
|
||||
// different problem once any rotation has been applied, producing the random
|
||||
// value jumps observed on the second axis drag.
|
||||
//
|
||||
// For each RotationOrder the Euler product is (row-vector convention):
|
||||
// XYZ: R = Rx * Ry * Rz → axes in order (X,Y,Z)
|
||||
// XZY: R = Rx * Rz * Ry → axes in order (X,Z,Y)
|
||||
// YXZ: R = Ry * Rx * Rz → axes in order (Y,X,Z)
|
||||
// YZX: R = Ry * Rz * Rx → axes in order (Y,Z,X)
|
||||
// ZXY: R = Rz * Rx * Ry → axes in order (Z,X,Y)
|
||||
// ZYX: R = Rz * Ry * Rx → axes in order (Z,Y,X)
|
||||
//
|
||||
// The hint angles must be ordered the same way (tw, fb, lr).
|
||||
|
||||
// Map rotation order → (twIdx, fbIdx, lrIdx) where 0=X,1=Y,2=Z
|
||||
int twIdx = 0, fbIdx = 1, lrIdx = 2;
|
||||
switch (m_dragOriginalRotOrder) {
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderXYZ: twIdx=0; fbIdx=1; lrIdx=2; break;
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderXZY: twIdx=0; fbIdx=2; lrIdx=1; break;
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderYXZ: twIdx=1; fbIdx=0; lrIdx=2; break;
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderYZX: twIdx=1; fbIdx=2; lrIdx=0; break;
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderZXY: twIdx=2; fbIdx=0; lrIdx=1; break;
|
||||
case pxr::UsdGeomXformCommonAPI::RotationOrderZYX: twIdx=2; fbIdx=1; lrIdx=0; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
static const pxr::GfVec3d kUnitAxes[3] = {
|
||||
{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}
|
||||
};
|
||||
const pxr::GfVec3d& twAxis = kUnitAxes[twIdx];
|
||||
const pxr::GfVec3d& fbAxis = kUnitAxes[fbIdx];
|
||||
const pxr::GfVec3d& lrAxis = kUnitAxes[lrIdx];
|
||||
|
||||
// Hint angles in tw/fb/lr order from current accumulated state
|
||||
double thetaTw = pxr::GfDegreesToRadians(double(m_dragStartRotate[twIdx]));
|
||||
double thetaFB = pxr::GfDegreesToRadians(double(m_dragStartRotate[fbIdx]));
|
||||
double thetaLR = pxr::GfDegreesToRadians(double(m_dragStartRotate[lrIdx]));
|
||||
double thetaSw = 0.0;
|
||||
|
||||
pxr::GfRotation::DecomposeRotation(
|
||||
newRotMat, twAxis, fbAxis, lrAxis, /*handedness=*/1.0,
|
||||
&thetaTw, &thetaFB, &thetaLR, &thetaSw, /*useHint=*/true);
|
||||
|
||||
// Write results back into the correct component slots
|
||||
pxr::GfVec3f newAngles = m_dragStartRotate;
|
||||
newAngles[twIdx] = float(pxr::GfRadiansToDegrees(thetaTw));
|
||||
newAngles[fbIdx] = float(pxr::GfRadiansToDegrees(thetaFB));
|
||||
newAngles[lrIdx] = float(pxr::GfRadiansToDegrees(thetaLR));
|
||||
m_dragStartRotate = newAngles;
|
||||
api.SetRotate(m_dragStartRotate, m_dragOriginalRotOrder,
|
||||
pxr::UsdTimeCode::Default());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue