Skip to content

Commit 126214c

Browse files
committed
Fix distance calculation
Signed-off-by: Simon Dathan <simon.dathan@kudan.eu>
1 parent cea3222 commit 126214c

File tree

2 files changed

+93
-71
lines changed

2 files changed

+93
-71
lines changed

‎nav2_costmap_2d/plugins/obstacle_layer.cpp‎

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,10 @@ ObstacleLayer::updateBounds(
527527

528528
const sensor_msgs::msg::PointCloud2 & cloud = obs.cloud_;
529529

530-
unsigned int cell_max_range = cellDistance(obs.obstacle_max_range_);
531-
unsigned int cell_min_range = cellDistance(obs.obstacle_min_range_);
530+
const unsigned int max_cells = cellDistance(obs.obstacle_max_range_);
531+
const unsigned int min_cells = cellDistance(obs.obstacle_min_range_);
532+
const uint64_t sq_obstacle_max_range = max_cells * max_cells;
533+
const uint64_t sq_obstacle_min_range = min_cells * min_cells;
532534

533535
sensor_msgs::PointCloud2ConstIterator<float> iter_x(cloud, "x");
534536
sensor_msgs::PointCloud2ConstIterator<float> iter_y(cloud, "y");
@@ -550,26 +552,23 @@ ObstacleLayer::updateBounds(
550552
}
551553

552554
// compute the distance from the hitpoint to the pointcloud's origin
553-
// Calculate the distance in cells to match the ray trace algorithm used for clearing
554-
// obstacles (see Costmap2D::raytraceLine).
555-
unsigned int origin_cell_x;
556-
unsigned int origin_cell_y;
557-
worldToMap(obs.origin_.x, obs.origin_.y, origin_cell_x, origin_cell_y);
558-
unsigned int point_cell_x;
559-
unsigned int point_cell_y;
560-
worldToMap(px, py, point_cell_x, point_cell_y);
561-
int delta_x = point_cell_x - origin_cell_x;
562-
int delta_y = point_cell_y - origin_cell_y;
563-
unsigned int max_delta = std::max(std::abs(delta_x), std::abs(delta_y));
555+
// Calculate the distance in cell space to match the ray trace algorithm
556+
// used for clearing obstacles (see Costmap2D::raytraceLine).
557+
unsigned int x0, y0, x1, y1;
558+
worldToMap(obs.origin_.x, obs.origin_.y, x0, y0);
559+
worldToMap(px, py, x1, y1);
560+
const uint64_t sq_dist =
561+
static_cast<int64_t>(x1 - x0) * static_cast<int64_t>(x1 - x0) +
562+
static_cast<int64_t>(y1 - y0) * static_cast<int64_t>(y1 - y0);
564563

565564
// if the point is far enough away... we won't consider it
566-
if (max_delta > cell_max_range) {
565+
if (sq_dist > sq_obstacle_max_range) {
567566
RCLCPP_DEBUG(logger_, "The point is too far away");
568567
continue;
569568
}
570569

571570
// if the point is too close, do not consider it
572-
if (max_delta < cell_min_range) {
571+
if (sq_dist < sq_obstacle_min_range) {
573572
RCLCPP_DEBUG(logger_, "The point is too close");
574573
continue;
575574
}
@@ -787,15 +786,13 @@ ObstacleLayer::raytraceFreespace(
787786
unsigned int cell_raytrace_max_range = cellDistance(clearing_observation.raytrace_max_range_);
788787
unsigned int cell_raytrace_min_range = cellDistance(clearing_observation.raytrace_min_range_);
789788

790-
// Calculate the distance to the endpoint in cells to limit the maximum length of the raytrace
791-
// and avoid clearing the endpoint cell.
792-
int delta_x = x1 - x0;
793-
int delta_y = y1 - y0;
794-
unsigned int cell_dist_to_endpoint = std::max(std::abs(delta_x), std::abs(delta_y));
795-
if (cell_dist_to_endpoint < 1) {
796-
continue;
797-
}
798-
cell_raytrace_max_range = std::min(cell_raytrace_max_range, cell_dist_to_endpoint - 1);
789+
int64_t dx = int64_t(x1) - int64_t(x0);
790+
int64_t dy = int64_t(y1) - int64_t(y0);
791+
double observation_dist = std::hypot(dx, dy);
792+
793+
cell_raytrace_max_range =
794+
std::min(cell_raytrace_max_range,
795+
static_cast<unsigned int>(observation_dist) - 1);
799796

800797
MarkCell marker(costmap_, FREE_SPACE);
801798
// and finally... we can execute our trace to clear obstacles along that line

‎nav2_costmap_2d/test/unit/obstacle_layer_test.cpp‎

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class ObstacleLayerFineResolutionTest : public ObstacleLayerTest
135135
*/
136136
TEST_F(ObstacleLayerTest, testPointWithinCellMaxRange)
137137
{
138-
// Add observation: point at (0.5, 0.0)
138+
// Add observation: point at (0.55, 0.0)
139139
// obstacle_max_range = 1.0m
140140
addObservation(obstacle_layer_, 0.55, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
141141
true, true, 100.0, 0.0, 1.0, 0.0);
@@ -153,7 +153,7 @@ TEST_F(ObstacleLayerTest, testPointWithinCellMaxRange)
153153
*/
154154
TEST_F(ObstacleLayerTest, testPointBeyondCellMaxRange)
155155
{
156-
// Add observation: point at (1.5, 0.0)
156+
// Add observation: point at (1.55, 0.0)
157157
// obstacle_max_range = 1.0m
158158
addObservation(obstacle_layer_, 1.55, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
159159
true, true, 100.0, 0.0, 1.0, 0.0);
@@ -171,7 +171,7 @@ TEST_F(ObstacleLayerTest, testPointBeyondCellMaxRange)
171171
*/
172172
TEST_F(ObstacleLayerTest, testPointWithinCellMinRange)
173173
{
174-
// Add observation: point at (0.3, 0.0)
174+
// Add observation: point at (0.35, 0.0)
175175
// obstacle_min_range = 0.5m, obstacle_max_range = 10.0m
176176
addObservation(obstacle_layer_, 0.35, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
177177
true, true, 100.0, 0.0, 10.0, 0.5);
@@ -189,15 +189,35 @@ TEST_F(ObstacleLayerTest, testPointWithinCellMinRange)
189189
*/
190190
TEST_F(ObstacleLayerTest, testDiagonalDistanceWithinRange)
191191
{
192-
// Add observation: point at (0.7, 0.7)
192+
// Add observation: point at (0.701, 0.701)
193193
// obstacle_max_range = 1.0m
194-
// Cell distance: max(|7-0|, |7-0|) = 7 cells < 10 cells, so should be marked
195-
addObservation(obstacle_layer_, 0.75, 0.75, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
196-
true, true, 100.0, 0.0, 1.0, 0.0);
194+
// obstacle range = sqrt(7^2 + 7^2) = 9.9 cells
195+
addObservation(obstacle_layer_, 0.701, 0.701, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
196+
true, true, 100.0, 0.0, 1.0, 0.0);
197+
update();
198+
unsigned int mx, my;
199+
obstacle_layer_->worldToMap(0.701, 0.701, mx, my);
200+
unsigned char cost = obstacle_layer_->getCost(mx, my);
201+
202+
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
203+
}
204+
205+
/**
206+
* Test that point beyond obstacle max range but in same cell as max range is
207+
* marked as obstacle.
208+
*/
209+
TEST_F(ObstacleLayerTest, testPointBeyondRangeButInSameCellAsMaxRange) {
210+
// Observation at (0.79, 0.0)
211+
// obstacle_max_range = 0.72m
212+
// obstacle range = 0.79m, > 0.72m but at same cell distance as max range so
213+
// should be marked as obstacle, since it could be cleared by raytracing with
214+
// the same max range.
215+
addObservation(obstacle_layer_, 0.79, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
216+
true, true, 100.0, 0.0, 0.72, 0.0);
197217
update();
198218

199219
unsigned int mx, my;
200-
obstacle_layer_->worldToMap(0.75, 0.75, mx, my);
220+
obstacle_layer_->worldToMap(0.79, 0.0, mx, my);
201221
unsigned char cost = obstacle_layer_->getCost(mx, my);
202222

203223
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
@@ -208,15 +228,16 @@ TEST_F(ObstacleLayerTest, testDiagonalDistanceWithinRange)
208228
*/
209229
TEST_F(ObstacleLayerTest, testDiagonalDistanceBeyondRange)
210230
{
211-
// Add observation: point at (1.2, 1.2)
212-
// obstacle_max_range = 1.0m
213-
// Cell distance: max(|12-0|, |12-0|) = 12 cells > 10 cells, so should NOT be marked
214-
addObservation(obstacle_layer_, 1.25, 1.25, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
215-
true, true, 100.0, 0.0, 1.0, 0.0);
231+
// Add observation: point at (1.0, 1.0)
232+
// obstacle_max_range = 1.2m
233+
// Distance: sqrt(10^2 + 10^2) = 14.14 cells, 14 cells > 12 cells, so should
234+
// NOT be marked
235+
addObservation(obstacle_layer_, 1.0, 1.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
236+
true, true, 100.0, 0.0, 1.2, 0.0);
216237
update();
217238

218239
unsigned int mx, my;
219-
obstacle_layer_->worldToMap(1.25, 1.25, mx, my);
240+
obstacle_layer_->worldToMap(1.0, 1.0, mx, my);
220241
unsigned char cost = obstacle_layer_->getCost(mx, my);
221242

222243
ASSERT_NE(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
@@ -232,7 +253,6 @@ TEST_F(ObstacleLayerTest, testPointAtCellMaxRangeBoundary)
232253
addObservation(obstacle_layer_, 1.0, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
233254
true, true, 100.0, 0.0, 1.0, 0.0);
234255
update();
235-
236256
unsigned int mx, my;
237257
obstacle_layer_->worldToMap(1.0, 0.0, mx, my);
238258
unsigned char cost = obstacle_layer_->getCost(mx, my);
@@ -258,38 +278,19 @@ TEST_F(ObstacleLayerTest, testPointAtCellMinRangeBoundary)
258278
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
259279
}
260280

261-
/**
262-
* Test asymmetric distance: point with different delta_x and delta_y
263-
*/
264-
TEST_F(ObstacleLayerTest, testAsymmetricDistance)
265-
{
266-
// Add observation: point at (0.8, 0.3)
267-
// obstacle_max_range = 1.0m
268-
// Cell distance: max(|8-0|, |3-0|) = max(8, 3) = 8 cells < 10 cells, so should be marked
269-
addObservation(obstacle_layer_, 0.85, 0.55, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
270-
true, true, 100.0, 0.0, 1.0, 0.0);
271-
update();
272-
273-
unsigned int mx, my;
274-
obstacle_layer_->worldToMap(0.85, 0.55, mx, my);
275-
unsigned char cost = obstacle_layer_->getCost(mx, my);
276-
277-
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
278-
}
279-
280281
/**
281282
* Test with different resolution
282283
*/
283284
TEST_F(ObstacleLayerFineResolutionTest, testDifferentResolution)
284285
{
285-
// Add observation: point at (0.5, 0.0)
286-
// obstacle_max_range = 0.5m = 10 cells at 0.05m resolution
287-
addObservation(obstacle_layer_, 0.52, 0.32, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
288-
true, true, 100.0, 0.0, 0.5, 0.0);
286+
// Add observation: point at (0.52, 0.28)
287+
// Resolution: 0.05m/cell
288+
// Cell index: (10, 5)
289+
addObservation(obstacle_layer_, 0.52, 0.28, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
290+
true, true, 100.0, 0.0, 1.0, 0.0);
289291
update();
290292

291-
// x = 0.5 / 0.05 = 10; y = 0.3 / 0.05 = 6;
292-
unsigned char cost = obstacle_layer_->getCost(10, 6);
293+
unsigned char cost = obstacle_layer_->getCost(10, 5);
293294
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
294295
}
295296

@@ -298,16 +299,15 @@ TEST_F(ObstacleLayerFineResolutionTest, testDifferentResolution)
298299
*/
299300
TEST_F(ObstacleLayerTest, testOriginOffset)
300301
{
301-
// Add observation: point at (5.5, 2.5) with origin at (5.0, 2.0)
302+
// Add observation: point at (1.75, 1.85) with origin at (0.5, 1.5)
302303
// obstacle_max_range = 2.0m
303304
addObservation(obstacle_layer_, 1.75, 1.85, MAX_Z / 2, 0.5, 1.5, MAX_Z / 2,
304-
true, true, 100.0, 0.0, 2.0, 0.0);
305+
true, true, 100.0, 0.0, 2.0, 0.0);
305306
update();
306307

307308
unsigned int mx, my;
308309
obstacle_layer_->worldToMap(1.75, 1.85, mx, my);
309310
unsigned char cost = obstacle_layer_->getCost(mx, my);
310-
311311
ASSERT_EQ(cost, nav2_costmap_2d::LETHAL_OBSTACLE);
312312
}
313313

@@ -325,14 +325,13 @@ TEST_F(ObstacleLayerTest, testRaytraceDoesNotClearEndpointCell)
325325
obstacle_layer_->setCost(x, my_end, nav2_costmap_2d::LETHAL_OBSTACLE);
326326
}
327327

328-
// Add observation at (1.01, 0.0)
328+
// Add observation at (1.05, 0.0)
329329
// This is beyond the obstacle_max_range of 0.5m so should not be marked.
330330
// raytrace_max_range = 2.0m
331331
// So raytrace should clear cells up to but NOT including the endpoint
332332
addObservation(obstacle_layer_, 1.05, 0.0, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
333333
true, true, 2.0, 0.0, 0.5, 0.0);
334334
update();
335-
336335
// The endpoint cell should still be LETHAL_OBSTACLE (not cleared)
337336
unsigned char cost_endpoint = obstacle_layer_->getCost(mx_end, my_end);
338337
ASSERT_EQ(cost_endpoint, nav2_costmap_2d::LETHAL_OBSTACLE);
@@ -345,7 +344,8 @@ TEST_F(ObstacleLayerTest, testRaytraceDoesNotClearEndpointCell)
345344
}
346345

347346
/**
348-
* Test that raytracing clears cells along the path and the endpoint is marked as LETHAL_OBSTACLE
347+
* Test that raytracing clears cells along the path and the endpoint is marked
348+
* as obstacle.
349349
*/
350350
TEST_F(ObstacleLayerTest, testRaytraceClearsPathAndMarksEndpoint)
351351
{
@@ -388,7 +388,7 @@ TEST_F(ObstacleLayerTest, testDiagonalRaytraceDoesNotClearEndpoint)
388388
obstacle_layer_->worldToMap(0.45, 0.45, mx_mid, my_mid);
389389
obstacle_layer_->setCost(mx_mid, my_mid, nav2_costmap_2d::LETHAL_OBSTACLE);
390390

391-
// Add observation at (0.7, 0.7)
391+
// Add observation at (0.75, 0.75)
392392
addObservation(obstacle_layer_, 0.75, 0.75, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
393393
true, true, 2.0, 0.0, 100.0, 0.0);
394394
update();
@@ -402,6 +402,31 @@ TEST_F(ObstacleLayerTest, testDiagonalRaytraceDoesNotClearEndpoint)
402402
ASSERT_EQ(cost_mid, nav2_costmap_2d::FREE_SPACE);
403403
}
404404

405+
/**
406+
* Test diagonal clearing up to max range
407+
*/
408+
TEST_F(ObstacleLayerTest, testClearDiagonalDistance) {
409+
// Mark all points as obstacles
410+
for (unsigned int x = 0; x < obstacle_layer_->getSizeInCellsX(); x++) {
411+
for (unsigned int y = 0; y < obstacle_layer_->getSizeInCellsY(); y++) {
412+
obstacle_layer_->setCost(x, y, nav2_costmap_2d::LETHAL_OBSTACLE);
413+
}
414+
}
415+
// Add observation at (1.55, 1.55) with max clearing range of 1.0m
416+
// This should clear the diagonal distance up to range of 1.0m, cells (0, 0)
417+
// to (7, 7)
418+
addObservation(obstacle_layer_, 1.55, 1.55, MAX_Z / 2, 0.0, 0.0, MAX_Z / 2,
419+
true, true, 1.0, 0.0, 100.0, 0.0);
420+
update();
421+
422+
for (unsigned int i = 0; i < 8; i++) {
423+
ASSERT_EQ(obstacle_layer_->getCost(i, i), nav2_costmap_2d::FREE_SPACE);
424+
}
425+
ASSERT_EQ(countValues(*obstacle_layer_, nav2_costmap_2d::FREE_SPACE), 8);
426+
ASSERT_EQ(countValues(*obstacle_layer_, nav2_costmap_2d::LETHAL_OBSTACLE),
427+
20 * 20 - 8);
428+
}
429+
405430
/**
406431
* Test edge case: observation very close to origin
407432
* Resolution: 0.1m/cell

0 commit comments

Comments
 (0)