Fix rotate issue
This commit is contained in:
parent
d4b46b438c
commit
3b0250b882
|
|
@ -1,9 +1,42 @@
|
||||||
#include "CreatePrimCommand.h"
|
#include "CreatePrimCommand.h"
|
||||||
#include "../../utils/Logger.h"
|
#include "../../utils/Logger.h"
|
||||||
#include <pxr/usd/usd/prim.h>
|
#include <pxr/usd/usd/prim.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ApplyCameraDefaultOrientation
|
||||||
|
//
|
||||||
|
// USD cameras always use +Y-up / look-down-−Z in their own local frame.
|
||||||
|
// For a Y-up stage that is already the standard world forward direction, so
|
||||||
|
// the identity transform is fine.
|
||||||
|
//
|
||||||
|
// For a Z-up stage the identity transform would point the camera straight
|
||||||
|
// DOWN (world −Z = −up), which produces wrong orientation when the viewport
|
||||||
|
// switches to the prim, and wrong tumble/roll directions when navigating.
|
||||||
|
//
|
||||||
|
// Fix: rotate the camera +90° around its local X axis so that:
|
||||||
|
// camera local +Y (up) → world +Z (stage up axis) ✓
|
||||||
|
// camera local −Z (view) → world +Y (horizontal "forward") ✓
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void ApplyCameraDefaultOrientation(pxr::UsdPrim& prim,
|
||||||
|
pxr::UsdStageRefPtr stage)
|
||||||
|
{
|
||||||
|
if (!prim.IsValid() || !stage) return;
|
||||||
|
|
||||||
|
bool isZUp = (pxr::UsdGeomGetStageUpAxis(stage) == pxr::UsdGeomTokens->z);
|
||||||
|
if (!isZUp) return; // Y-up default (identity) is already correct
|
||||||
|
|
||||||
|
pxr::UsdGeomXformCommonAPI xformAPI(prim);
|
||||||
|
// +90° around X: camera up aligns with world Z, view direction becomes +Y
|
||||||
|
xformAPI.SetRotate(pxr::GfVec3f(90.f, 0.f, 0.f),
|
||||||
|
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
|
||||||
|
pxr::UsdTimeCode::Default());
|
||||||
|
}
|
||||||
|
|
||||||
CreatePrimCommand::CreatePrimCommand(pxr::UsdStageRefPtr stage,
|
CreatePrimCommand::CreatePrimCommand(pxr::UsdStageRefPtr stage,
|
||||||
const pxr::SdfPath& primPath,
|
const pxr::SdfPath& primPath,
|
||||||
const pxr::TfToken& typeName)
|
const pxr::TfToken& typeName)
|
||||||
|
|
@ -18,8 +51,16 @@ void CreatePrimCommand::Execute() {
|
||||||
if (!m_stage) return;
|
if (!m_stage) return;
|
||||||
try {
|
try {
|
||||||
pxr::UsdPrim prim = m_stage->DefinePrim(m_primPath, m_typeName);
|
pxr::UsdPrim prim = m_stage->DefinePrim(m_primPath, m_typeName);
|
||||||
if (!prim.IsValid())
|
if (!prim.IsValid()) {
|
||||||
LOG_ERROR("CreatePrimCommand: failed to define prim " + m_primPath.GetString());
|
LOG_ERROR("CreatePrimCommand: failed to define prim " + m_primPath.GetString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Camera-specific initialisation: orient the prim so the camera looks
|
||||||
|
// in the correct "forward" direction for the stage's up-axis.
|
||||||
|
if (m_typeName == pxr::TfToken("Camera"))
|
||||||
|
ApplyCameraDefaultOrientation(prim, m_stage);
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR(std::string("CreatePrimCommand::Execute error: ") + e.what());
|
LOG_ERROR(std::string("CreatePrimCommand::Execute error: ") + e.what());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <pxr/usd/usdGeom/metrics.h>
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
#include <pxr/usd/usdGeom/tokens.h>
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
#include <pxr/usd/usdGeom/xform.h>
|
#include <pxr/usd/usdGeom/xform.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
#include <pxr/usd/sdf/layer.h>
|
#include <pxr/usd/sdf/layer.h>
|
||||||
#include <pxr/usd/sdf/reference.h>
|
#include <pxr/usd/sdf/reference.h>
|
||||||
#include <pxr/usd/sdf/primSpec.h>
|
#include <pxr/usd/sdf/primSpec.h>
|
||||||
|
|
@ -202,6 +203,17 @@ void SceneHierarchyPanel::Render() {
|
||||||
try {
|
try {
|
||||||
UsdPrim newPrim = m_stage->DefinePrim(primPath, TfToken(typeName));
|
UsdPrim newPrim = m_stage->DefinePrim(primPath, TfToken(typeName));
|
||||||
if (newPrim.IsValid()) {
|
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);
|
LOG_INFO("Created prim '" + primPath.GetString() + "' of type " + baseName);
|
||||||
SetSelectedPathFromClick(primPath.GetString());
|
SetSelectedPathFromClick(primPath.GetString());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,13 @@
|
||||||
#include <pxr/usd/usd/editContext.h>
|
#include <pxr/usd/usd/editContext.h>
|
||||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
#include <pxr/usd/usdGeom/xformCache.h>
|
#include <pxr/usd/usdGeom/xformCache.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformOp.h>
|
||||||
#include <pxr/base/gf/matrix4d.h>
|
#include <pxr/base/gf/matrix4d.h>
|
||||||
#include <pxr/base/gf/matrix4f.h>
|
#include <pxr/base/gf/matrix4f.h>
|
||||||
#include <pxr/base/gf/vec3d.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 <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -566,6 +570,14 @@ bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& viewProj,
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
|
||||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
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,
|
ImVec2 delta = { mouse.x - m_dragLastPos.x,
|
||||||
mouse.y - m_dragLastPos.y };
|
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::UsdEditContext ec(m_stage, m_stage->GetEditTarget());
|
||||||
pxr::UsdGeomXformCommonAPI api(prim);
|
pxr::UsdGeomXformCommonAPI api(prim);
|
||||||
|
|
||||||
m_dragStartRotate[axisIndex] += angleDeg;
|
// ── Correct, gimbal-lock-free rotation (mirrors usdtweak RotationManipulator) ──
|
||||||
api.SetRotate(m_dragStartRotate,
|
//
|
||||||
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ,
|
// 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());
|
pxr::UsdTimeCode::Default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue