Skip to content

Commit d91afb2

Browse files
author
Addisu Z. Taddese
authored
Merge branch 'gz-physics9' into mujoco_prototype
2 parents 69226f6 + 84f9d3a commit d91afb2

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
@@ -1363,12 +1363,18 @@ bool SDFFeatures::AddSdfCollision(
13631363
// match the existing collider and issue a warning if they don't.
13641364
}
13651365

1366-
this->AddCollision(
1366+
btCollisionShape *shapePtr = shape.get();
1367+
auto colID = this->AddCollision(
13671368
CollisionInfo{
13681369
_collision.Name(),
13691370
std::move(shape),
13701371
_linkID,
13711372
linkFrameToCollision});
1373+
1374+
// Use user index to store the collision id in gz-physics
1375+
// This is used by GetContactsFromLastStep to retrieve the collision id
1376+
// from btCollisionShape
1377+
shapePtr->setUserIndex(std::size_t(colID));
13721378
}
13731379

13741380
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
@@ -1308,11 +1308,32 @@ TYPED_TEST(SimulationFeaturesTestBasic, ShapeBoundingBox)
13081308
}
13091309
}
13101310

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

1324-
auto checkedOutput = StepWorld<Features>(world, true).first;
1345+
auto checkedOutput = StepWorld<FeaturesCollisionFilter>(world, true).first;
13251346
EXPECT_TRUE(checkedOutput);
13261347
auto contacts = world->GetContactsFromLastStep();
13271348
// Only box_colliding should collide with box_base
@@ -1334,7 +1355,7 @@ TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
13341355
// Also test the getter
13351356
EXPECT_EQ(0xF0, collidingShape->GetCollisionFilterMask());
13361357
// Step and make sure there are no collisions
1337-
checkedOutput = StepWorld<Features>(world, false).first;
1358+
checkedOutput = StepWorld<FeaturesCollisionFilter>(world, false).first;
13381359
EXPECT_FALSE(checkedOutput);
13391360
contacts = world->GetContactsFromLastStep();
13401361
EXPECT_EQ(0u, contacts.size());
@@ -1343,7 +1364,7 @@ TYPED_TEST(SimulationFeaturesTestBasic, CollideBitmasks)
13431364
// Equivalent to 0xFF
13441365
collidingShape->RemoveCollisionFilterMask();
13451366
filteredShape->RemoveCollisionFilterMask();
1346-
checkedOutput = StepWorld<Features>(world, false).first;
1367+
checkedOutput = StepWorld<FeaturesCollisionFilter>(world, false).first;
13471368
EXPECT_FALSE(checkedOutput);
13481369
// Expect box_filtered and box_colliding to collide with box_base
13491370
contacts = world->GetContactsFromLastStep();
@@ -1532,6 +1553,101 @@ TYPED_TEST(SimulationFeaturesTestBasic, RetrieveContacts)
15321553
}
15331554
}
15341555

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

0 commit comments

Comments
 (0)