43 #include <gtest/gtest.h> 
   76 template <
typename S> 
void NearestPointInBox() {
 
   78   Box<S> box{S(0.6), S(1.2), S(3.6)};
 
   93         Vector3<S> quadrant{x, y, z};
 
   95         p_BQ = quadrant.cwiseProduct(
half_size * 0.5);
 
  102         for (
int axis : {0, 1, 2}) {
 
  104           Vector3<S> scale{0.5, 0.5, 0.5};
 
  106           p_BQ = quadrant.cwiseProduct(
half_size.cwiseProduct(scale));
 
  109           for (
int i : {0, 1, 2}) {
 
  117           scale << 1.5, 1.5, 1.5;
 
  119           p_BQ = quadrant.cwiseProduct(
half_size.cwiseProduct(scale));
 
  122           for (
int i : {0, 1, 2}) {
 
  130           scale << 0.5, 0.5, 0.5;
 
  132           p_BQ = quadrant.cwiseProduct(
half_size.cwiseProduct(scale));
 
  140         p_BQ = quadrant.cwiseProduct(
half_size * 1.5);
 
  143         for (
int i : {0, 1, 2})
 
  159 template <
typename S>
 
  160 struct TestConfiguration {
 
  161   TestConfiguration(
const std::string& name_in, 
const Vector3<S>& half_size_in,
 
  162                     S radius, 
const Vector3<S>& p_BSo_in, 
bool colliding)
 
  193 template <
typename S>
 
  194 std::vector<TestConfiguration<S>> AppendLabel(
 
  195     const std::vector<TestConfiguration<S>>& configurations,
 
  196     const std::string& 
label) {
 
  197   std::vector<TestConfiguration<S>> configs;
 
  198   for (
const auto& config : configurations) {
 
  199     configs.push_back(config);
 
  200     configs.back().name += 
" - " + 
label;
 
  207 template <
typename S>
 
  208 std::vector<TestConfiguration<S>> GetUniformConfigurations() {
 
  215   const Vector3<S> 
half_size{w / 2, d / 2, h / 2};
 
  216   const bool collides = 
true;
 
  218   std::vector<TestConfiguration<S>> configurations;
 
  224   configurations.emplace_back(
 
  225       "Separated; nearest face +z", 
half_size, 
r, p_BS, !collides);
 
  227     TestConfiguration<S>& config = configurations.back();
 
  228     config.expected_distance = p_BS(2) - 
half_size(2) - 
r;
 
  229     config.expected_p_BBs = Vector3<S>{p_BS(0), p_BS(1), 
half_size(2)};
 
  230     config.expected_p_BSb = Vector3<S>{p_BS(0), p_BS(1), p_BS(2) - 
r};
 
  235     const Vector3<S> p_BS = 
half_size + Vector3<S>{
r, 
r, 
r} * S(1.25);
 
  236   configurations.emplace_back(
 
  237       "Separated; nearest +x, +y, +z corner", 
half_size, 
r, p_BS, !collides);
 
  239     TestConfiguration<S>& config = configurations.back();
 
  241     const Vector3<S> r_SN = 
half_size - p_BS;
 
  242     const S len_r_SN = r_SN.norm();
 
  243     config.expected_distance = len_r_SN - 
r;
 
  245     config.expected_p_BSb = p_BS + r_SN * (
r / len_r_SN);
 
  254     const S target_depth = 
r * 0.5;
 
  255     const Vector3<S> p_BS{
half_size + Vector3<S>{0, 0, 
r - target_depth}};
 
  256     configurations.emplace_back(
 
  257         "Colliding: center outside, center projects onto +z face", 
half_size, 
r,
 
  259     TestConfiguration<S>& config = configurations.back();
 
  260     config.expected_depth = target_depth;
 
  261     config.expected_normal = -Vector3<S>::UnitZ();
 
  262     config.expected_pos = Vector3<S>{p_BS(0), p_BS(1), (h - target_depth) / 2};
 
  270     const S target_depth = 
r * 0.5;
 
  271     const Vector3<S> n_SB_B = Vector3<S>(-1, -2, -3).normalized();
 
  272     const Vector3<S> p_BS = 
half_size  - n_SB_B * (
r - target_depth);
 
  273     configurations.emplace_back(
 
  274         "Colliding: center outside, center nearest +x, +y, +z vertex",
 
  276     TestConfiguration<S>& config = configurations.back();
 
  277     config.expected_depth = target_depth;
 
  278     config.expected_normal = n_SB_B;
 
  279     config.expected_pos = 
half_size + n_SB_B * (target_depth * 0.5);
 
  292   const std::string axis_name[] = {
"x", 
"y", 
"z"};
 
  293   const S center_inset = 
half_size.minCoeff() * 0.5;
 
  294   for (
int axis = 0; axis < 3; ++axis) {
 
  295     for (
int sign : {-1, 1}) {
 
  296       const Vector3<S> dir = sign * Vector3<S>::Unit(axis);
 
  297       const Vector3<S> p_BS = dir * (center_inset - 
half_size(axis));
 
  298       configurations.emplace_back(
 
  299           "Colliding: center inside, center nearest " +
 
  300               std::string(sign > 0 ? 
"+" : 
"-") + axis_name[axis] + 
" face",
 
  302       TestConfiguration<S>& config = configurations.back();
 
  303       config.expected_depth = center_inset + 
r;
 
  304       config.expected_normal = dir;
 
  305       config.expected_pos = dir * ((
r + center_inset) / 2 - 
half_size(axis));
 
  316     const Vector3<S> p_BS{S(0.), S(0.), 
half_size(2)};
 
  317     configurations.emplace_back(
"Sphere center lies on +z face", 
half_size, 
r,
 
  319     TestConfiguration<S>& config = configurations.back();
 
  320     config.expected_depth = 
r;
 
  321     config.expected_normal = -Vector3<S>::UnitZ();
 
  322     config.expected_pos << p_BS(0), p_BS(1), p_BS(2) - 
r / 2;
 
  329     configurations.emplace_back(
"Sphere center lies on +x, +y, +z corner",
 
  331     TestConfiguration<S>& config = configurations.back();
 
  332     config.expected_depth = 
r;
 
  336     config.expected_normal = -Vector3<S>::UnitX();
 
  337     config.expected_pos << p_BS(0) - 
r / 2, p_BS(1), p_BS(2);
 
  346     configurations.emplace_back(
 
  347         "Sphere and cube origins coincident", Vector3<S>{10, 10, 10}, 5,
 
  348         Vector3<S>::Zero(), collides);
 
  349     TestConfiguration<S>& config = configurations.back();
 
  350     config.expected_depth = 15;
 
  351     config.expected_normal = -Vector3<S>::UnitX();
 
  352     config.expected_pos << 2.5, 0, 0;
 
  358     configurations.emplace_back(
 
  359         "Sphere and box coincident - x & z are minimum dimension",
 
  360         Vector3<S>{10, 15, 10}, 5, Vector3<S>::Zero(), collides);
 
  361     TestConfiguration<S>& config = configurations.back();
 
  362     config.expected_depth = 15;
 
  363     config.expected_normal = -Vector3<S>::UnitX();
 
  364     config.expected_pos << 2.5, 0, 0;
 
  370     configurations.emplace_back(
 
  371         "Sphere and box coincident - x is minimum dimension",
 
  372         Vector3<S>{10, 12, 14}, 5, Vector3<S>::Zero(), collides);
 
  373     TestConfiguration<S>& config = configurations.back();
 
  374     config.expected_depth = 15;
 
  375     config.expected_normal = -Vector3<S>::UnitX();
 
  376     config.expected_pos << 2.5, 0, 0;
 
  382     configurations.emplace_back(
 
  383         "Sphere and box coincident - y & z are minimum dimension",
 
  384         Vector3<S>{15, 10, 10}, 5, Vector3<S>::Zero(), collides);
 
  385     TestConfiguration<S>& config = configurations.back();
 
  386     config.expected_depth = 15;
 
  387     config.expected_normal = -Vector3<S>::UnitY();
 
  388     config.expected_pos << 0, 2.5, 0;
 
  394     configurations.emplace_back(
 
  395         "Sphere and box coincident - y is minimum dimension",
 
  396         Vector3<S>{15, 10, 14}, 5, Vector3<S>::Zero(), collides);
 
  397     TestConfiguration<S>& config = configurations.back();
 
  398     config.expected_depth = 15;
 
  399     config.expected_normal = -Vector3<S>::UnitY();
 
  400     config.expected_pos << 0, 2.5, 0;
 
  406     configurations.emplace_back(
 
  407         "Sphere and box coincident - z is minimum dimension",
 
  408         Vector3<S>{15, 12, 10}, 5, Vector3<S>::Zero(), collides);
 
  409     TestConfiguration<S>& config = configurations.back();
 
  410     config.expected_depth = 15;
 
  411     config.expected_normal = -Vector3<S>::UnitZ();
 
  412     config.expected_pos << 0, 0, 2.5;
 
  415   return configurations;
 
  420 template <
typename S>
 
  421 std::vector<TestConfiguration<S>> GetNonUniformConfigurations() {
 
  422   std::vector<TestConfiguration<S>> configurations;
 
  430       const Vector3<S> p_BS{
half_size(0) * S(0.95), S(0.),
 
  432       configurations.emplace_back(
"Long, skinny box collides with small sphere",
 
  434       TestConfiguration<S>& config = configurations.back();
 
  435       config.expected_normal = -Vector3<S>::UnitZ();
 
  436       config.expected_depth = 
r - (p_BS(2) - 
half_size(2));
 
  437       config.expected_pos =
 
  438           Vector3<S>{p_BS(0), p_BS(1),
 
  439                      half_size(2) - config.expected_depth / 2};
 
  444       const Vector3<S> p_BS{
half_size(0) * S(0.95), S(0.),
 
  446       configurations.emplace_back(
 
  447           "Long, skinny box *not* colliding with small sphere", 
half_size,
 
  449       TestConfiguration<S>& config = configurations.back();
 
  450       config.expected_distance = 
distance;
 
  451       config.expected_p_BSb = p_BS - Vector3<S>{0, 0, 
r};
 
  452       config.expected_p_BBs << p_BS(0), p_BS(1), 
half_size(2);
 
  459     const Vector3<S> 
half_size(0.1, 0.15, 0.2);
 
  461     const Vector3<S> n_SB = Vector3<S>{-1, -2, -3}.normalized();
 
  464       S target_depth = 
half_size.minCoeff() * 0.5;
 
  465       const Vector3<S> p_BS = 
half_size - n_SB * (
r - target_depth);
 
  466       configurations.emplace_back(
"Large sphere colliding with tiny box",
 
  468       TestConfiguration<S>& config = configurations.back();
 
  469       config.expected_normal = n_SB;
 
  470       config.expected_depth = target_depth;
 
  471       config.expected_pos = 
half_size + n_SB * (target_depth * 0.5);
 
  477       configurations.emplace_back(
 
  478           "Large sphere *not* colliding with tiny box", 
half_size,
 
  480       TestConfiguration<S>& config = configurations.back();
 
  481       config.expected_distance = 
distance;
 
  482       config.expected_p_BSb = p_BS + n_SB * 
r;
 
  487   return configurations;
 
  490 template <
typename S>
 
  492     std::function<void(
const TestConfiguration<S> &, 
const Transform3<S> &,
 
  493                        const Matrix3<S> &, S)>;
 
  502 template <
typename S>
 
  503 void EvalCollisionForTestConfiguration(
const TestConfiguration<S>& config,
 
  504                                        const Transform3<S>& X_WB,
 
  505                                        const Matrix3<S>& R_SB,
 
  508   Box<S> box{config.half_size * 2};
 
  509   Sphere<S> sphere{config.r};
 
  510   Transform3<S> X_BS = Transform3<S>::Identity();
 
  511   X_BS.translation() = config.p_BSo;
 
  512   X_BS.linear() = R_SB;
 
  513   Transform3<S> X_WS = X_WB * X_BS;
 
  515   bool colliding = sphereBoxIntersect<S>(sphere, X_WS, box, X_WB, 
nullptr);
 
  516   EXPECT_EQ(colliding, config.expected_colliding) << config.name;
 
  518   std::vector<ContactPoint<S>> contacts;
 
  519   colliding = sphereBoxIntersect<S>(sphere, X_WS, box, X_WB, &contacts);
 
  520   EXPECT_EQ(colliding, config.expected_colliding) << config.name;
 
  521   if (config.expected_colliding) {
 
  522     EXPECT_EQ(contacts.size(), 1u) << config.name;
 
  523     const ContactPoint<S>& contact = contacts[0];
 
  524     EXPECT_NEAR(contact.penetration_depth, config.expected_depth, eps)
 
  527                                 X_WB.linear() * config.expected_normal, eps,
 
  534     EXPECT_EQ(contacts.size(), 0u) << config.name;
 
  545 template <
typename S>
 
  546 void EvalDistanceForTestConfiguration(
const TestConfiguration<S>& config,
 
  547                                       const Transform3<S>& X_WB,
 
  548                                       const Matrix3<S>& R_SB,
 
  551   Box<S> box{config.half_size * 2};
 
  552   Sphere<S> sphere{config.r};
 
  553   Transform3<S> X_BS = Transform3<S>::Identity();
 
  554   X_BS.translation() = config.p_BSo;
 
  555   X_BS.linear() = R_SB;
 
  556   Transform3<S> X_WS = X_WB * X_BS;
 
  558   bool separated = sphereBoxDistance<S>(sphere, X_WS, box, X_WB, 
nullptr,
 
  560   EXPECT_NE(separated, config.expected_colliding) << config.name;
 
  565   Vector3<S> p_WSb{0, 0, 0};
 
  566   Vector3<S> p_WBs{0, 0, 0};
 
  569       sphereBoxDistance<S>(sphere, X_WS, box, X_WB, &
distance, &p_WSb, &p_WBs);
 
  570   EXPECT_NE(separated, config.expected_colliding) << config.name;
 
  571   if (!config.expected_colliding) {
 
  575                                 X_WB * config.expected_p_BSb, eps,
 
  579                                 X_WB * config.expected_p_BBs, eps,
 
  592 template <
typename S>
 
  593 void QueryWithVaryingWorldFrames(
 
  594     const std::vector<TestConfiguration<S>>& configurations,
 
  595     EvalFunc<S> query_eval, 
const Matrix3<S>& R_BS = Matrix3<S>::Identity()) {
 
  597   auto evaluate_all = [&R_BS, query_eval](
 
  598       const std::vector<TestConfiguration<S>>& configs,
 
  599       const Transform3<S>& X_FB) {
 
  600     for (
const auto config : configs) {
 
  601       query_eval(config, X_FB, R_BS, Eps<S>::value());
 
  606   Transform3<S> X_FB = Transform3<S>::Identity();
 
  607   evaluate_all(AppendLabel(configurations, 
"X_FB = I"), X_FB);
 
  610   X_FB.translation() << 1.3, 2.7, 6.5;
 
  611   evaluate_all(AppendLabel(configurations, 
"X_FB is translation"), X_FB);
 
  613   std::string axis_name[] = {
"x", 
"y", 
"z"};
 
  615   for (
int axis = 0; axis < 3; ++axis) {
 
  616     std::string 
label = 
"X_FB is 90-degree rotation around " + axis_name[axis];
 
  618     X_FB.linear() << angle_axis.matrix();
 
  619     evaluate_all(AppendLabel(configurations, 
label), X_FB);
 
  625                             Vector3<S>{1, 2, 3}.normalized()};
 
  626     X_FB.linear() << angle_axis.matrix();
 
  627     evaluate_all(AppendLabel(configurations, 
"X_FB is arbitrary rotation"),
 
  634     X_FB.linear() << angle_axis.matrix();
 
  635     evaluate_all(AppendLabel(configurations, 
"X_FB is near identity"),
 
  643 template <
typename S>
 
  644 void QueryWithOrientedSphere(
 
  645     const std::vector<TestConfiguration<S>>& configurations,
 
  646     EvalFunc<S> query_eval) {
 
  648   std::string axis_name[] = {
"x", 
"y", 
"z"};
 
  651   for (
int axis = 0; axis < 3; ++axis) {
 
  653     std::string 
label = 
"sphere rotate 90-degrees around " + axis_name[axis];
 
  654     QueryWithVaryingWorldFrames<S>(AppendLabel(configurations, 
label),
 
  655                                    query_eval, angle_axis.matrix());
 
  661                             Vector3<S>{1, 2, 3}.normalized()};
 
  662     std::string 
label = 
"sphere rotated arbitrarily";
 
  663     QueryWithVaryingWorldFrames<S>(AppendLabel(configurations, 
label),
 
  664                                    query_eval, angle_axis.matrix());
 
  670     std::string 
label = 
"sphere rotated near axes";
 
  671     QueryWithVaryingWorldFrames<S>(AppendLabel(configurations, 
label),
 
  672                                    query_eval, angle_axis.matrix());
 
  679 GTEST_TEST(SphereBoxPrimitiveTest, NearestPointInBox) {
 
  680   NearestPointInBox<float>();
 
  681   NearestPointInBox<double>();
 
  686 GTEST_TEST(SphereBoxPrimitiveTest, CollisionAcrossVaryingWorldFrames) {
 
  687   QueryWithVaryingWorldFrames<float>(GetUniformConfigurations<float>(),
 
  688                                      EvalCollisionForTestConfiguration<float>);
 
  689   QueryWithVaryingWorldFrames<double>(GetUniformConfigurations<double>(),
 
  690       EvalCollisionForTestConfiguration<double>);
 
  695 GTEST_TEST(SphereBoxPrimitiveTest, CollisionWithSphereRotations) {
 
  696   QueryWithOrientedSphere<float>(GetUniformConfigurations<float>(),
 
  697                                  EvalCollisionForTestConfiguration<float>);
 
  698   QueryWithOrientedSphere<double>(GetUniformConfigurations<double>(),
 
  699                                   EvalCollisionForTestConfiguration<double>);
 
  705 GTEST_TEST(SphereBoxPrimitiveTest, CollisionIncompatibleScales) {
 
  706   QueryWithVaryingWorldFrames<float>(GetNonUniformConfigurations<float>(),
 
  707                                      EvalCollisionForTestConfiguration<float>);
 
  708   QueryWithVaryingWorldFrames<double>(
 
  709       GetNonUniformConfigurations<double>(),
 
  710       EvalCollisionForTestConfiguration<double>);
 
  715 GTEST_TEST(SphereBoxPrimitiveTest, DistanceAcrossVaryingWorldFrames) {
 
  716   QueryWithVaryingWorldFrames<float>(GetUniformConfigurations<float>(),
 
  717                                      EvalDistanceForTestConfiguration<float>);
 
  718   QueryWithVaryingWorldFrames<double>(GetUniformConfigurations<double>(),
 
  719                                       EvalDistanceForTestConfiguration<double>);
 
  724 GTEST_TEST(SphereBoxPrimitiveTest, DistanceWithSphereRotations) {
 
  725   QueryWithOrientedSphere<float>(GetUniformConfigurations<float>(),
 
  726                                  EvalDistanceForTestConfiguration<float>);
 
  727   QueryWithOrientedSphere<double>(GetUniformConfigurations<double>(),
 
  728                                   EvalDistanceForTestConfiguration<double>);
 
  734 GTEST_TEST(SphereBoxPrimitiveTest, DistanceIncompatibleScales) {
 
  735   QueryWithVaryingWorldFrames<float>(GetNonUniformConfigurations<float>(),
 
  736                                      EvalDistanceForTestConfiguration<float>);
 
  737   QueryWithVaryingWorldFrames<double>(GetNonUniformConfigurations<double>(),
 
  738                                       EvalDistanceForTestConfiguration<double>);
 
  746 int main(
int argc, 
char *argv[]) {
 
  747   ::testing::InitGoogleTest(&argc, argv);
 
  748   return RUN_ALL_TESTS();