Skip to content

Commit 5df6cf4

Browse files
iche033Addisu Z. Taddese
authored andcommitted
bullet-featherstone: Fix get contacts (#838)
Signed-off-by: Ian Chen <ichen@openrobotics.org> Co-authored-by: Addisu Z. Taddese <addisu@openrobotics.org> (cherry picked from commit 00e0fdd)
1 parent f56db23 commit 5df6cf4

3 files changed

Lines changed: 234 additions & 28 deletions

File tree

bullet-featherstone/src/SDFFeatures.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1351,12 +1351,18 @@ bool SDFFeatures::AddSdfCollision(
13511351
// match the existing collider and issue a warning if they don't.
13521352
}
13531353

1354-
this->AddCollision(
1354+
btCollisionShape *shapePtr = shape.get();
1355+
auto colID = this->AddCollision(
13551356
CollisionInfo{
13561357
_collision.Name(),
13571358
std::move(shape),
13581359
_linkID,
13591360
linkFrameToCollision});
1361+
1362+
// Use user index to store the collision id in gz-physics
1363+
// This is used by GetContactsFromLastStep to retrieve the collision id
1364+
// from btCollisionShape
1365+
shapePtr->setUserIndex(std::size_t(colID));
13601366
}
13611367

13621368
return true;

bullet-featherstone/src/SimulationFeatures.cc

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,77 @@ namespace gz {
2727
namespace physics {
2828
namespace bullet_featherstone {
2929

30+
/////////////////////////////////////////////////
31+
bool hasConvexHullChildShapes(
32+
const btCollisionShape *_shape)
33+
{
34+
if (!_shape || !_shape->isCompound())
35+
return false;
36+
37+
const btCompoundShape *compoundShape =
38+
static_cast<const btCompoundShape *>(_shape);
39+
return (compoundShape->getNumChildShapes() > 0 &&
40+
compoundShape->getChildShape(0)->getShapeType() ==
41+
CONVEX_HULL_SHAPE_PROXYTYPE);
42+
}
43+
44+
/////////////////////////////////////////////////
45+
const btCollisionShape *findCollisionShape(
46+
const btCompoundShape *_compoundShape, int _childIndex)
47+
{
48+
// _childIndex should give us the index of the child shape within
49+
// _compoundShape which represents the collision.
50+
// One exception is when the collision is a convex decomposed mesh.
51+
// In this case, the child shape is another btCompoundShape (nested), and
52+
// _childIndex is the index of one of the decomposed convex hulls
53+
// in the nested compound shape. The nested compound shape is the collision.
54+
int childCount = _compoundShape->getNumChildShapes();
55+
if (childCount > 0)
56+
{
57+
if (_childIndex >= 0 && _childIndex < childCount)
58+
{
59+
// todo(iche033) We do not have sufficient info to determine which
60+
// child shape is the collision if the link has convex decomposed mesh
61+
// collisions alongside of other collisions. See following example:
62+
// parentLink -> boxShape0
63+
// -> boxShape1
64+
// -> compoundShape -> convexShape0
65+
// -> convexShape1
66+
// A _childIndex of 1 is ambiguous as it could refer to either
67+
// boxShape1 or convexShape1
68+
// return nullptr in this case to indicate ambiguity
69+
if (childCount > 1)
70+
{
71+
for (int i = 0; i < childCount; ++i)
72+
{
73+
const btCollisionShape *shape = _compoundShape->getChildShape(i);
74+
if (hasConvexHullChildShapes(shape))
75+
{
76+
static bool informed{false};
77+
if (!informed)
78+
{
79+
gzwarn << "Unable to determine the collision id for a link with "
80+
<< "both simple primitive and convex shape collisions."
81+
<< std::endl;
82+
informed = true;
83+
}
84+
return nullptr;
85+
}
86+
}
87+
}
88+
89+
const btCollisionShape *shape =
90+
_compoundShape->getChildShape(_childIndex);
91+
return shape;
92+
}
93+
else
94+
{
95+
return _compoundShape->getChildShape(0);
96+
}
97+
}
98+
return nullptr;
99+
}
100+
30101
/////////////////////////////////////////////////
31102
void enforceFixedConstraint(
32103
btMultiBodyFixedConstraint *_fixedConstraint)
@@ -187,34 +258,47 @@ SimulationFeatures::GetContactsFromLastStep(const Identity &_worldID) const
187258
{
188259
btPersistentManifold* contactManifold =
189260
world->world->getDispatcher()->getManifoldByIndexInternal(i);
190-
const btMultiBodyLinkCollider* obA =
261+
const btMultiBodyLinkCollider* ob0 =
191262
dynamic_cast<const btMultiBodyLinkCollider*>(contactManifold->getBody0());
192-
const btMultiBodyLinkCollider* obB =
263+
const btMultiBodyLinkCollider* ob1 =
193264
dynamic_cast<const btMultiBodyLinkCollider*>(contactManifold->getBody1());
194-
std::size_t collision1ID = std::numeric_limits<std::size_t>::max();
195-
std::size_t collision2ID = std::numeric_limits<std::size_t>::max();
196265

197-
for (const auto & link : this->links)
198-
{
199-
if (obA == link.second->collider.get())
200-
{
201-
for (const auto &v : link.second->collisionNameToEntityId)
202-
{
203-
collision1ID = v.second;
204-
}
205-
}
206-
if (obB == link.second->collider.get())
207-
{
208-
for (const auto &v : link.second->collisionNameToEntityId)
209-
{
210-
collision2ID = v.second;
211-
}
212-
}
213-
}
266+
if (!ob0 || !ob1)
267+
continue;
268+
269+
const btCollisionShape *linkShape0 = ob0->getCollisionShape();
270+
const btCollisionShape *linkShape1 = ob1->getCollisionShape();
271+
272+
if (!linkShape0 || !linkShape1 ||
273+
!linkShape0->isCompound() || !linkShape1->isCompound())
274+
continue;
275+
276+
const btCompoundShape *compoundShape0 =
277+
static_cast<const btCompoundShape *>(linkShape0);
278+
const btCompoundShape *compoundShape1 =
279+
static_cast<const btCompoundShape *>(linkShape1);
280+
214281
int numContacts = contactManifold->getNumContacts();
215282
for (int j = 0; j < numContacts; j++)
216283
{
217284
btManifoldPoint& pt = contactManifold->getContactPoint(j);
285+
286+
const btCollisionShape *colShape0 = findCollisionShape(
287+
compoundShape0, pt.m_index0);
288+
const btCollisionShape *colShape1 = findCollisionShape(
289+
compoundShape1, pt.m_index1);
290+
291+
std::size_t collision0ID = std::numeric_limits<std::size_t>::max();
292+
std::size_t collision1ID = std::numeric_limits<std::size_t>::max();
293+
if (colShape0)
294+
collision0ID = colShape0->getUserIndex();
295+
else if (compoundShape0->getNumChildShapes() > 0)
296+
collision0ID = compoundShape0->getChildShape(0)->getUserIndex();
297+
if (colShape1)
298+
collision1ID = colShape1->getUserIndex();
299+
else if (compoundShape1->getNumChildShapes() > 0)
300+
collision1ID = compoundShape1->getChildShape(0)->getUserIndex();
301+
218302
CompositeData extraData;
219303

220304
// Add normal, depth and wrench to extraData.
@@ -228,8 +312,8 @@ SimulationFeatures::GetContactsFromLastStep(const Identity &_worldID) const
228312
extraContactData.depth = pt.getDistance();
229313

230314
outContacts.push_back(SimulationFeatures::ContactInternal {
315+
this->GenerateIdentity(collision0ID, this->collisions.at(collision0ID)),
231316
this->GenerateIdentity(collision1ID, this->collisions.at(collision1ID)),
232-
this->GenerateIdentity(collision2ID, this->collisions.at(collision2ID)),
233317
convert(pt.getPositionWorldOnA()), extraData});
234318
}
235319
}

test/common_test/simulation_features.cc

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,11 +1306,32 @@ TYPED_TEST(SimulationFeaturesTestBasic, ShapeBoundingBox)
13061306
}
13071307
}
13081308

1309-
TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
1309+
using FeaturesCollisionContacts= gz::physics::FeatureList<
1310+
gz::physics::sdf::ConstructSdfModel,
1311+
gz::physics::sdf::ConstructSdfWorld,
1312+
gz::physics::GetModelFromWorld,
1313+
gz::physics::GetLinkFromModel,
1314+
gz::physics::GetShapeFromLink,
1315+
gz::physics::ForwardStep,
1316+
gz::physics::GetContactsFromLastStepFeature
1317+
>;
1318+
1319+
using SimulationFeaturesCollisionContacts =
1320+
SimulationFeaturesTest<FeaturesCollisionContacts>;
1321+
1322+
using FeaturesCollisionFilter = gz::physics::FeatureList<
1323+
FeaturesCollisionContacts,
1324+
gz::physics::CollisionFilterMaskFeature
1325+
>;
1326+
1327+
using SimulationFeaturesCollisionFilter =
1328+
SimulationFeaturesTest<FeaturesCollisionFilter>;
1329+
1330+
TEST_F(SimulationFeaturesCollisionFilter, CollideBitmasks)
13101331
{
13111332
for (const std::string &name : this->pluginNames)
13121333
{
1313-
auto world = LoadPluginAndWorld<Features>(
1334+
auto world = LoadPluginAndWorld<FeaturesCollisionFilter>(
13141335
this->loader,
13151336
name,
13161337
common_test::worlds::kShapesBitmaskWorld);
@@ -1319,7 +1340,7 @@ TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
13191340
auto filteredBox = world->GetModel("box_filtered");
13201341
auto collidingBox = world->GetModel("box_colliding");
13211342

1322-
auto checkedOutput = StepWorld<Features>(world, true).first;
1343+
auto checkedOutput = StepWorld<FeaturesCollisionFilter>(world, true).first;
13231344
EXPECT_TRUE(checkedOutput);
13241345
auto contacts = world->GetContactsFromLastStep();
13251346
// Only box_colliding should collide with box_base
@@ -1332,7 +1353,7 @@ TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
13321353
// Also test the getter
13331354
EXPECT_EQ(0xF0, collidingShape->GetCollisionFilterMask());
13341355
// Step and make sure there are no collisions
1335-
checkedOutput = StepWorld<Features>(world, false).first;
1356+
checkedOutput = StepWorld<FeaturesCollisionFilter>(world, false).first;
13361357
EXPECT_FALSE(checkedOutput);
13371358
contacts = world->GetContactsFromLastStep();
13381359
EXPECT_EQ(0u, contacts.size());
@@ -1341,7 +1362,7 @@ TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
13411362
// Equivalent to 0xFF
13421363
collidingShape->RemoveCollisionFilterMask();
13431364
filteredShape->RemoveCollisionFilterMask();
1344-
checkedOutput = StepWorld<Features>(world, false).first;
1365+
checkedOutput = StepWorld<FeaturesCollisionFilter>(world, false).first;
13451366
EXPECT_FALSE(checkedOutput);
13461367
// Expect box_filtered and box_colliding to collide with box_base
13471368
contacts = world->GetContactsFromLastStep();
@@ -1530,6 +1551,101 @@ TYPED_TEST(SimulationFeaturesTestBasic, RetrieveContacts)
15301551
}
15311552
}
15321553

1554+
TEST_F(SimulationFeaturesCollisionContacts,
1555+
ContactsForLinkWithMultipleCollisions)
1556+
{
1557+
// This test verifies that the collision entities in contact points
1558+
// are correct. Thest test world consists of a box model with a single
1559+
// link containining multiple collisions over a ground plane. Verify
1560+
// that only the bottom collision in the box model should collide with
1561+
// the ground plane.
1562+
1563+
auto getModelStr = [](const std::string &_name,
1564+
const gz::math::Pose3d &_pose)
1565+
{
1566+
std::stringstream modelStaticStr;
1567+
modelStaticStr << R"(
1568+
<sdf version="1.11">
1569+
<model name=")";
1570+
modelStaticStr << _name << R"(">
1571+
<pose>)";
1572+
modelStaticStr << _pose;
1573+
modelStaticStr << R"(</pose>
1574+
<link name="body">
1575+
<collision name="box_collision_mid">
1576+
<pose>0 0 1.0 0 0 0</pose>
1577+
<geometry>
1578+
<box><size>1 1 1</size></box>
1579+
</geometry>
1580+
</collision>
1581+
<collision name="box_collision_bottom">
1582+
<geometry>
1583+
<box><size>1 1 1</size></box>
1584+
</geometry>
1585+
</collision>
1586+
<collision name="box_collision_top">
1587+
<pose>0 0 2.0 0 0 0</pose>
1588+
<geometry>
1589+
<box><size>1 1 1</size></box>
1590+
</geometry>
1591+
</collision>
1592+
</link>
1593+
<static>false</static>
1594+
</model>
1595+
</sdf>)";
1596+
return modelStaticStr.str();
1597+
};
1598+
1599+
for (const std::string &name : this->pluginNames)
1600+
{
1601+
// TPE does not support collision checking with plane shapes.
1602+
if (this->PhysicsEngineName(name) == "tpe") continue;
1603+
1604+
auto world =
1605+
LoadPluginAndWorld<FeaturesCollisionContacts>(
1606+
this->loader,
1607+
name,
1608+
common_test::worlds::kGroundSdf);
1609+
ASSERT_NE(nullptr, world);
1610+
1611+
sdf::Root root;
1612+
sdf::Errors errors = root.LoadSdfString(getModelStr(
1613+
"boxes", gz::math::Pose3d(0, 0, 0.5, 0, 0, 0)));
1614+
ASSERT_TRUE(errors.empty()) << errors.front();
1615+
ASSERT_NE(nullptr, root.Model());
1616+
world->ConstructModel(*root.Model());
1617+
1618+
gz::physics::ForwardStep::Output output;
1619+
gz::physics::ForwardStep::State state;
1620+
gz::physics::ForwardStep::Input input;
1621+
for (std::size_t i = 0; i < 10; ++i)
1622+
{
1623+
world->Step(output, state, input);
1624+
}
1625+
1626+
// box lands on ground plane
1627+
// verify contacts
1628+
auto contacts = world->GetContactsFromLastStep();
1629+
EXPECT_LT(0u, contacts.size());
1630+
1631+
for (auto contact : contacts)
1632+
{
1633+
const auto &contactPoint = contact.template Get<
1634+
gz::physics::World3d<FeaturesCollisionContacts>::ContactPoint>();
1635+
ASSERT_TRUE(contactPoint.collision1);
1636+
ASSERT_TRUE(contactPoint.collision2);
1637+
EXPECT_NE(contactPoint.collision1, contactPoint.collision2);
1638+
1639+
auto c1 = contactPoint.collision1;
1640+
auto c2 = contactPoint.collision2;
1641+
// Contacts should be between ground plane model's 'collision'
1642+
// and box model's 'box_collision_bottom' collision
1643+
EXPECT_TRUE(c1->GetName() == "collision" ||
1644+
c1->GetName() == "box_collision_bottom");
1645+
}
1646+
}
1647+
}
1648+
15331649
struct FeaturesContactPropertiesCallback : gz::physics::FeatureList<
15341650
gz::physics::ConstructEmptyWorldFeature,
15351651

0 commit comments

Comments
 (0)