Files
UsdLayerManager/src/ui/TransformManipulator.h
T
indigo 7f0fa6a944 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>
2026-06-15 09:57:39 +08:00

146 lines
6.4 KiB
C++

#pragma once
#include <imgui.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/line.h>
#include <pxr/base/gf/plane.h>
#include <pxr/base/gf/ray.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include "../core/commands/TransformCommand.h"
namespace UsdLayerManager {
class CommandHistory;
enum class ManipulatorMode { Select, Move, Rotate, Scale };
enum class TransformSpace { Object, World };
class TransformManipulator {
public:
TransformManipulator() = default;
~TransformManipulator() = default;
void SetMode(ManipulatorMode m) { m_mode = m; }
ManipulatorMode GetMode() const { return m_mode; }
void SetTransformSpace(TransformSpace s) { m_transformSpace = s; }
TransformSpace GetTransformSpace() const { return m_transformSpace; }
void SetStage(pxr::UsdStageRefPtr stage);
void SetSelectedPrim(const pxr::SdfPath& path);
void SetCommandHistory(CommandHistory* h) { m_commandHistory = h; }
/// displayTime — frame used to read/evaluate transforms (timeline frame).
/// editTime — frame writes are authored at (Default unless Auto-Key).
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime) {
m_displayTime = displayTime;
m_editTime = editTime;
}
void Render(ImDrawList* dl, const pxr::GfMatrix4d& viewProj,
const pxr::GfVec3d& pivot, const pxr::GfVec3d& cameraEye,
const ImVec2& imagePos, int viewW, int viewH);
bool HandleInput(const pxr::GfMatrix4d& viewProj,
const pxr::GfVec3d& pivot, const pxr::GfVec3d& cameraEye,
const ImVec2& imagePos, int viewW, int viewH,
bool viewportHovered);
bool IsDragging() const { return m_isDragging; }
private:
static float ComputeScreenFactor(const pxr::GfMatrix4d& vp,
const pxr::GfVec3d& pivot,
int vW, int vH, float frac = 0.15f);
static bool WorldToScreen(const pxr::GfVec3d& world,
const pxr::GfMatrix4d& vp,
int vW, int vH,
const ImVec2& imgPos, ImVec2& out);
static float PointToSegmentDist(ImVec2 p, ImVec2 a, ImVec2 b);
static pxr::GfRay ComputeMouseRay(const pxr::GfMatrix4d& vp,
const ImVec2& mouse,
const ImVec2& imgPos, int vW, int vH);
void DrawMoveGizmo (ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
float, const ImVec2&, int, int, const pxr::GfVec3d[3]);
void DrawRotateGizmo(ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
float, const pxr::GfVec3d&, const ImVec2&, int, int,
const pxr::GfVec3d[3]);
void DrawScaleGizmo (ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
float, const ImVec2&, int, int, const pxr::GfVec3d[3]);
int HitTestAxes(const pxr::GfMatrix4d&, const pxr::GfVec3d&, float,
const ImVec2&, int, int, const ImVec2&,
const pxr::GfVec3d[3]) const;
int HitTestRotateRings(const pxr::GfMatrix4d&, const pxr::GfVec3d&, float,
const pxr::GfVec3d&, const ImVec2&, int, int,
const ImVec2&, const pxr::GfVec3d[3]) const;
void ApplyTranslate(const pxr::GfVec3d&);
void ApplyRotate(const pxr::GfVec3f&, pxr::UsdGeomXformCommonAPI::RotationOrder);
void ApplyScale(const pxr::GfVec3f&);
void GetGizmoAxes(pxr::GfVec3d[3]) const;
// -----------------------------------------------------------------------
ManipulatorMode m_mode = ManipulatorMode::Select;
TransformSpace m_transformSpace = TransformSpace::Object;
pxr::UsdStageRefPtr m_stage;
pxr::SdfPath m_primPath;
CommandHistory* m_commandHistory = nullptr;
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
bool m_isDragging = false;
int m_dragAxis = -1;
int m_hoveredAxis = -1;
// ---- Translate drag ---------------------------------------------------
pxr::GfLine m_dragMoveAxisLine;
pxr::GfVec3d m_dragMoveOriginOnAxis;
// ---- Rotate drag (usdtweak clock-hand, extended with space-switch) ----
//
// Ring plane: world-space ring normal (for mouse-ray intersection + sign).
//
// 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; // 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;
// ---- Scale drag -------------------------------------------------------
pxr::GfLine m_dragScaleAxisLine;
pxr::GfVec3d m_dragScaleOriginOnAxis;
// ---- Snapshot ---------------------------------------------------------
pxr::GfVec3d m_dragOriginalTranslate = {0,0,0};
pxr::GfVec3f m_dragOriginalRotate = {0,0,0};
pxr::GfVec3f m_dragOriginalScale = {1,1,1};
pxr::UsdGeomXformCommonAPI::RotationOrder m_dragOriginalRotOrder =
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ;
pxr::GfVec3d m_dragStartTranslate = {0,0,0};
pxr::GfVec3f m_dragStartRotate = {0,0,0};
pxr::GfVec3f m_dragStartScale = {1,1,1};
// Sample presence at m_editTime, captured at drag start (before the
// first live write) so the end-of-drag TransformCommand can undo
// keyframe creation correctly.
TransformCommand::SamplePresence m_dragPreEditSamples;
};
} // namespace UsdLayerManager