Skip to content

Commit

Permalink
Split character_test_helpers (#152)
Browse files Browse the repository at this point in the history
Summary:

This diff splits `character_test_helpers` into two modules: `character_test_helpers` (no gtest dependency) and `character_test_helpers_gtest` (with gtest dependency). The goal is to remove gtest dependencies from all pymomentum submodules. `character_test_helpers_gtest` will be only used for C++ testing.

Previously, a separate `geometry_test_helper` submodule was created (D63378477) to remove gtest dependencies from `geometry`. However, it was found that any pymomentum submodule with a gtest dependency is problematic. This diff aims to completely remove gtest dependencies from all pymomentum submodules.

Reviewed By: cdtwigg

Differential Revision: D66789357
  • Loading branch information
jeongseok-meta authored and facebook-github-bot committed Dec 6, 2024
1 parent e81047f commit 590d3ee
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 238 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,15 @@ if(MOMENTUM_BUILD_TESTING)
NAME character_test_helpers
HEADERS_VARS character_test_helpers_public_headers
SOURCES_VARS character_test_helpers_sources
PUBLIC_LINK_LIBRARIES
character
EXCLUDE_FROM_INSTALL
)

mt_library(
NAME character_test_helpers_gtest
HEADERS_VARS character_test_helpers_gtest_public_headers
SOURCES_VARS character_test_helpers_gtest_sources
PUBLIC_LINK_LIBRARIES
character
PRIVATE_LINK_LIBRARIES
Expand Down
8 changes: 8 additions & 0 deletions cmake/build_variables.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ character_test_helpers_sources = [
"test/character/character_helpers.cpp",
]

character_test_helpers_gtest_public_headers = [
"test/character/character_helpers_gtest.h",
]

character_test_helpers_gtest_sources = [
"test/character/character_helpers_gtest.cpp",
]

solver_test_helper_public_headers = [
"test/solver/solver_test_helpers.h",
]
Expand Down
239 changes: 5 additions & 234 deletions momentum/test/character/character_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@
#include "momentum/math/mppca.h"
#include "momentum/math/random.h"

#include <gmock/gmock-matchers.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <limits>

namespace momentum {

namespace {

template <typename T>
Eigen::MatrixX<T> randomMatrix(Eigen::Index nRows, Eigen::Index nCols) {
return normal<MatrixX<T>>(nRows, nCols, 0, 1);
}

[[nodiscard]] Skeleton createDefaultSkeleton(size_t numJoints) {
Skeleton result;
Joint joint;
Expand Down Expand Up @@ -176,39 +177,6 @@ ParameterLimits createDefaultParameterLimits() {
return lm;
}

template <typename DerivedIndexMatrix, typename DerivedWeightMatrix>
void sortSkinWeightsRows(
Eigen::MatrixBase<DerivedIndexMatrix>& indexMap,
Eigen::MatrixBase<DerivedWeightMatrix>& weightMap) {
using Index = typename DerivedIndexMatrix::Scalar;
using Scalar = typename DerivedWeightMatrix::Scalar;

for (int i = 0; i < indexMap.rows(); ++i) {
std::vector<std::pair<Index, Scalar>> pairs;

for (int j = 0; j < indexMap.cols(); ++j) {
if (indexMap(i, j) == 0) { // Assuming 0 is the marker for unused elements
break;
}
pairs.emplace_back(indexMap(i, j), weightMap(i, j));
}

// Sort pairs based on the first element of the pair (index value)
std::sort(
pairs.begin(),
pairs.end(),
[](const std::pair<Index, Scalar>& a, const std::pair<Index, Scalar>& b) {
return a.first < b.first;
});

// Place sorted values back into the matrices
for (Index k = 0; k < pairs.size(); ++k) {
indexMap(i, k) = pairs[k].first;
weightMap(i, k) = pairs[k].second;
}
}
}

} // namespace

template <typename T>
Expand All @@ -229,203 +197,6 @@ CharacterT<T> createTestCharacter(size_t numJoints) {
template CharacterT<float> createTestCharacter(size_t numJoints);
template CharacterT<double> createTestCharacter(size_t numJoints);

namespace {

template <typename T>
Eigen::MatrixX<T> randomMatrix(Eigen::Index nRows, Eigen::Index nCols) {
Eigen::MatrixX<T> result(nRows, nCols);
for (Eigen::Index i = 0; i < nRows; ++i) {
for (Eigen::Index j = 0; j < nCols; ++j) {
result(i, j) = normal<T>(0, 1);
}
}
return result;
}

MATCHER_P(FloatNearPointwise, tol, "Value mismatch") {
for (int i = 0; i < std::get<0>(arg).size(); i++) {
if (std::abs(std::get<0>(arg)[i] - std::get<1>(arg)[i]) > tol) {
return false;
}
}
return true;
}

MATCHER(IntExactPointwise, "Value mismatch") {
for (int i = 0; i < std::get<0>(arg).size(); i++) {
if (std::get<0>(arg)[i] != std::get<1>(arg)[i]) {
return false;
}
}
return true;
}

MATCHER(ElementsEq, "Elements mismatch") {
return ::testing::get<0>(arg) == ::testing::get<1>(arg);
}

MATCHER(ElementsIsApprox, "Elements mismatch") {
return ::testing::get<0>(arg).isApprox(::testing::get<1>(arg));
}

} // namespace

void compareMeshes(const Mesh_u& refMesh, const Mesh_u& mesh) {
ASSERT_TRUE((refMesh && mesh));
EXPECT_THAT(refMesh->vertices, testing::Pointwise(FloatNearPointwise(0.0001), mesh->vertices));
EXPECT_THAT(refMesh->normals, testing::Pointwise(FloatNearPointwise(0.01), mesh->normals));
EXPECT_THAT(refMesh->faces, testing::Pointwise(IntExactPointwise(), mesh->faces));
EXPECT_THAT(refMesh->colors, testing::Pointwise(IntExactPointwise(), mesh->colors));
EXPECT_THAT(refMesh->texcoords, testing::Pointwise(FloatNearPointwise(0.0001), mesh->texcoords));
EXPECT_THAT(
refMesh->confidence, testing::Pointwise(testing::DoubleNear(0.0001), mesh->confidence));
EXPECT_THAT(
refMesh->texcoord_faces, testing::Pointwise(IntExactPointwise(), mesh->texcoord_faces));
}

void compareLocators(const LocatorList& refLocators, const LocatorList& locators) {
EXPECT_EQ(refLocators.size(), locators.size());
auto sortedRefLocators = refLocators;
auto sortedLocators = locators;
auto compareLocators = [](const Locator& l1, const Locator& l2) {
return l1.parent != l2.parent ? l1.parent < l2.parent : l1.name < l2.name;
};
std::sort(sortedRefLocators.begin(), sortedRefLocators.end(), compareLocators);
std::sort(sortedLocators.begin(), sortedLocators.end(), compareLocators);
EXPECT_THAT(sortedRefLocators, testing::Pointwise(ElementsEq(), sortedLocators));
}

void compareCollisionGeometry(
const CollisionGeometry_u& refCollision,
const CollisionGeometry_u& collision) {
if (refCollision == nullptr)
ASSERT_EQ(collision, nullptr);
else {
ASSERT_NE(collision, nullptr);
EXPECT_EQ(refCollision->size(), collision->size());
auto sortedRefCollision = *refCollision;
auto sortedCollision = *collision;
auto compareCollisions = [](const TaperedCapsule& l1, const TaperedCapsule& l2) {
if (l1.parent != l2.parent)
return l1.parent < l2.parent;
if (l1.length != l2.length)
return l1.length < l2.length;
if (!l1.radius.isApprox(l2.radius))
return l1.radius.x() != l2.radius.x() ? l1.radius.x() < l2.radius.x()
: l1.radius.y() < l2.radius.y();
const auto t1 = l1.transformation.matrix();
const auto t2 = l2.transformation.matrix();
EXPECT_EQ(t1.size(), t2.size());
for (auto i = 0; i < t1.size(); i++) {
if (std::abs(t1.data()[i] - t2.data()[i]) > std::numeric_limits<float>::epsilon()) {
return t1.data()[i] < t2.data()[i];
}
}
return false;
};
std::sort(sortedRefCollision.begin(), sortedRefCollision.end(), compareCollisions);
std::sort(sortedCollision.begin(), sortedCollision.end(), compareCollisions);
for (size_t i = 0; i < sortedCollision.size(); ++i) {
const auto& collA = sortedRefCollision[i];
const auto& collB = sortedCollision[i];

EXPECT_TRUE(collA.isApprox(collB)) << "Collision geometry mismatch at index " << i << ":\n"
<< "- refCollision:\n"
<< " - radius_0 : " << collA.radius.x() << "\n"
<< " - radius_1 : " << collA.radius.y() << "\n"
<< " - length : " << collA.length << "\n"
<< " - parent : " << collA.parent << "\n"
<< " - transform:\n"
<< collA.transformation.matrix() << "\n"
<< "- collision:\n"
<< " - radius_0 : " << collB.radius.x() << "\n"
<< " - radius_1 : " << collB.radius.y() << "\n"
<< " - length : " << collB.length << "\n"
<< " - parent : " << collB.parent << "\n"
<< " - transform:\n"
<< collB.transformation.matrix() << std::endl;
}
}
}

void compareChars(const Character& refChar, const Character& character, const bool withMesh) {
const auto& refJoints = refChar.skeleton.joints;
const auto& joints = character.skeleton.joints;
ASSERT_EQ(refJoints.size(), joints.size());
for (size_t i = 0; i < refJoints.size(); ++i) {
EXPECT_TRUE(refJoints[i].isApprox(joints[i]))
<< "Joint " << i << " is not equal:\n"
<< "- refJoint:\n"
<< " - name: " << refJoints[i].name << "\n"
<< " - parent: " << refJoints[i].parent << "\n"
<< " - preRotation: " << refJoints[i].preRotation.coeffs().transpose() << "\n"
<< " - translationOffset: " << refJoints[i].translationOffset.transpose() << "\n"
<< "- joint:\n"
<< " - name: " << joints[i].name << "\n"
<< " - parent: " << joints[i].parent << "\n"
<< " - preRotation: " << joints[i].preRotation.coeffs().transpose() << "\n"
<< " - translationOffset: " << joints[i].translationOffset.transpose() << "\n";
}
ASSERT_TRUE(refChar.parameterTransform.isApprox(character.parameterTransform));
EXPECT_THAT(refChar.parameterLimits, testing::Pointwise(ElementsEq(), character.parameterLimits));
compareLocators(refChar.locators, character.locators);
compareCollisionGeometry(refChar.collision, character.collision);
ASSERT_EQ(refChar.inverseBindPose.size(), character.inverseBindPose.size());
for (size_t i = 0; i < refChar.inverseBindPose.size(); ++i) {
EXPECT_TRUE(refChar.inverseBindPose[i].isApprox(character.inverseBindPose[i], 1e-4f))
<< "InverseBindPose " << i << " is not equal:\n"
<< "- Expected:\n"
<< refChar.inverseBindPose[i].matrix() << "\n"
<< "- Actual :\n"
<< character.inverseBindPose[i].matrix() << std::endl;
}
EXPECT_EQ(refChar.jointMap, character.jointMap);

if (withMesh) {
auto ptrValEq = [](auto& l, auto& r) {
if (l == nullptr && r == nullptr) {
return true;
}
if (l == nullptr) {
return (r == nullptr);
}
if (r == nullptr) {
return false;
}
return true;
};

compareMeshes(refChar.mesh, character.mesh);

auto refCharIndices = refChar.skinWeights->index;
auto refCharWeights = refChar.skinWeights->weight;
sortSkinWeightsRows(refCharIndices, refCharWeights);

auto charIndices = refChar.skinWeights->index;
auto charWeights = refChar.skinWeights->weight;
sortSkinWeightsRows(charIndices, charWeights);

ASSERT_EQ(refCharIndices.rows(), charIndices.rows());
ASSERT_EQ(refCharIndices.cols(), charIndices.cols());
for (auto i = 0u; i < refCharIndices.rows(); ++i) {
EXPECT_EQ(refCharIndices.row(i), charIndices.row(i))
<< "SkinWeights index row " << i << " mismatch\n";
}
ASSERT_LT((refCharWeights - charWeights).lpNorm<Eigen::Infinity>(), 1e-4f);
ASSERT_TRUE(ptrValEq(refChar.skinWeights, character.skinWeights));

auto ptrValIsApprox = [](auto& l, auto& r) {
if (l == nullptr)
return (r == nullptr);
if (r == nullptr)
return false;
return l->isApprox(*r);
};
ASSERT_TRUE(ptrValIsApprox(refChar.poseShapes, character.poseShapes));
ASSERT_TRUE(ptrValIsApprox(refChar.blendShape, character.blendShape));
}
}

template <typename T>
CharacterT<T> withTestBlendShapes(const CharacterT<T>& character) {
MT_CHECK(character.mesh);
Expand Down
4 changes: 0 additions & 4 deletions momentum/test/character/character_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@

namespace momentum {

// Matching methods
void compareMeshes(const Mesh_u& refMesh, const Mesh_u& mesh);
void compareChars(const Character& refChar, const Character& character, bool withMesh = true);

/// Creates a character with a customizable number of joints.
///
/// @param numJoints The number of joints in the resulting character.
Expand Down
Loading

0 comments on commit 590d3ee

Please sign in to comment.