Skip to content

Commit d15387a

Browse files
rjoomenclaude
andcommitted
Batch the parallel-array transform setters in Coal
The base-class array setters loop the single-object setter, flushing the broadphase per object and never reaching the full-refit branch. Override them (discrete one-pose, cast one-pose and two-pose) to collect then flush once. Also restores the debug size/presence asserts on the cast map-pair setter. Adds a conformance test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4fd0ba8 commit d15387a

6 files changed

Lines changed: 213 additions & 0 deletions

File tree

tesseract/collision/coal/include/tesseract/collision/coal/coal_cast_managers.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ class CoalCastBVHManager : public ContinuousContactManager
131131
void setCollisionObjectsTransform(const tesseract::common::LinkIdTransformMap& pose1,
132132
const tesseract::common::LinkIdTransformMap& pose2) override final;
133133

134+
void setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
135+
const tesseract::common::VectorIsometry3d& poses) override final;
136+
137+
void setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
138+
const tesseract::common::VectorIsometry3d& pose1,
139+
const tesseract::common::VectorIsometry3d& pose2) override final;
140+
134141
const std::vector<tesseract::common::LinkId>& getCollisionObjects() const override final;
135142

136143
void setActiveCollisionObjects(const std::unordered_set<tesseract::common::LinkId>& ids) override final;

tesseract/collision/coal/include/tesseract/collision/coal/coal_discrete_managers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class CoalDiscreteBVHManager : public DiscreteContactManager
111111

112112
void setCollisionObjectsTransform(const tesseract::common::LinkIdTransformMap& transforms) override final;
113113

114+
void setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
115+
const tesseract::common::VectorIsometry3d& poses) override final;
116+
114117
const std::vector<tesseract::common::LinkId>& getCollisionObjects() const override final;
115118

116119
void setActiveCollisionObjects(const std::unordered_set<tesseract::common::LinkId>& ids) override final;

tesseract/collision/coal/src/coal_cast_managers.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include <tesseract/common/macros.h>
4141
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
4242
#include <coal/broadphase/broadphase_dynamic_AABB_tree.h>
43+
#include <stdexcept>
44+
#include <string>
4345
TESSERACT_COMMON_IGNORE_WARNINGS_POP
4446

4547
#include <tesseract/geometry/geometry.h>
@@ -250,11 +252,13 @@ void CoalCastBVHManager::setCollisionObjectsTransform(const tesseract::common::L
250252
void CoalCastBVHManager::setCollisionObjectsTransform(const tesseract::common::LinkIdTransformMap& pose1,
251253
const tesseract::common::LinkIdTransformMap& pose2)
252254
{
255+
assert(pose1.size() == pose2.size());
253256
static_update_.clear();
254257
dynamic_update_.clear();
255258
for (const auto& [id, tf1] : pose1)
256259
{
257260
auto it2 = pose2.find(id);
261+
assert(it2 != pose2.end());
258262
if (it2 == pose2.end())
259263
continue;
260264

@@ -268,6 +272,47 @@ void CoalCastBVHManager::setCollisionObjectsTransform(const tesseract::common::L
268272
flushBatchUpdate();
269273
}
270274

275+
void CoalCastBVHManager::setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
276+
const tesseract::common::VectorIsometry3d& poses)
277+
{
278+
if (ids.size() != poses.size())
279+
throw std::runtime_error("CoalCastBVHManager, setCollisionObjectsTransform received " + std::to_string(ids.size()) +
280+
" ids but " + std::to_string(poses.size()) + " poses!");
281+
282+
static_update_.clear();
283+
dynamic_update_.clear();
284+
for (std::size_t i = 0; i < ids.size(); ++i)
285+
{
286+
auto it = link2cow_.find(ids[i]);
287+
if (it != link2cow_.end())
288+
collectTransformUpdate(it, poses[i]);
289+
}
290+
flushBatchUpdate();
291+
}
292+
293+
void CoalCastBVHManager::setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
294+
const tesseract::common::VectorIsometry3d& pose1,
295+
const tesseract::common::VectorIsometry3d& pose2)
296+
{
297+
if (ids.size() != pose1.size() || ids.size() != pose2.size())
298+
throw std::runtime_error("CoalCastBVHManager, setCollisionObjectsTransform received " + std::to_string(ids.size()) +
299+
" ids but " + std::to_string(pose1.size()) + " start poses and " +
300+
std::to_string(pose2.size()) + " end poses!");
301+
302+
static_update_.clear();
303+
dynamic_update_.clear();
304+
for (std::size_t i = 0; i < ids.size(); ++i)
305+
{
306+
auto cast_it = link2castcow_.find(ids[i]);
307+
if (cast_it == link2castcow_.end())
308+
continue;
309+
310+
auto reg_it = link2cow_.find(ids[i]);
311+
collectCastTransformUpdate(cast_it, reg_it, pose1[i], pose2[i]);
312+
}
313+
flushBatchUpdate();
314+
}
315+
271316
const std::vector<tesseract::common::LinkId>& CoalCastBVHManager::getCollisionObjects() const
272317
{
273318
return collision_objects_;

tesseract/collision/coal/src/coal_discrete_managers.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
#include <tesseract/common/macros.h>
4141
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
4242
#include <coal/broadphase/broadphase_dynamic_AABB_tree.h>
43+
#include <stdexcept>
44+
#include <string>
4345
TESSERACT_COMMON_IGNORE_WARNINGS_POP
4446

4547
#include <tesseract/collision/coal/coal_collision_geometry_cache.h>
@@ -210,6 +212,24 @@ void CoalDiscreteBVHManager::setCollisionObjectsTransform(const tesseract::commo
210212
flushBatchUpdate();
211213
}
212214

215+
void CoalDiscreteBVHManager::setCollisionObjectsTransform(const std::vector<tesseract::common::LinkId>& ids,
216+
const tesseract::common::VectorIsometry3d& poses)
217+
{
218+
if (ids.size() != poses.size())
219+
throw std::runtime_error("CoalDiscreteBVHManager, setCollisionObjectsTransform received " +
220+
std::to_string(ids.size()) + " ids but " + std::to_string(poses.size()) + " poses!");
221+
222+
static_update_.clear();
223+
dynamic_update_.clear();
224+
for (std::size_t i = 0; i < ids.size(); ++i)
225+
{
226+
auto it = link2cow_.find(ids[i]);
227+
if (it != link2cow_.end())
228+
collectTransformUpdate(it, poses[i]);
229+
}
230+
flushBatchUpdate();
231+
}
232+
213233
const std::vector<tesseract::common::LinkId>& CoalDiscreteBVHManager::getCollisionObjects() const
214234
{
215235
return collision_objects_;

tesseract/collision/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ add_gtest(${PROJECT_NAME}_coal_utils_shape_conversion_unit coal/coal_utils_shape
9292
add_gtest(${PROJECT_NAME}_coal_collision_object_wrapper_unit coal/coal_collision_object_wrapper_unit.cpp)
9393
add_gtest(${PROJECT_NAME}_coal_vs_bullet_convex_cast_unit coal/coal_vs_bullet_convex_cast_unit.cpp)
9494
add_gtest(${PROJECT_NAME}_coal_cast_gradient_quality_unit coal/coal_cast_gradient_quality_unit.cpp)
95+
add_gtest(${PROJECT_NAME}_coal_batched_transform_unit coal/coal_batched_transform_unit.cpp)
9596
add_gtest(${PROJECT_NAME}_coal_deferred_octree_expansion_unit coal/coal_deferred_octree_expansion_unit.cpp)
9697
add_gtest(${PROJECT_NAME}_coal_gjk_guess_threshold_unit coal/coal_gjk_guess_threshold_unit.cpp)
9798
target_link_libraries(${PROJECT_NAME}_coal_gjk_guess_threshold_unit PRIVATE ${PROJECT_NAME}_coal_factories)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#include <tesseract/common/macros.h>
2+
TESSERACT_COMMON_IGNORE_WARNINGS_PUSH
3+
#include <gtest/gtest.h>
4+
#include <Eigen/Geometry>
5+
#include <memory>
6+
#include <stdexcept>
7+
#include <vector>
8+
TESSERACT_COMMON_IGNORE_WARNINGS_POP
9+
10+
#include <tesseract/collision/coal/coal_cast_managers.h>
11+
#include <tesseract/collision/coal/coal_discrete_managers.h>
12+
#include <tesseract/geometry/impl/box.h>
13+
14+
using namespace tesseract::collision;
15+
16+
namespace
17+
{
18+
/** @brief Add a unit box collision object at the origin */
19+
template <typename ManagerType>
20+
void addBox(ManagerType& checker, const tesseract::common::LinkId& id)
21+
{
22+
CollisionShapePtr box = std::make_shared<tesseract::geometry::Box>(1, 1, 1);
23+
CollisionShapesConst shapes{ box };
24+
tesseract::common::VectorIsometry3d poses{ Eigen::Isometry3d::Identity() };
25+
checker.addCollisionObject(id, 0, shapes, poses);
26+
}
27+
28+
/** @brief Move two boxes into contact with a single one-pose parallel-array call and confirm the broadphase sees it */
29+
template <typename ManagerType>
30+
void runOnePoseArraySetterTest()
31+
{
32+
ManagerType checker;
33+
addBox(checker, tesseract::common::LinkId("box_a"));
34+
addBox(checker, tesseract::common::LinkId("box_b"));
35+
checker.setActiveCollisionObjects({ tesseract::common::LinkId("box_a"), tesseract::common::LinkId("box_b") });
36+
checker.setDefaultCollisionMargin(0.0);
37+
38+
// Apart
39+
const std::vector<tesseract::common::LinkId> ids{ "box_a", "box_b" };
40+
Eigen::Isometry3d far_a{ Eigen::Isometry3d::Identity() };
41+
far_a.translation() = Eigen::Vector3d(-10, 0, 0);
42+
Eigen::Isometry3d far_b{ Eigen::Isometry3d::Identity() };
43+
far_b.translation() = Eigen::Vector3d(10, 0, 0);
44+
checker.setCollisionObjectsTransform(ids, tesseract::common::VectorIsometry3d{ far_a, far_b });
45+
46+
ContactResultMap result;
47+
checker.contactTest(result, ContactRequest(ContactTestType::ALL));
48+
EXPECT_TRUE(result.empty());
49+
50+
// Overlapping, set in one call
51+
Eigen::Isometry3d near_a{ Eigen::Isometry3d::Identity() };
52+
near_a.translation() = Eigen::Vector3d(-0.25, 0, 0);
53+
Eigen::Isometry3d near_b{ Eigen::Isometry3d::Identity() };
54+
near_b.translation() = Eigen::Vector3d(0.25, 0, 0);
55+
checker.setCollisionObjectsTransform(ids, tesseract::common::VectorIsometry3d{ near_a, near_b });
56+
57+
result.clear();
58+
checker.contactTest(result, ContactRequest(ContactTestType::ALL));
59+
EXPECT_FALSE(result.empty());
60+
61+
EXPECT_TRUE(checker.getCollisionObjectsTransform(tesseract::common::LinkId("box_a")).isApprox(near_a, 1e-8));
62+
EXPECT_TRUE(checker.getCollisionObjectsTransform(tesseract::common::LinkId("box_b")).isApprox(near_b, 1e-8));
63+
}
64+
} // namespace
65+
66+
// Two boxes are moved into contact with a single parallel-array call. If the array setter does not apply its
67+
// updates to the broadphase, contactTest sees the stale poses and reports nothing: contactTest does not refresh
68+
// the broadphase itself.
69+
TEST(CoalBatchedTransformUnit, DiscreteArraySetterUpdatesBroadphase) // NOLINT
70+
{
71+
runOnePoseArraySetterTest<tesseract_collision_coal::CoalDiscreteBVHManager>();
72+
}
73+
74+
// A cast sweep set through the array setter must produce the same contact as the same sweep set one object at a time.
75+
TEST(CoalBatchedTransformUnit, CastArraySetterUpdatesBroadphase) // NOLINT
76+
{
77+
tesseract_collision_coal::CoalCastBVHManager checker;
78+
addBox(checker, tesseract::common::LinkId("box_a"));
79+
addBox(checker, tesseract::common::LinkId("box_b"));
80+
checker.setActiveCollisionObjects({ tesseract::common::LinkId("box_a") });
81+
checker.setDefaultCollisionMargin(0.0);
82+
83+
Eigen::Isometry3d start{ Eigen::Isometry3d::Identity() };
84+
start.translation() = Eigen::Vector3d(-5, 0, 0);
85+
Eigen::Isometry3d end{ Eigen::Isometry3d::Identity() };
86+
end.translation() = Eigen::Vector3d(5, 0, 0);
87+
88+
const std::vector<tesseract::common::LinkId> ids{ "box_a" };
89+
checker.setCollisionObjectsTransform(
90+
ids, tesseract::common::VectorIsometry3d{ start }, tesseract::common::VectorIsometry3d{ end });
91+
92+
ContactResultMap result;
93+
checker.contactTest(result, ContactRequest(ContactTestType::ALL));
94+
EXPECT_FALSE(result.empty()); // the swept box passes through box_b at the origin
95+
}
96+
97+
// The cast manager's one-pose array form positions objects without a sweep; it must apply its updates to the
98+
// broadphase just like the discrete form, with a zero-length sweep reporting the same overlap.
99+
TEST(CoalBatchedTransformUnit, CastOnePoseArraySetterUpdatesBroadphase) // NOLINT
100+
{
101+
runOnePoseArraySetterTest<tesseract_collision_coal::CoalCastBVHManager>();
102+
}
103+
104+
// Unregistered ids are skipped, exactly as the single-object setter skips them.
105+
TEST(CoalBatchedTransformUnit, DiscreteArraySetterSkipsUnknownIds) // NOLINT
106+
{
107+
tesseract_collision_coal::CoalDiscreteBVHManager checker;
108+
addBox(checker, tesseract::common::LinkId("box_a"));
109+
110+
Eigen::Isometry3d pose{ Eigen::Isometry3d::Identity() };
111+
pose.translation() = Eigen::Vector3d(1, 2, 3);
112+
const std::vector<tesseract::common::LinkId> ids{ "box_a", "does_not_exist" };
113+
114+
EXPECT_NO_THROW(checker.setCollisionObjectsTransform( // NOLINT
115+
ids,
116+
tesseract::common::VectorIsometry3d{ pose, Eigen::Isometry3d::Identity() }));
117+
EXPECT_TRUE(checker.getCollisionObjectsTransform(tesseract::common::LinkId("box_a")).isApprox(pose, 1e-8));
118+
}
119+
120+
// The size check lives in the base class; an override must not drop it.
121+
TEST(CoalBatchedTransformUnit, ArraySetterSizeMismatchThrows) // NOLINT
122+
{
123+
tesseract_collision_coal::CoalDiscreteBVHManager discrete;
124+
tesseract_collision_coal::CoalCastBVHManager cast;
125+
const std::vector<tesseract::common::LinkId> ids{ "box_a", "box_b" };
126+
const tesseract::common::VectorIsometry3d poses{ Eigen::Isometry3d::Identity() };
127+
128+
EXPECT_THROW(discrete.setCollisionObjectsTransform(ids, poses), std::runtime_error); // NOLINT
129+
EXPECT_THROW(cast.setCollisionObjectsTransform(ids, poses), std::runtime_error); // NOLINT
130+
EXPECT_THROW(cast.setCollisionObjectsTransform(ids, poses, poses), std::runtime_error); // NOLINT
131+
}
132+
133+
int main(int argc, char** argv)
134+
{
135+
testing::InitGoogleTest(&argc, argv);
136+
return RUN_ALL_TESTS();
137+
}

0 commit comments

Comments
 (0)