Skip to content

Commit 0595ea9

Browse files
Compute link bounding box on physics system
Signed-off-by: Gabriel Pacheco <[email protected]>
1 parent 84e70e7 commit 0595ea9

File tree

5 files changed

+403
-45
lines changed

5 files changed

+403
-45
lines changed

src/systems/physics/Physics.cc

Lines changed: 122 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,14 @@ class gz::sim::systems::PhysicsPrivate
343343
/// \param[in] _world The world to disable it for.
344344
public: void DisableContactSurfaceCustomization(const Entity &_world);
345345

346+
/// \brief Update the AxisAlignedBox for link entities.
347+
/// \param[in] _ecm The entity component manager.
348+
public: void UpdateLinksBoundingBoxes(EntityComponentManager &_ecm);
349+
350+
/// \brief Update the AxisAlignedBox for model entities.
351+
/// \param[in] _ecm The entity component manager.
352+
public: void UpdateModelsBoundingBoxes(EntityComponentManager &_ecm);
353+
346354
/// \brief Cache the top-level model for each entity.
347355
/// The key is an entity and the value is its top level model.
348356
public: std::unordered_map<Entity, Entity> topLevelModelMap;
@@ -555,12 +563,18 @@ class gz::sim::systems::PhysicsPrivate
555563

556564

557565
//////////////////////////////////////////////////
558-
// Bounding box
566+
// Model Bounding box
559567
/// \brief Feature list for model bounding box.
560-
public: struct BoundingBoxFeatureList : physics::FeatureList<
568+
public: struct ModelBoundingBoxFeatureList : physics::FeatureList<
561569
MinimumFeatureList,
562570
physics::GetModelBoundingBox>{};
563571

572+
//////////////////////////////////////////////////
573+
// Link Bounding box
574+
/// \brief Feature list for model bounding box.
575+
public: struct LinkBoundingBoxFeatureList : physics::FeatureList<
576+
MinimumFeatureList,
577+
physics::GetLinkBoundingBox>{};
564578

565579
//////////////////////////////////////////////////
566580
// Joint velocity command
@@ -680,7 +694,7 @@ class gz::sim::systems::PhysicsPrivate
680694
physics::Model,
681695
MinimumFeatureList,
682696
JointFeatureList,
683-
BoundingBoxFeatureList,
697+
ModelBoundingBoxFeatureList,
684698
NestedModelFeatureList,
685699
ConstructSdfLinkFeatureList,
686700
ConstructSdfJointFeatureList>;
@@ -697,7 +711,8 @@ class gz::sim::systems::PhysicsPrivate
697711
CollisionFeatureList,
698712
HeightmapFeatureList,
699713
LinkForceFeatureList,
700-
MeshFeatureList>;
714+
MeshFeatureList,
715+
LinkBoundingBoxFeatureList>;
701716

702717
/// \brief A map between link entity ids in the ECM to Link Entities in
703718
/// gz-physics.
@@ -2853,46 +2868,8 @@ void PhysicsPrivate::UpdatePhysics(EntityComponentManager &_ecm)
28532868
});
28542869

28552870
// Populate bounding box info
2856-
// Only compute bounding box if component exists to avoid unnecessary
2857-
// computations
2858-
_ecm.Each<components::Model, components::AxisAlignedBox>(
2859-
[&](const Entity &_entity, const components::Model *,
2860-
components::AxisAlignedBox *_bbox)
2861-
{
2862-
if (!this->entityModelMap.HasEntity(_entity))
2863-
{
2864-
gzwarn << "Failed to find model [" << _entity << "]." << std::endl;
2865-
return true;
2866-
}
2867-
2868-
auto bbModel =
2869-
this->entityModelMap.EntityCast<BoundingBoxFeatureList>(_entity);
2870-
2871-
if (!bbModel)
2872-
{
2873-
static bool informed{false};
2874-
if (!informed)
2875-
{
2876-
gzdbg << "Attempting to get a bounding box, but the physics "
2877-
<< "engine doesn't support feature "
2878-
<< "[GetModelBoundingBox]. Bounding box won't be populated."
2879-
<< std::endl;
2880-
informed = true;
2881-
}
2882-
2883-
// Break Each call since no AxisAlignedBox'es can be processed
2884-
return false;
2885-
}
2886-
2887-
math::AxisAlignedBox bbox =
2888-
math::eigen3::convert(bbModel->GetAxisAlignedBoundingBox());
2889-
auto state = _bbox->SetData(bbox, this->axisAlignedBoxEql) ?
2890-
ComponentState::PeriodicChange :
2891-
ComponentState::NoChange;
2892-
_ecm.SetChanged(_entity, components::AxisAlignedBox::typeId, state);
2893-
2894-
return true;
2895-
});
2871+
this->UpdateLinksBoundingBoxes(_ecm);
2872+
this->UpdateModelsBoundingBoxes(_ecm);
28962873
} // NOLINT readability/fn_size
28972874
// TODO (azeey) Reduce size of function and remove the NOLINT above
28982875

