Fix rotate issue
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user