Fix rotate issue

This commit is contained in:
2026-06-06 09:34:39 +08:00
parent d4b46b438c
commit 3b0250b882
3 changed files with 174 additions and 4 deletions
+12
View File
@@ -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 {
+120 -3
View File
@@ -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 02 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());
}