@@ -4371,6 +4348,107 @@ void PhysicsPrivate::DisableContactSurfaceCustomization(const Entity &_world)
43714348
<< _world << "]" << std::endl;
43724349
}
43734350

4351+
//////////////////////////////////////////////////
4352+
void PhysicsPrivate::UpdateLinksBoundingBoxes(EntityComponentManager &_ecm)
4353+
{
4354+
// Only compute bounding box if component exists to avoid unnecessary
4355+
// computation for links.
4356+
_ecm.Each<components::Link, components::AxisAlignedBox>(
4357+
[&](const Entity &_entity, const components::Link *,
4358+
components::AxisAlignedBox *_bbox)
4359+
{
4360+
auto linkPhys = this->entityLinkMap.Get(_entity);
4361+
if (!linkPhys)
4362+
{
4363+
gzwarn << "Failed to find link [" << _entity << "]." << std::endl;
4364+
return true;
4365+
}
4366+
4367+
auto bbLink = this->entityLinkMap.EntityCast<
4368+
LinkBoundingBoxFeatureList>(_entity);
4369+
4370+
// Bounding box expressed in the link frame
4371+
math::AxisAlignedBox bbox;
4372+
4373+
if (bbLink)
4374+
{
4375+
bbox = math::eigen3::convert(
4376+
bbLink->GetAxisAlignedBoundingBox(linkPhys->GetFrameID()));
4377+
}
4378+
else
4379+
{
4380+
static bool informed{false};
4381+
if (!informed)
4382+
{
4383+
gzdbg << "Attempting to get a bounding box, but the physics "
4384+
<< "engine doesn't support feature "
4385+
<< "[GetLinkBoundingBox]. Link bounding boxes will be "
4386+
<< "computed from their collision shapes based on their"
4387+
<< "geometry properties in SDF." << std::endl;
4388+
informed = true;
4389+
}
4390+
4391+
// Fallback to SDF API to get the link AABB from its collision shapes.
4392+
// If the link has no collision shapes, the AABB will be invalid.
4393+
bbox = gz::sim::Link(_entity).ComputeAxisAlignedBox(_ecm).value_or(
4394+
math::AxisAlignedBox());
4395+
}
4396+
4397+
auto state = _bbox->SetData(bbox, this->axisAlignedBoxEql) ?
4398+
ComponentState::PeriodicChange :
4399+
ComponentState::NoChange;
4400+
_ecm.SetChanged(_entity, components::AxisAlignedBox::typeId, state);
4401+
4402+
return true;
4403+
});
4404+
}
4405+
4406+
//////////////////////////////////////////////////
4407+
void PhysicsPrivate::UpdateModelsBoundingBoxes(EntityComponentManager &_ecm)
4408+
{
4409+
// Only compute bounding box if component exists to avoid unnecessary
4410+
// computation for models.
4411+
_ecm.Each<components::Model, components::AxisAlignedBox>(
4412+
[&](const Entity &_entity, const components::Model *,
4413+
components::AxisAlignedBox *_bbox)
4414+
{
4415+
if (!this->entityModelMap.HasEntity(_entity))
4416+
{
4417+
gzwarn << "Failed to find model [" << _entity << "]." << std::endl;
4418+
return true;
4419+
}
4420+
4421+
auto bbModel = this->entityModelMap.EntityCast<
4422+
ModelBoundingBoxFeatureList>(_entity);
4423+
4424+
if (!bbModel)
4425+
{
4426+
static bool informed{false};
4427+
if (!informed)
4428+
{
4429+
gzdbg << "Attempting to get a bounding box, but the physics "
4430+
<< "engine doesn't support feature "
4431+
<< "[GetModelBoundingBox]. Bounding box won't be populated."
4432+
<< std::endl;
4433+
informed = true;
4434+
}
4435+
4436+
// Break Each call since no AxisAlignedBox'es can be processed
4437+
return false;
4438+
}
4439+
4440+
// Bounding box expressed in the world frame
4441+
math::AxisAlignedBox bbox =
4442+
math::eigen3::convert(bbModel->GetAxisAlignedBoundingBox());
4443+
auto state = _bbox->SetData(bbox, this->axisAlignedBoxEql) ?
4444+
ComponentState::PeriodicChange :
4445+
ComponentState::NoChange;
4446+
_ecm.SetChanged(_entity, components::AxisAlignedBox::typeId, state);
4447+
4448+
return true;
4449+
});
4450+
}
4451+
43744452
GZ_ADD_PLUGIN(Physics,
43754453
System,
43764454
Physics::ISystemConfigure,

src/systems/physics/Physics.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
#include <gz/sim/config.hh>
5353
#include <gz/sim/System.hh>
54+
#include <gz/sim/Link.hh>
5455

5556
namespace gz
5657
{

test/integration/physics_system.cc

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,8 @@ TEST_F(PhysicsSystemFixtureWithDart6_10, JointEffortLimitsCommandComponent)
14931493
}
14941494

14951495
/////////////////////////////////////////////////
1496-
TEST_F(PhysicsSystemFixture, GZ_UTILS_TEST_DISABLED_ON_WIN32(GetBoundingBox))
1496+
TEST_F(PhysicsSystemFixture,
1497+
GZ_UTILS_TEST_DISABLED_ON_WIN32(GetModelBoundingBox))
14971498
{
14981499
ServerConfig serverConfig;
14991500

@@ -1561,6 +1562,138 @@ TEST_F(PhysicsSystemFixture, GZ_UTILS_TEST_DISABLED_ON_WIN32(GetBoundingBox))
15611562
bbox.begin()->second);
15621563
}
15631564

1565+
/////////////////////////////////////////////////
1566+
TEST_F(PhysicsSystemFixture,
1567+
GZ_UTILS_TEST_DISABLED_ON_WIN32(GetLinkBoundingBox))
1568+
{
1569+
const auto sdfFile = std::string(PROJECT_BINARY_PATH) +
1570+
"/test/worlds/bounding_boxes.sdf";
1571+
1572+
sdf::Root root;
1573+
root.Load(sdfFile);
1574+
const sdf::World *world = root.WorldByIndex(0);
1575+
auto gravity = world->Gravity();
1576+
1577+
// The server update period
1578+
auto dt = 1ms;
1579+
1580+
ServerConfig serverConfig;
1581+
serverConfig.SetSdfFile(sdfFile);
1582+
Server server(serverConfig);
1583+
server.SetUpdatePeriod(dt);
1584+
1585+
// Create a map of link scoped name to its world axis aligned box
1586+
std::map<std::string, math::AxisAlignedBox> worldLinkBbox;
1587+
1588+
// Create a map of link scoped name to its initial world axis aligned box
1589+
std::map<std::string, math::AxisAlignedBox> initialWorldLinkBbox;
1590+
1591+
test::Relay testSystem;
1592+
1593+
testSystem.OnPreUpdate(
1594+
[&](const UpdateInfo &_info,
1595+
EntityComponentManager &_ecm)
1596+
{
1597+
_ecm.Each<components::Link, components::Name>(
1598+
[&](const Entity &_entity, const components::Link *,
1599+
const components::Name *_name)->bool
1600+
{
1601+
auto link = Link(_entity);
1602+
auto model = link.ParentModel(_ecm);
1603+
EXPECT_TRUE(model.has_value());
1604+
1605+
if (_name->Data() == "link" && !model->Static(_ecm))
1606+
{
1607+
// The component should be null in the first iteration only
1608+
if (_info.iterations <= 1)
1609+
{
1610+
EXPECT_FALSE(link.AxisAlignedBox(_ecm).has_value());
1611+
link.EnableBoundingBoxChecks(_ecm);
1612+
1613+
// Store initial world bounding boxes for post-simulation checks
1614+
initialWorldLinkBbox[scopedName(_entity, _ecm, "/", false)] =
1615+
link.WorldAxisAlignedBox(_ecm).value();
1616+
1617+
return true;
1618+
}
1619+
auto linkAabb = link.AxisAlignedBox(_ecm);
1620+
auto worldAabb = link.WorldAxisAlignedBox(_ecm);
1621+
1622+
EXPECT_TRUE(linkAabb.has_value());
1623+
EXPECT_TRUE(worldAabb.has_value());
1624+
1625+
// Links are not initialized at the world origin
1626+
// and their models are free-falling.
1627+
EXPECT_NE(linkAabb, worldAabb);
1628+
}
1629+
return true;
1630+
});
1631+
});
1632+
1633+
testSystem.OnPostUpdate(
1634+
[&](const UpdateInfo &,
1635+
const EntityComponentManager &_ecm)
1636+
{
1637+
// store links that have axis aligned box computed
1638+
_ecm.Each<components::Link, components::AxisAlignedBox>(
1639+
[&](const Entity & _entity, const components::Link *,
1640+
const components::AxisAlignedBox *)->bool
1641+
{
1642+
auto link = Link(_entity);
1643+
auto aabbSdf = link.ComputeAxisAlignedBox(_ecm).value();
1644+
auto aabbPhysics = link.AxisAlignedBox(_ecm).value();
1645+
1646+
// The bounding box computed by the physics engine should be
1647+
// close-enough to the one computed from the SDF collisions.
1648+
EXPECT_TRUE(aabbPhysics.Min().Equal(aabbSdf.Min(), 5e-3));
1649+
EXPECT_TRUE(aabbPhysics.Max().Equal(aabbSdf.Max(), 5e-3));
1650+
1651+
// Store final world bounding boxes for post-simulation checks
1652+
worldLinkBbox[scopedName(_entity, _ecm, "/", false)] =
1653+
link.WorldAxisAlignedBox(_ecm).value();
1654+
1655+
return true;
1656+
});
1657+
});
1658+
1659+
server.AddSystem(testSystem.systemPtr);
1660+
size_t iters = 100;
1661+
const double tf = std::chrono::duration<double>(dt).count() * iters;
1662+
server.Run(true, iters, false);
1663+
EXPECT_EQ(2u, worldLinkBbox.size());
1664+
1665+
// Check that final world bound boxes are close to the initial ones
1666+
// plus the expected free-fall displacement.
1667+
for (const auto &[name, bbox] : worldLinkBbox)
1668+
{
1669+
auto expectedBbox = initialWorldLinkBbox[name] +
1670+
0.5 * gravity * tf * tf;
1671+
1672+
// 5mm tolerance is used since actual dt is variable
1673+
EXPECT_TRUE(expectedBbox.Min().Equal(bbox.Min(), 5e-3));
1674+
EXPECT_TRUE(expectedBbox.Max().Equal(bbox.Max(), 5e-3));
1675+
}
1676+
1677+
// Simulate until t=1.5s which is enough for models to reach the ground.
1678+
server.Run(true, 1500 - iters, false);
1679+
EXPECT_EQ(2u, worldLinkBbox.size());
1680+
1681+
// Collisions link starts at (2, 2, 2) and it is trivial to obtain its
1682+
// bounding box'es min and max points.
1683+
auto collisionsBbox = worldLinkBbox["bounding_boxes/collisions/link"];
1684+
EXPECT_EQ(math::Vector3d(2 - 1.5, 2 - 1.5, 0), collisionsBbox.Min());
1685+
EXPECT_EQ(math::Vector3d(2 + 1.5, 2 + 1.5, 1), collisionsBbox.Max());
1686+
1687+
// Duck link starts at (0, 0, 3) and its bounding box can be qualitatively
1688+
// checked since the displacement is along the z-axis only.
1689+
auto duckBbox = worldLinkBbox["bounding_boxes/duck/link"];
1690+
EXPECT_NEAR(duckBbox.Min().Z(), 0, 1e-3);
1691+
EXPECT_NEAR(duckBbox.Max().Z(), duckBbox.ZLength(), 1e-3);
1692+
EXPECT_LT(duckBbox.Min().X(), 0);
1693+
EXPECT_GT(duckBbox.Max().X(), 0);
1694+
EXPECT_LT(duckBbox.Min().Y(), 0);
1695+
EXPECT_GT(duckBbox.Max().Y(), 0);
1696+
}
15641697

15651698
/////////////////////////////////////////////////
15661699
// This tests whether nested models can be loaded correctly

test/worlds/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ configure_file (environmental_data.sdf.in ${PROJECT_BINARY_DIR}/test/worlds/envi
66

77
configure_file (environmental_sensor.sdf.in ${PROJECT_BINARY_DIR}/test/worlds/environmental_sensor.sdf)
88
configure_file (hydrodynamics.sdf.in ${PROJECT_BINARY_DIR}/test/worlds/hydrodynamics.sdf)
9+
configure_file (bounding_boxes.sdf.in ${PROJECT_BINARY_DIR}/test/worlds/bounding_boxes.sdf)

0 commit comments

Comments
 (0)