Files
ParkingRobot/ClumsyPilot/ParkrobTrajplanner/CoarsePath/Vehicle/FootprintCollisionChecker.cs
T

161 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using MultiWheelC.TrajectoryPlanning.Mapping;
using MultiWheelC.TrajectoryPlanning.Utils;
namespace MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle;
/// <summary>
/// 对以车辆几何中心表示的扩大矩形执行连续碰撞检查。
/// 地图查询使用 m;任何地图外车辆部分、占据格相交或擦边均按碰撞处理。
/// </summary>
public sealed class FootprintCollisionChecker
{
/// <summary>创建连续车辆碰撞检查器。</summary>
public FootprintCollisionChecker()
{
}
/// <summary>
/// 判断单个车辆位姿是否无碰撞。
/// 参数:pose 为车辆几何中心的世界 m/rad 位姿;map 为不可变规划地图;vehicle 为车辆尺寸;
/// additionalMarginMeters 为临时额外安全余量,单位 mbodyClearanceMeters 输出不含该临时余量的保守车体净空下界,单位 m。
/// 返回:扩大车辆矩形完整位于地图内且不与任何占据格相交或擦边时为 true;无效输入保守地返回 false。
/// </summary>
public bool IsPoseCollisionFree(Pose2D pose, PlanningGridMap map, VehicleParameters vehicle,
double additionalMarginMeters, out double bodyClearanceMeters)
{
bodyClearanceMeters = 0d;
if (map == null || !NumericGuard.IsFinite(additionalMarginMeters) || additionalMarginMeters < 0d ||
!VehicleFootprint.TryCreate(pose, vehicle, 0d, out VehicleFootprint bodyFootprint) ||
!VehicleFootprint.TryCreate(pose, vehicle, additionalMarginMeters, out VehicleFootprint checkedFootprint))
return false;
if (!AreCornersInsideMap(checkedFootprint, map)) return false;
double centerDistanceMeters = map.GetConservativeObstacleDistanceMeters(pose.X, pose.Y);
bodyClearanceMeters = GetBodyClearance(centerDistanceMeters, bodyFootprint.CircumscribedRadiusMeters);
if (centerDistanceMeters > checkedFootprint.CircumscribedRadiusMeters) return true;
return !IntersectsOccupiedCell(checkedFootprint, map);
}
/// <summary>
/// 判断两个位姿之间的平移和转向扫掠是否无碰撞。
/// 参数:from、to 为世界 m/rad 位姿;maximumCenterStepMeters 为允许的最大中心采样间距,单位 m;
/// minimumBodyClearanceMeters 输出沿途不含临时扫掠余量的保守车体净空下界,单位 m。
/// 返回:端点和每个分段扫掠均无碰撞时为 true;无效输入、地图外或任一中间碰撞时返回 false。
/// </summary>
public bool IsSweptMotionCollisionFree(Pose2D from, Pose2D to, PlanningGridMap map, VehicleParameters vehicle,
double maximumCenterStepMeters, out double minimumBodyClearanceMeters)
{
minimumBodyClearanceMeters = 0d;
if (map == null || from == null || to == null || !NumericGuard.IsFinite(maximumCenterStepMeters) || maximumCenterStepMeters <= 0d ||
!NumericGuard.IsFinite(from.X) || !NumericGuard.IsFinite(from.Y) || !NumericGuard.IsFinite(from.Heading) ||
!NumericGuard.IsFinite(to.X) || !NumericGuard.IsFinite(to.Y) || !NumericGuard.IsFinite(to.Heading) ||
!VehicleFootprint.TryCreate(from, vehicle, 0d, out VehicleFootprint bodyFootprint))
return false;
double allowedStepMeters = Math.Min(maximumCenterStepMeters, map.ResolutionMeters / 2d);
if (!NumericGuard.IsPositiveFinite(allowedStepMeters)) return false;
if (!IsPoseCollisionFree(from, map, vehicle, 0d, out double fromClearanceMeters)) return false;
minimumBodyClearanceMeters = fromClearanceMeters;
double deltaX = to.X - from.X;
double deltaY = to.Y - from.Y;
double centerDistanceMeters = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
if (!NumericGuard.IsFinite(centerDistanceMeters)) return false;
double headingDeltaRadians = AngleMath.ShortestSignedDifference(from.Heading, to.Heading);
if (!NumericGuard.IsFinite(headingDeltaRadians)) return false;
double rawSegmentCount = Math.Ceiling(centerDistanceMeters / allowedStepMeters);
if (!NumericGuard.IsFinite(rawSegmentCount) || rawSegmentCount > int.MaxValue) return false;
int segmentCount = Math.Max(1, (int)rawSegmentCount);
Pose2D previousPose = from;
for (int segment = 1; segment <= segmentCount; segment++)
{
double endFraction = (double)segment / segmentCount;
double middleFraction = ((double)segment - 0.5d) / segmentCount;
var currentPose = new Pose2D(
from.X + deltaX * endFraction,
from.Y + deltaY * endFraction,
from.Heading + headingDeltaRadians * endFraction);
var middlePose = new Pose2D(
from.X + deltaX * middleFraction,
from.Y + deltaY * middleFraction,
from.Heading + headingDeltaRadians * middleFraction);
double segmentDeltaX = currentPose.X - previousPose.X;
double segmentDeltaY = currentPose.Y - previousPose.Y;
double segmentCenterDisplacementMeters = Math.Sqrt(segmentDeltaX * segmentDeltaX + segmentDeltaY * segmentDeltaY);
double segmentHeadingDeltaRadians = currentPose.Heading - previousPose.Heading;
double temporaryMarginMeters = 0.5d * (segmentCenterDisplacementMeters +
bodyFootprint.CircumscribedRadiusMeters * Math.Abs(segmentHeadingDeltaRadians));
if (!NumericGuard.IsFinite(temporaryMarginMeters) ||
!IsPoseCollisionFree(middlePose, map, vehicle, temporaryMarginMeters, out double middleClearanceMeters))
return false;
minimumBodyClearanceMeters = Math.Min(minimumBodyClearanceMeters, middleClearanceMeters);
previousPose = currentPose;
}
if (!IsPoseCollisionFree(to, map, vehicle, 0d, out double toClearanceMeters)) return false;
minimumBodyClearanceMeters = Math.Min(minimumBodyClearanceMeters, toClearanceMeters);
return true;
}
private static bool AreCornersInsideMap(VehicleFootprint footprint, PlanningGridMap map)
{
for (int index = 0; index < 4; index++)
{
footprint.GetCorner(index, out double cornerX, out double cornerY);
if (!map.TryWorldToGrid(cornerX, cornerY, out _, out _)) return false;
}
return true;
}
private static double GetBodyClearance(double centerDistanceMeters, double bodyRadiusMeters)
{
if (double.IsPositiveInfinity(centerDistanceMeters)) return double.PositiveInfinity;
if (!NumericGuard.IsFinite(centerDistanceMeters) || !NumericGuard.IsFinite(bodyRadiusMeters)) return 0d;
return Math.Max(0d, centerDistanceMeters - bodyRadiusMeters);
}
private static bool IntersectsOccupiedCell(VehicleFootprint footprint, PlanningGridMap map)
{
GetCellRange(map, footprint.MinX, footprint.MaxX, footprint.MinY, footprint.MaxY,
out int firstRow, out int lastRow, out int firstCol, out int lastCol);
for (int row = firstRow; row <= lastRow; row++)
for (int col = firstCol; col <= lastCol; col++)
{
if (!map.IsOccupied(row, col)) continue;
GetCellBoundsMeters(map, row, col, out double cellMinX, out double cellMaxX, out double cellMinY, out double cellMaxY);
if (OrientedRectangleCellIntersection.Intersects(footprint, cellMinX, cellMaxX, cellMinY, cellMaxY)) return true;
}
return false;
}
private static void GetCellRange(PlanningGridMap map, double minX, double maxX, double minY, double maxY,
out int firstRow, out int lastRow, out int firstCol, out int lastCol)
{
double minimumMapX = map.Bounds.XMin / 1000d;
double minimumMapY = map.Bounds.YMin / 1000d;
firstCol = Clamp((int)Math.Floor((minX - minimumMapX) / map.ResolutionMeters) - 1, 0, map.Cols - 1);
lastCol = Clamp((int)Math.Floor((maxX - minimumMapX) / map.ResolutionMeters), 0, map.Cols - 1);
firstRow = Clamp((int)Math.Floor((minY - minimumMapY) / map.ResolutionMeters) - 1, 0, map.Rows - 1);
lastRow = Clamp((int)Math.Floor((maxY - minimumMapY) / map.ResolutionMeters), 0, map.Rows - 1);
}
private static void GetCellBoundsMeters(PlanningGridMap map, int row, int col,
out double cellMinX, out double cellMaxX, out double cellMinY, out double cellMaxY)
{
cellMinX = map.Bounds.XMin / 1000d + col * map.ResolutionMeters;
cellMinY = map.Bounds.YMin / 1000d + row * map.ResolutionMeters;
cellMaxX = Math.Min(map.Bounds.XMax / 1000d, cellMinX + map.ResolutionMeters);
cellMaxY = Math.Min(map.Bounds.YMax / 1000d, cellMinY + map.ResolutionMeters);
}
private static int Clamp(int value, int minimum, int maximum)
{
return value < minimum ? minimum : value > maximum ? maximum : value;
}
}