Fix rotate manipulator desync on already-rotated prims
The matrix->Euler decode at the end of a rotate drag used the rows of the prim's initial rotation matrix (its already-rotated frame) as the DecomposeRotation axes. But UsdGeomXformCommonAPI::SetRotate interprets the written angles about the CANONICAL x/y/z axes, so the two only agreed when the prim was at zero rotation -- the authored orientation desynced from the manipulator the moment the prim already carried a rotation. Decompose the result matrix about the canonical axes instead, matching how SetRotate reconstructs it. The result-matrix computation (which correctly tracks the visual ring axis) is unchanged. Verified numerically via the real XformCommonAPI authoring path: from rotateXYZ=(30,45,60), a +20deg object-X drag now authors exactly (50,45,60) (err 0.0) vs the old decode's (41.07,27.07,66.61) (err 0.287). Assumes RotationOrderXYZ (the XformCommonAPI default); non-XYZ orders are mapped as before -- noted in a code comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -645,11 +645,21 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
|
|||||||
? deltaM * m_dragRotateInitialRotMat
|
? deltaM * m_dragRotateInitialRotMat
|
||||||
: m_dragRotateInitialRotMat * deltaM;
|
: m_dragRotateInitialRotMat * deltaM;
|
||||||
|
|
||||||
// usdtweak: GfRotation::DecomposeRotation(resultingRotation,
|
// Decompose `result` (the new total local rotation matrix) back
|
||||||
// pxAxis, pyAxis, pzAxis, 1.0, &tw, &fb, &lr, &sw, true)
|
// into XformCommonAPI Euler angles.
|
||||||
const pxr::GfVec3d pxA=m_dragRotateInitialRotMat.GetRow3(0);
|
//
|
||||||
const pxr::GfVec3d pyA=m_dragRotateInitialRotMat.GetRow3(1);
|
// SetRotate() interprets the written angles as rotations about the
|
||||||
const pxr::GfVec3d pzA=m_dragRotateInitialRotMat.GetRow3(2);
|
// CANONICAL x/y/z axes, so the decompose axes MUST be canonical.
|
||||||
|
// The previous port used the rows of initRot here -- i.e. the prim's
|
||||||
|
// already-rotated frame -- which equals the canonical axes only when
|
||||||
|
// the prim is at zero rotation. That mismatch is exactly why the
|
||||||
|
// authored result desynced from the manipulator once the prim already
|
||||||
|
// carried a rotation: the angles were measured in the rotated frame
|
||||||
|
// but written as if they were canonical-frame angles.
|
||||||
|
//
|
||||||
|
// (Decomposition assumes RotationOrderXYZ -- the XformCommonAPI
|
||||||
|
// default; non-XYZ orders are mapped tw->X, fb->Y, lr->Z as before.)
|
||||||
|
static const pxr::GfVec3d kCanonX(1,0,0), kCanonY(0,1,0), kCanonZ(0,0,1);
|
||||||
|
|
||||||
double tw=pxr::GfDegreesToRadians(double(m_dragStartRotate[0]));
|
double tw=pxr::GfDegreesToRadians(double(m_dragStartRotate[0]));
|
||||||
double fb=pxr::GfDegreesToRadians(double(m_dragStartRotate[1]));
|
double fb=pxr::GfDegreesToRadians(double(m_dragStartRotate[1]));
|
||||||
@@ -657,7 +667,7 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
|
|||||||
double sw=0;
|
double sw=0;
|
||||||
|
|
||||||
pxr::GfRotation::DecomposeRotation(
|
pxr::GfRotation::DecomposeRotation(
|
||||||
result,pxA,pyA,pzA,1.0,&tw,&fb,&lr,&sw,/*useHint=*/true);
|
result,kCanonX,kCanonY,kCanonZ,1.0,&tw,&fb,&lr,&sw,/*useHint=*/true);
|
||||||
|
|
||||||
pxr::GfVec3f newRot(float(pxr::GfRadiansToDegrees(tw)),
|
pxr::GfVec3f newRot(float(pxr::GfRadiansToDegrees(tw)),
|
||||||
float(pxr::GfRadiansToDegrees(fb)),
|
float(pxr::GfRadiansToDegrees(fb)),
|
||||||
|
|||||||
Reference in New Issue
Block a user