99 lines
5.4 KiB
C#
99 lines
5.4 KiB
C#
using System;
|
||
using MultiWheelC.TrajectoryPlanning.Utils;
|
||
|
||
namespace MultiWheelC.TrajectoryPlanning.CoarsePath.Search;
|
||
|
||
/// <summary>
|
||
/// 将运动原语的长度、方向、曲率和净空转换为统一的等效米搜索代价。
|
||
/// 此类型不修改搜索状态;换向标记必须由调用方依据相邻原语的方向关系提供。
|
||
/// </summary>
|
||
public sealed class SearchCostCalculator
|
||
{
|
||
/// <summary>创建使用调用时配置参数的等效米代价计算器。</summary>
|
||
public SearchCostCalculator()
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算一条运动原语的等效米增量代价。
|
||
/// 参数:lengthMeters 为非负弧长 m;direction 为原语方向;isGearSwitch 表示该原语前是否换向;
|
||
/// curvaturePerMeter 与 maximumCurvaturePerMeter 的单位为 1/m;curvatureLevelDelta 为相邻曲率等级差;
|
||
/// bodyClearanceMeters 为非负车体保守净空 m,可为正无穷;configuration 提供非负权重和惩罚。
|
||
/// 返回:有限且非负的等效米代价。
|
||
/// 失败:任一数值、方向或权重无效时抛出 <see cref="ArgumentOutOfRangeException"/>。
|
||
/// </summary>
|
||
public double Calculate(
|
||
double lengthMeters,
|
||
TravelDirection direction,
|
||
bool isGearSwitch,
|
||
double curvaturePerMeter,
|
||
double maximumCurvaturePerMeter,
|
||
int curvatureLevelDelta,
|
||
double bodyClearanceMeters,
|
||
HybridAStarConfiguration configuration)
|
||
{
|
||
ValidateInput(lengthMeters, direction, curvaturePerMeter, maximumCurvaturePerMeter, bodyClearanceMeters, configuration);
|
||
|
||
double directionMultiplier = direction == TravelDirection.Reverse ? configuration.ReverseCostMultiplier : 1d;
|
||
double normalizedCurvature = Math.Abs(curvaturePerMeter / maximumCurvaturePerMeter);
|
||
double clearanceDeficit = GetClearanceDeficit(bodyClearanceMeters, configuration.ClearanceCostDistanceMeters);
|
||
double levelDelta = Math.Abs((double)curvatureLevelDelta);
|
||
double motionCost = lengthMeters * directionMultiplier *
|
||
(1d + configuration.CurvatureMagnitudeWeight * normalizedCurvature +
|
||
configuration.ClearanceCostWeight * clearanceDeficit);
|
||
double gearSwitchCost = isGearSwitch ? configuration.GearSwitchPenaltyMeters : 0d;
|
||
double curvatureChangeCost = configuration.CurvatureChangePenaltyMetersPerLevel * levelDelta;
|
||
double totalCost = motionCost + gearSwitchCost + curvatureChangeCost;
|
||
if (!NumericGuard.IsFinite(totalCost) || totalCost < 0d)
|
||
throw new ArgumentOutOfRangeException(nameof(lengthMeters), "The calculated cost must remain finite and non-negative.");
|
||
|
||
return totalCost;
|
||
}
|
||
|
||
private static void ValidateInput(
|
||
double lengthMeters,
|
||
TravelDirection direction,
|
||
double curvaturePerMeter,
|
||
double maximumCurvaturePerMeter,
|
||
double bodyClearanceMeters,
|
||
HybridAStarConfiguration configuration)
|
||
{
|
||
if (!NumericGuard.IsFinite(lengthMeters) || lengthMeters < 0d)
|
||
throw new ArgumentOutOfRangeException(nameof(lengthMeters));
|
||
if (direction != TravelDirection.Forward && direction != TravelDirection.Reverse)
|
||
throw new ArgumentOutOfRangeException(nameof(direction));
|
||
if (!NumericGuard.IsFinite(curvaturePerMeter))
|
||
throw new ArgumentOutOfRangeException(nameof(curvaturePerMeter));
|
||
if (!NumericGuard.IsPositiveFinite(maximumCurvaturePerMeter))
|
||
throw new ArgumentOutOfRangeException(nameof(maximumCurvaturePerMeter));
|
||
if (Math.Abs(curvaturePerMeter) > maximumCurvaturePerMeter)
|
||
throw new ArgumentOutOfRangeException(nameof(curvaturePerMeter));
|
||
if (double.IsNaN(bodyClearanceMeters) || bodyClearanceMeters < 0d)
|
||
throw new ArgumentOutOfRangeException(nameof(bodyClearanceMeters));
|
||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||
|
||
ValidateNonNegativeFinite(configuration.HeuristicWeight, nameof(configuration.HeuristicWeight));
|
||
if (!NumericGuard.IsPositiveFinite(configuration.ReverseCostMultiplier))
|
||
throw new ArgumentOutOfRangeException(nameof(configuration.ReverseCostMultiplier));
|
||
ValidateNonNegativeFinite(configuration.GearSwitchPenaltyMeters, nameof(configuration.GearSwitchPenaltyMeters));
|
||
ValidateNonNegativeFinite(configuration.CurvatureMagnitudeWeight, nameof(configuration.CurvatureMagnitudeWeight));
|
||
ValidateNonNegativeFinite(configuration.CurvatureChangePenaltyMetersPerLevel, nameof(configuration.CurvatureChangePenaltyMetersPerLevel));
|
||
ValidateNonNegativeFinite(configuration.ClearanceCostWeight, nameof(configuration.ClearanceCostWeight));
|
||
if (!NumericGuard.IsPositiveFinite(configuration.ClearanceCostDistanceMeters))
|
||
throw new ArgumentOutOfRangeException(nameof(configuration.ClearanceCostDistanceMeters));
|
||
}
|
||
|
||
private static void ValidateNonNegativeFinite(double value, string parameterName)
|
||
{
|
||
if (!NumericGuard.IsFinite(value) || value < 0d)
|
||
throw new ArgumentOutOfRangeException(parameterName);
|
||
}
|
||
|
||
private static double GetClearanceDeficit(double bodyClearanceMeters, double clearanceCostDistanceMeters)
|
||
{
|
||
if (double.IsPositiveInfinity(bodyClearanceMeters)) return 0d;
|
||
double deficit = 1d - bodyClearanceMeters / clearanceCostDistanceMeters;
|
||
return deficit > 0d ? deficit : 0d;
|
||
}
|
||
}
|