Fix rotate manipulator axis and gizmo pivot placement

Rotate manipulator — correct axis bug:
- Object-space delta axis was set to initRot.GetRow3(axis) (a parent-space
  vector) then applied in the prim's local frame via deltaM * initRot.
  These frames are mismatched: the axis must be the canonical e_a so that
  Rot(e_a) fixes e_a and the prim rotates purely about its own axis.
  Fixed to use the canonical axis for both spaces; only composition order
  differs (deltaM * initRot for object, initRot * deltaM for world).

Gizmo pivot placement:
- ComputeGizmoPivot() used the bounding-box centre (geometry centroid),
  placing the gizmo at the wrong location for any non-centred prim.
  Replaced with parentToWorld.Transform(translate + pivot) from
  XformCommonAPI::GetXformVectors — the authored pivot point in world space.
  Fallback for incompatible op stacks uses worldMatrix.ExtractTranslation().

Property panel:
- Show pivot (X/Y/Z) as read-only row below Scale, populated from
  XformCommonAPI::GetXformVectors each frame.
- Removed active-rotate-axis display (was added and then removed per request).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 09:57:39 +08:00
parent e0b0fd8204
commit 7f0fa6a944
7 changed files with 82 additions and 42 deletions
+8 -12
View File
@@ -108,19 +108,15 @@ private:
//
// Ring plane: world-space ring normal (for mouse-ray intersection + sign).
//
// Delta-rotation axis differs by TransformSpace:
// Object => initRot.GetRow3(axis) -- local axis in PARENT space
// (exact usdtweak localPlaneNormal convention)
// World => world unit vector
// (correct for root-level / simple hierarchies)
//
// resultingRotation formula:
// Object => GfMatrix4d(1).SetRotate(delta) * initRot
// (usdtweak: delta applied in local frame before initRot)
// World => initRot * GfMatrix4d(1).SetRotate(delta)
// (USD row-vector: initRot maps local->parent, then world delta)
// Delta-rotation axis is the canonical axis e_a for the dragged ring in
// BOTH spaces; only the composition order differs:
// Object => SetRotate(delta) * initRot
// delta applied in the prim's own local frame, so the dragged
// local axis stays fixed -> rotates purely about its own axis.
// World => initRot * SetRotate(delta)
// initRot maps local->parent, then delta about the world axis.
pxr::GfVec3d m_dragRotatePlaneNormal; // world-space
pxr::GfVec3d m_dragRotateDeltaAxis; // space-dependent (see above)
pxr::GfVec3d m_dragRotateDeltaAxis; // canonical axis e_a for ring
pxr::GfVec3d m_dragRotateFrom; // clock-hand at drag start
pxr::GfMatrix4d m_dragRotateInitialRotMat; // local->parent rot at drag start
bool m_dragRotateObjectSpace = true;