Skip to content

Commit

Permalink
Adding a test for parameter limits round trip. (#135)
Browse files Browse the repository at this point in the history
Summary:

I want to rewrite the limit parsing code, and a first step is adding a test to make sure I don't break anything.  We have existing tests that validate loading models but we don't have precise checks that verify that every part of the limit loading is correct.

Reviewed By: jeongseok-meta

Differential Revision: D66413502
  • Loading branch information
Chris Twigg authored and facebook-github-bot committed Nov 25, 2024
1 parent d20235e commit dc733df
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 1 deletion.
4 changes: 4 additions & 0 deletions cmake/build_variables.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ io_skeleton_sources = [
"io/skeleton/parameters_io.cpp",
]

io_skeleton_test_sources = [
"test/io/io_parameter_limits_test.cpp",
]

io_shape_public_headers = [
"io/shape/blend_shape_io.h",
"io/shape/pose_shape_io.h",
Expand Down
4 changes: 3 additions & 1 deletion momentum/io/skeleton/parameter_limits_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,12 @@ std::string writeParameterLimits(
Eigen::Matrix3f scalingMat;
ellipsoid.computeRotationScaling(&rotationMat, &scalingMat);

const Eigen::Vector3f eulerXYZVecRad = rotationMatrixToEulerXYZ(rotationMat);
const Eigen::Vector3f eulerXYZVecRad =
rotationMatrixToEulerXYZ(rotationMat, EulerConvention::Extrinsic);
// Not sure why this is reversed but it's implemented this way in the parser above.
const Eigen::Vector3f eulerZYXVecDeg(
toDeg(eulerXYZVecRad.z()), toDeg(eulerXYZVecRad.y()), toDeg(eulerXYZVecRad.x()));

MT_CHECK_LT(limit.data.ellipsoid.parent, skeleton.joints.size());
MT_CHECK_LT(limit.data.ellipsoid.ellipsoidParent, skeleton.joints.size());
oss << skeleton.joints.at(limit.data.ellipsoid.parent).name << " ellipsoid "
Expand Down
157 changes: 157 additions & 0 deletions momentum/test/io/io_parameter_limits_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <gtest/gtest.h>
#include <momentum/character/parameter_limits.h>
#include <momentum/io/skeleton/parameter_limits_io.h>
#include "momentum/test/character/character_helpers.h"

using namespace momentum;

namespace {

Character createCharacterWithLimits() {
auto testCharacter = createTestCharacter();

ParameterLimits limits;

{
ParameterLimit limit;
limit.type = MinMax;
limit.weight = 1.5f;
limit.data.minMax.limits = Vector2f(-0.2, 0.1);
limit.data.minMax.parameterIndex = 1;
limits.push_back(limit);
}

{
ParameterLimit limit;
limit.type = LimitType::MinMaxJoint;
limit.weight = 2.0f;
limit.data.minMaxJoint.limits = Vector2f(-0.3, 0.0);
limit.data.minMaxJoint.jointIndex = 1;
limit.data.minMaxJoint.jointParameter = 2;
limits.push_back(limit);
}

{
ParameterLimit limit;
limit.type = LimitType::MinMaxJointPassive;
limit.weight = 2.0f;
limit.data.minMaxJoint.limits = Vector2f(-0.0, 0.5);
limit.data.minMaxJoint.jointIndex = 2;
limit.data.minMaxJoint.jointParameter = 4;
limits.push_back(limit);
}

{
ParameterLimit limit;
limit.type = LimitType::Ellipsoid;
limit.weight = 4.0;
limit.data.ellipsoid.ellipsoid = limit.data.ellipsoid.ellipsoid = Affine3f::Identity();
limit.data.ellipsoid.ellipsoid.translation() = Eigen::Vector3f(2, 3, 4);
const Vector3f eulerXYZ = Vector3f(M_PI / 2.0, M_PI / 4.0, -M_PI / 3.0);
limit.data.ellipsoid.ellipsoid.linear() =
eulerXYZToRotationMatrix(eulerXYZ, EulerConvention::Extrinsic) *
Eigen::Scaling(0.8f, 0.9f, 1.3f);
limit.data.ellipsoid.ellipsoidInv = limit.data.ellipsoid.ellipsoid.inverse();
limit.data.ellipsoid.offset = Eigen::Vector3f(1, 2, 3);
limit.data.ellipsoid.ellipsoidParent = 2;
limit.data.ellipsoid.parent = 1;
limits.push_back(limit);
}

{
ParameterLimit limit;
limit.type = LimitType::Ellipsoid;
limit.weight = 5.0;
limit.data.ellipsoid.ellipsoid = limit.data.ellipsoid.ellipsoid = Affine3f::Identity();
limit.data.ellipsoid.ellipsoid.translation() = Eigen::Vector3f(2, 3, 4);
const Vector3f eulerXYZ = Vector3f(-M_PI / 4.0, 2.0f * M_PI / 4.0, M_PI / 3.0);
limit.data.ellipsoid.ellipsoid.linear() =
eulerXYZToRotationMatrix(eulerXYZ, EulerConvention::Extrinsic) *
Eigen::Scaling(2.0f, 0.4f, 0.6f);
limit.data.ellipsoid.ellipsoidInv = limit.data.ellipsoid.ellipsoid.inverse();
limit.data.ellipsoid.offset = Eigen::Vector3f(2, 1, 4);
limit.data.ellipsoid.ellipsoidParent = 0;
limit.data.ellipsoid.parent = 2;
limits.push_back(limit);
}

{
// ellipsoid constraints are the hardest to get right so let's include two of them:
ParameterLimit limit;
limit.type = LimitType::Linear;
limit.weight = 2.0;
limit.data.linear.scale = 3.0;
limit.data.linear.offset = 2.0;
limit.data.linear.referenceIndex = 2;
limit.data.linear.targetIndex = 1;
limits.push_back(limit);
}

return Character(testCharacter.skeleton, testCharacter.parameterTransform, limits);
}

void validateParameterLimitsSame(const ParameterLimits& limits1, const ParameterLimits& limits2) {
ASSERT_EQ(limits1.size(), limits2.size());

for (size_t i = 0; i < limits1.size(); ++i) {
const auto& l1 = limits1[i];
const auto& l2 = limits2[i];

EXPECT_NEAR(l1.weight, l2.weight, 1e-4f);
EXPECT_EQ(l1.type, l2.type);

switch (l1.type) {
case LimitType::MinMax:
EXPECT_NEAR(l1.data.minMax.limits.x(), l2.data.minMax.limits.x(), 1e-4f);
EXPECT_NEAR(l1.data.minMax.limits.y(), l2.data.minMax.limits.y(), 1e-4f);
EXPECT_EQ(l1.data.minMax.parameterIndex, l2.data.minMax.parameterIndex);
break;
case LimitType::MinMaxJoint:
case LimitType::MinMaxJointPassive:
EXPECT_NEAR(l1.data.minMaxJoint.limits.x(), l2.data.minMaxJoint.limits.x(), 1e-4f);
EXPECT_NEAR(l1.data.minMaxJoint.limits.y(), l2.data.minMaxJoint.limits.y(), 1e-4f);
EXPECT_EQ(l1.data.minMaxJoint.jointIndex, l2.data.minMaxJoint.jointIndex);
EXPECT_EQ(l1.data.minMaxJoint.jointParameter, l2.data.minMaxJoint.jointParameter);
break;
case LimitType::Linear:
EXPECT_NEAR(l1.data.linear.offset, l2.data.linear.offset, 1e-4f);
EXPECT_NEAR(l1.data.linear.scale, l2.data.linear.scale, 1e-4f);
EXPECT_EQ(l1.data.linear.targetIndex, l2.data.linear.targetIndex);
EXPECT_EQ(l1.data.linear.referenceIndex, l2.data.linear.referenceIndex);
break;
case LimitType::Ellipsoid:
EXPECT_LE(
(l1.data.ellipsoid.ellipsoid.matrix() - l2.data.ellipsoid.ellipsoid.matrix())
.lpNorm<Eigen::Infinity>(),
1e-4f);
EXPECT_LE(
(l1.data.ellipsoid.ellipsoidInv.matrix() - l2.data.ellipsoid.ellipsoidInv.matrix())
.lpNorm<Eigen::Infinity>(),
1e-4f);
EXPECT_EQ(l1.data.ellipsoid.ellipsoidParent, l2.data.ellipsoid.ellipsoidParent);
EXPECT_EQ(l1.data.ellipsoid.parent, l2.data.ellipsoid.parent);
EXPECT_LE((l1.data.ellipsoid.offset - l2.data.ellipsoid.offset).norm(), 1e-4f);
break;
}
}
}

} // namespace

TEST(IoCharacterTest, ParameterLimits_RoundTrip) {
const Character character = createCharacterWithLimits();

const std::string limitsStr = writeParameterLimits(
character.parameterLimits, character.skeleton, character.parameterTransform);
std::cout << limitsStr << "\n";
const auto limits2 =
parseParameterLimits(limitsStr, character.skeleton, character.parameterTransform);
validateParameterLimitsSame(character.parameterLimits, limits2);
}

0 comments on commit dc733df

Please sign in to comment.