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();