Skip to content

Commit

Permalink
Merge pull request #2065 from AnimalLogic/csyshing/compare-rotations-…
Browse files Browse the repository at this point in the history
…with-quaternions

[al] Use quaternion form to compare rotation angles
  • Loading branch information
Krystian Ligenza authored Feb 4, 2022
2 parents a17520f + 96a82bd commit 1f134bc
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ class BasicTransformationMatrix : public MPxTransformationMatrix
{
public:
/// \brief ctor
AL_USDMAYA_PUBLIC
BasicTransformationMatrix();

/// \brief ctor
/// \param prim the USD prim that this matrix should represent
AL_USDMAYA_PUBLIC
BasicTransformationMatrix(const UsdPrim& prim);

/// \brief dtor
Expand All @@ -53,6 +55,7 @@ class BasicTransformationMatrix : public MPxTransformationMatrix
/// \brief set the prim that this transformation matrix will read/write to.
/// \param prim the prim
/// \param scopeNode the owning maya node
AL_USDMAYA_PUBLIC
virtual void setPrim(const UsdPrim& prim, Scope* scopeNode);

/// \brief sets the MObject for the transform
Expand Down
12 changes: 10 additions & 2 deletions plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/TransformationMatrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1894,7 +1894,14 @@ void TransformationMatrix::pushRotateToPrim()
internal_readRotation(tempRotate, op);

// only write back if data has changed significantly
if (!tempRotate.isEquivalent(MPxTransformationMatrix::rotationValue)) {
// Note: the rotation values are converted to quaternion form to avoid
// checking in Euler space, twisted values e.g.:
// (180, -2.31718, 180) == (0, 182.317, 0)
// are in fact the same although their raw values look different.
// Also notice that we lower the tolerance for comparison since
// the values are converted from Euler.
if (!tempRotate.asQuaternion().isEquivalent(
MPxTransformationMatrix::rotationValue.asQuaternion(), 1e-5)) {
internal_pushRotation(MPxTransformationMatrix::rotationValue, op);
m_rotationFromUsd = MPxTransformationMatrix::rotationValue;
m_rotationTweak = MEulerRotation(0, 0, 0);
Expand Down Expand Up @@ -2299,7 +2306,8 @@ void TransformationMatrix::enableReadAnimatedValues(bool enabled)
//----------------------------------------------------------------------------------------------------------------------
void TransformationMatrix::enablePushToPrim(bool enabled)
{
TF_DEBUG(ALUSDMAYA_TRANSFORM_MATRIX).Msg("TransformationMatrix::enablePushToPrim\n");
TF_DEBUG(ALUSDMAYA_TRANSFORM_MATRIX)
.Msg("TransformationMatrix::enablePushToPrim %d\n", enabled);
if (enabled)
m_flags |= kPushToPrimEnabled;
else
Expand Down
17 changes: 16 additions & 1 deletion plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/TransformationMatrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,15 +431,18 @@ class TransformationMatrix : public BasicTransformationMatrix
static MPxTransformationMatrix* creator();

/// \brief ctor
AL_USDMAYA_PUBLIC
TransformationMatrix();

/// \brief ctor
/// \param prim the USD prim that this matrix should represent
AL_USDMAYA_PUBLIC
TransformationMatrix(const UsdPrim& prim);

/// \brief set the prim that this transformation matrix will read/write to.
/// \param prim the prim
/// \param transformNode the owning transform node
AL_USDMAYA_PUBLIC
void setPrim(const UsdPrim& prim, Scope* transformNode) override;

/// \brief Returns the timecode to use when pushing the transform values to the USD prim. If
Expand Down Expand Up @@ -538,19 +541,31 @@ class TransformationMatrix : public BasicTransformationMatrix
/// \param readFromPrim if true, the maya attribute values will be updated from those found on
/// the USD prim \param node the transform node to which this matrix belongs (and where the USD
/// prim will be extracted from)
AL_USDMAYA_PUBLIC
void initialiseToPrim(bool readFromPrim = true, Scope* node = 0) override;

AL_USDMAYA_PUBLIC
void pushTranslateToPrim();
AL_USDMAYA_PUBLIC
void pushPivotToPrim();
AL_USDMAYA_PUBLIC
void pushRotatePivotTranslateToPrim();
AL_USDMAYA_PUBLIC
void pushRotatePivotToPrim();
AL_USDMAYA_PUBLIC
void pushRotateToPrim();
AL_USDMAYA_PUBLIC
void pushRotateQuatToPrim();
AL_USDMAYA_PUBLIC
void pushRotateAxisToPrim();
AL_USDMAYA_PUBLIC
void pushScalePivotTranslateToPrim();
AL_USDMAYA_PUBLIC
void pushScalePivotToPrim();
AL_USDMAYA_PUBLIC
void pushScaleToPrim();
AL_USDMAYA_PUBLIC
void pushShearToPrim();
AL_USDMAYA_PUBLIC
void pushTransformToPrim();

// Helper class. Creating a variable of this class temporarily disables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <pxr/usd/sdf/types.h>
#include <pxr/usd/usd/attribute.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/sphere.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>

Expand Down Expand Up @@ -1344,3 +1345,148 @@ TEST(Transform, getTimeCode)
// Need to test the behaviour of the transform node when the animation data present is from Matrices
// rather than TRS components.
TEST(Transform, matrixAnimationChannels) { AL_USDMAYA_UNTESTED; }

// Test twisted rotation values (angles should be considered the same)
TEST(Transform, checkTwistedRotation)
{
MStatus status;
const char* xformName = "myXform";
SdfPath xformPath(std::string("/") + xformName);
SdfPath spherePath(xformPath.AppendChild(TfToken("mesh")));

MFileIO::newFile(true);

const std::string temp_path
= buildTempPath("AL_USDMayaTests_transform_checkTwistedRotation.usda");

// generate some data for the proxy shape
{
UsdStageRefPtr stage = UsdStage::CreateInMemory();
UsdGeomXform a = UsdGeomXform::Define(stage, xformPath);
auto op = a.AddRotateXYZOp();
op.Set(GfVec3f(180.f, -2.317184f, 180.f));
UsdGeomSphere::Define(stage, spherePath);
stage->Export(temp_path, false);
}

MFnDagNode fn;
MObject xform = fn.create("transform");
MString proxyParentMayaPath = fn.fullPathName();
MObject shape = fn.create("AL_usdmaya_ProxyShape", xform);
MString proxyShapeMayaPath = fn.fullPathName();

auto* proxy = (AL::usdmaya::nodes::ProxyShape*)fn.userNode();

// force the stage to load
proxy->filePathPlug().setString(temp_path.c_str());

auto stage = proxy->getUsdStage();

// Verify current edit target has nothing
ASSERT_TRUE(stage->GetEditTarget().GetLayer()->IsEmpty());

MDagModifier modifier1;
MDGModifier modifier2;
proxy->makeUsdTransformChain(
stage->GetPrimAtPath(spherePath),
modifier1,
AL::usdmaya::nodes::ProxyShape::kSelection,
&modifier2);
EXPECT_EQ(MStatus(MS::kSuccess), modifier1.doIt());
EXPECT_EQ(MStatus(MS::kSuccess), modifier2.doIt());

MSelectionList sel;
sel.add("myXform");
MObject xformMobj;
ASSERT_TRUE(sel.getDependNode(0, xformMobj));
MFnTransform xformMfn(xformMobj);

{
// Verify default values from Maya after loading USD
MEulerRotation rot;
ASSERT_TRUE(xformMfn.getRotation(rot) == MStatus::kSuccess);
EXPECT_NEAR(rot.x, 0.f, 1e-5f);
EXPECT_NEAR(rot.y, 3.1820349693298f, 1e-5f);
EXPECT_NEAR(rot.z, 0.f, 1e-5f);
// Expect nothing changed in USD
ASSERT_TRUE(stage->GetEditTarget().GetLayer()->IsEmpty());
}

{
// Explicitly rotate X axis by 360 degree, there should be no "over" on USD
// Notice that we only set the X component here
MPlug(xformMobj, MPxTransform::rotateX).setValue(M_PI * 2);
ASSERT_TRUE(stage->GetEditTarget().GetLayer()->IsEmpty());
}

{
auto xformPrim = stage->GetPrimAtPath(xformPath);
AL::usdmaya::nodes::TransformationMatrix tm;
tm.setPrim(xformPrim, nullptr);

MEulerRotation rot(M_PI, -2.317184f * M_PI / 180.f, M_PI);
((MPxTransformationMatrix*)(&tm))->rotateTo(rot);
// Verify the matrix has been set to expected values
MStatus status;
auto tmRot = tm.eulerRotation(MSpace::kTransform, &status);
ASSERT_TRUE(status == MStatus::kSuccess);
EXPECT_NEAR(tmRot.x, M_PI, 1e-5f);
EXPECT_NEAR(tmRot.y, -2.317184f * M_PI / 180.f, 1e-5f);
EXPECT_NEAR(tmRot.z, M_PI, 1e-5f);
// Verify the USD rotation
{
GfVec3f usdRot { 0.f, 0.f, 0.f };
ASSERT_TRUE(xformPrim.GetAttribute(TfToken("xformOp:rotateXYZ")).Get(&usdRot));
EXPECT_NEAR(usdRot[0], 180.f, 1e-5f);
EXPECT_NEAR(usdRot[1], -2.317184f, 1e-5f);
EXPECT_NEAR(usdRot[2], 180.f, 1e-5f);
}

// Attempt to apply the rotation to USD
tm.pushRotateToPrim();
// Expect no "over" being created
ASSERT_TRUE(stage->GetEditTarget().GetLayer()->IsEmpty());
// Verify again the rotation values in USD
{
GfVec3f usdRot { 0.f, 0.f, 0.f };
ASSERT_TRUE(xformPrim.GetAttribute(TfToken("xformOp:rotateXYZ")).Get(&usdRot));
EXPECT_NEAR(usdRot[0], 180.f, 1e-5f);
EXPECT_NEAR(usdRot[1], -2.317184f, 1e-5f);
EXPECT_NEAR(usdRot[2], 180.f, 1e-5f);
}
}

{
auto xformPrim = stage->GetPrimAtPath(xformPath);
AL::usdmaya::nodes::TransformationMatrix tm;
tm.setPrim(stage->GetPrimAtPath(xformPath), nullptr);

MEulerRotation rot(0.f, 0.f, 0.f);
((MPxTransformationMatrix*)(&tm))->rotateTo(rot);

// Verify the original rotation in USD
{
GfVec3f usdRot { 0.f, 0.f, 0.f };
ASSERT_TRUE(xformPrim.GetAttribute(TfToken("xformOp:rotateXYZ")).Get(&usdRot));
EXPECT_NEAR(usdRot[0], 180.f, 1e-5f);
EXPECT_NEAR(usdRot[1], -2.317184f, 1e-5f);
EXPECT_NEAR(usdRot[2], 180.f, 1e-5f);
}
// This should change the USD since the rotation values are different now
tm.pushRotateToPrim();

// Expect an "over" in USD
ASSERT_FALSE(stage->GetEditTarget().GetLayer()->IsEmpty());
auto primSpec = stage->GetEditTarget().GetLayer()->GetPrimAtPath(xformPath);
ASSERT_TRUE(primSpec);
EXPECT_EQ(primSpec->GetSpecifier(), SdfSpecifierOver);
// Rotation should have been changed as well
{
GfVec3f usdRot { 0.f, 0.f, 0.f };
ASSERT_TRUE(xformPrim.GetAttribute(TfToken("xformOp:rotateXYZ")).Get(&usdRot));
EXPECT_NEAR(usdRot[0], 0.f, 1e-5f);
EXPECT_NEAR(usdRot[1], 0.f, 1e-5f);
EXPECT_NEAR(usdRot[2], 0.f, 1e-5f);
}
}
}

0 comments on commit 1f134bc

Please sign in to comment.