feat: define path smoothing contracts

This commit is contained in:
梁薄云
2026-07-28 16:48:24 +08:00
parent 727d0e5255
commit ebf7d7d3d4
14 changed files with 712 additions and 0 deletions
@@ -0,0 +1,8 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>三次 B 样条平滑参数。</summary>
public sealed class CubicBSplineOptions
{
/// <summary>端点切向控制柄相对于相邻弦长的比例。</summary>
public double EndpointTangentScale { get; set; } = 1d / 3d;
}
@@ -0,0 +1,16 @@
using System;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>局部三次 Bézier 平滑参数。</summary>
public sealed class LocalCubicBezierOptions
{
/// <summary>判定为明显转角的最小航向变化,单位 rad。</summary>
public double CornerHeadingThresholdRadians { get; set; } = Math.PI / 18d;
/// <summary>单个局部平滑窗口的最大弧长,单位 m。</summary>
public double MaximumWindowLengthMeters { get; set; } = 0.60d;
/// <summary>控制柄相对于窗口局部弦长的比例。</summary>
public double HandleLengthRatio { get; set; } = 1d / 3d;
}
@@ -0,0 +1,71 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>一条原始或平滑路径的不可变质量指标。</summary>
public sealed class PathQualityMetrics
{
/// <summary>创建全零、不可行的质量指标。</summary>
public PathQualityMetrics()
: this(false, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d)
{
}
/// <summary>创建完整的质量指标快照。</summary>
public PathQualityMetrics(
bool isFeasible,
double pathLengthMeters,
double maximumAbsoluteVehicleCurvaturePerMeter,
double rootMeanSquareVehicleCurvaturePerMeter,
double totalAbsoluteCurvatureVariationPerMeter,
double curvatureVariationEnergy,
double minimumBodyClearanceMeters,
double lengthChangePercent,
double peakCurvatureChangePercent,
double curvatureVariationChangePercent,
double minimumClearanceChangeMeters)
{
IsFeasible = isFeasible;
PathLengthMeters = pathLengthMeters;
MaximumAbsoluteVehicleCurvaturePerMeter = maximumAbsoluteVehicleCurvaturePerMeter;
RootMeanSquareVehicleCurvaturePerMeter = rootMeanSquareVehicleCurvaturePerMeter;
TotalAbsoluteCurvatureVariationPerMeter = totalAbsoluteCurvatureVariationPerMeter;
CurvatureVariationEnergy = curvatureVariationEnergy;
MinimumBodyClearanceMeters = minimumBodyClearanceMeters;
LengthChangePercent = lengthChangePercent;
PeakCurvatureChangePercent = peakCurvatureChangePercent;
CurvatureVariationChangePercent = curvatureVariationChangePercent;
MinimumClearanceChangeMeters = minimumClearanceChangeMeters;
}
/// <summary>该路径是否通过完整安全和运动学复核。</summary>
public bool IsFeasible { get; }
/// <summary>路径总弧长,单位 m。</summary>
public double PathLengthMeters { get; }
/// <summary>绝对车辆曲率峰值,单位 1/m。</summary>
public double MaximumAbsoluteVehicleCurvaturePerMeter { get; }
/// <summary>车辆曲率均方根,单位 1/m。</summary>
public double RootMeanSquareVehicleCurvaturePerMeter { get; }
/// <summary>逐方向段累加的绝对曲率变化,单位 1/m。</summary>
public double TotalAbsoluteCurvatureVariationPerMeter { get; }
/// <summary>逐方向段计算的曲率变化能量。</summary>
public double CurvatureVariationEnergy { get; }
/// <summary>完整扩大车体的最小保守净空,单位 m。</summary>
public double MinimumBodyClearanceMeters { get; }
/// <summary>相对原始粗路径的长度变化百分比。</summary>
public double LengthChangePercent { get; }
/// <summary>相对原始粗路径的峰值曲率变化百分比。</summary>
public double PeakCurvatureChangePercent { get; }
/// <summary>相对原始粗路径的曲率变化百分比。</summary>
public double CurvatureVariationChangePercent { get; }
/// <summary>相对原始粗路径的最小净空变化,单位 m。</summary>
public double MinimumClearanceChangeMeters { get; }
}
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>路径平滑的公共配置;所有距离使用 m。</summary>
public sealed class PathSmoothingConfiguration
{
/// <summary>创建带有安全默认值的平滑配置。</summary>
public PathSmoothingConfiguration()
{
OutputSpacingMeters = 0.05d;
MaximumCollisionCheckStepMeters = 0.025d;
MinimumClearanceReserveMeters = 0.02d;
SmoothingStrength = 1d;
AllowFallbackToCoarsePath = true;
RetryStrengthScales = new ReadOnlyCollection<double>(
new List<double> { 1d, 0.75d, 0.50d, 0.25d });
}
/// <summary>正式单算法入口使用的方法。</summary>
public SmoothingMethod Method { get; set; }
/// <summary>输出路径的目标弧长采样间距,单位 m。</summary>
public double OutputSpacingMeters { get; set; }
/// <summary>扫掠碰撞检查的最大步长,单位 m。</summary>
public double MaximumCollisionCheckStepMeters { get; set; }
/// <summary>平滑候选必须在最小净空之外保留的额外余量,单位 m。</summary>
public double MinimumClearanceReserveMeters { get; set; }
/// <summary>算法初始平滑强度。</summary>
public double SmoothingStrength { get; set; }
/// <summary>所有平滑尝试失败时是否允许发布经过复核的原粗路径。</summary>
public bool AllowFallbackToCoarsePath { get; set; }
/// <summary>有限且严格递减的平滑强度重试比例。</summary>
public IReadOnlyList<double> RetryStrengthScales { get; }
/// <summary>三次 B 样条专用参数。</summary>
public CubicBSplineOptions CubicBSpline { get; } = new CubicBSplineOptions();
/// <summary>局部三次 Bézier 专用参数。</summary>
public LocalCubicBezierOptions LocalCubicBezier { get; } = new LocalCubicBezierOptions();
/// <summary>分段五次多项式专用参数。</summary>
public PiecewiseQuinticOptions PiecewiseQuintic { get; } = new PiecewiseQuinticOptions();
}
@@ -0,0 +1,80 @@
using System;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>一次平滑尝试的不可变诊断信息。</summary>
public sealed class PathSmoothingDiagnostics
{
/// <summary>创建不含路径指标的默认诊断信息。</summary>
public PathSmoothingDiagnostics()
: this(new PathQualityMetrics(), TimeSpan.Zero, 0, 0d, string.Empty)
{
}
/// <summary>创建完整的平滑诊断快照。</summary>
public PathSmoothingDiagnostics(
PathQualityMetrics metrics,
TimeSpan elapsed,
int retryCount,
double acceptedStrength,
string terminationReason = null)
{
Metrics = metrics ?? new PathQualityMetrics();
Elapsed = elapsed;
RetryCount = retryCount;
AcceptedStrength = acceptedStrength;
TerminationReason = terminationReason ?? string.Empty;
}
/// <summary>使用所有测量量创建平滑诊断快照。</summary>
public PathSmoothingDiagnostics(
bool isFeasible,
double pathLengthMeters,
double maximumAbsoluteVehicleCurvaturePerMeter,
double rootMeanSquareVehicleCurvaturePerMeter,
double totalAbsoluteCurvatureVariationPerMeter,
double curvatureVariationEnergy,
double minimumBodyClearanceMeters,
double lengthChangePercent,
double peakCurvatureChangePercent,
double curvatureVariationChangePercent,
double minimumClearanceChangeMeters,
TimeSpan elapsed,
int retryCount,
double acceptedStrength,
string terminationReason = null)
: this(
new PathQualityMetrics(
isFeasible,
pathLengthMeters,
maximumAbsoluteVehicleCurvaturePerMeter,
rootMeanSquareVehicleCurvaturePerMeter,
totalAbsoluteCurvatureVariationPerMeter,
curvatureVariationEnergy,
minimumBodyClearanceMeters,
lengthChangePercent,
peakCurvatureChangePercent,
curvatureVariationChangePercent,
minimumClearanceChangeMeters),
elapsed,
retryCount,
acceptedStrength,
terminationReason)
{
}
/// <summary>路径质量指标;始终非空。</summary>
public PathQualityMetrics Metrics { get; }
/// <summary>从算法入口到返回诊断的耗时。</summary>
public TimeSpan Elapsed { get; }
/// <summary>已执行的安全强度重试次数。</summary>
public int RetryCount { get; }
/// <summary>通过复核的平滑强度;未接受候选时为零。</summary>
public double AcceptedStrength { get; }
/// <summary>面向调用方的稳定终止说明。</summary>
public string TerminationReason { get; }
}
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MultiWheelC.TrajectoryPlanning.CoarsePath;
using MultiWheelC.TrajectoryPlanning.Mapping;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>路径平滑所需的原始粗路径、复核上下文和配置。</summary>
public sealed class PathSmoothingRequest
{
/// <summary>创建路径平滑请求,并复制粗路径和方向分段集合。</summary>
public PathSmoothingRequest(
IReadOnlyList<CoarsePathPoint> coarsePath,
IReadOnlyList<PathSegment> segments,
PlanningGridMap map,
VehicleParameters vehicle,
PathSmoothingConfiguration configuration)
{
CoarsePath = CopyReadOnly(coarsePath);
Segments = CopyReadOnly(segments);
Map = map;
Vehicle = vehicle;
Configuration = configuration;
}
/// <summary>原始粗路径的不可变快照。</summary>
public IReadOnlyList<CoarsePathPoint> CoarsePath { get; }
/// <summary>原始粗路径方向分段的不可变快照。</summary>
public IReadOnlyList<PathSegment> Segments { get; }
/// <summary>用于平滑后完整车体复核的规划栅格地图。</summary>
public PlanningGridMap Map { get; }
/// <summary>车辆几何与最大曲率约束。</summary>
public VehicleParameters Vehicle { get; }
/// <summary>本次平滑的配置。</summary>
public PathSmoothingConfiguration Configuration { get; }
private static IReadOnlyList<T> CopyReadOnly<T>(IReadOnlyList<T> source)
{
var copy = new List<T>(source == null ? 0 : source.Count);
if (source != null)
{
for (int index = 0; index < source.Count; index++)
copy.Add(source[index]);
}
return new ReadOnlyCollection<T>(copy);
}
}
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>路径平滑的最终不可变结果。</summary>
public sealed class PathSmoothingResult
{
private static readonly IReadOnlyList<SmoothedPathPoint> EmptyPath =
new ReadOnlyCollection<SmoothedPathPoint>(new List<SmoothedPathPoint>());
private static readonly IReadOnlyList<SmoothedPathSegment> EmptySegments =
new ReadOnlyCollection<SmoothedPathSegment>(new List<SmoothedPathSegment>());
private PathSmoothingResult(
PathSmoothingStatus status,
SmoothingMethod? method,
IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments,
PathSmoothingDiagnostics diagnostics)
{
Status = status;
Method = method;
Path = path;
Segments = segments;
Diagnostics = diagnostics ?? new PathSmoothingDiagnostics(
new PathQualityMetrics(),
TimeSpan.Zero,
0,
0d,
"No smoothing diagnostics were supplied.");
}
/// <summary>最终发布状态。</summary>
public PathSmoothingStatus Status { get; }
/// <summary>成功或回退时的实际(或尝试)平滑方法;失败时为空。</summary>
public SmoothingMethod? Method { get; }
/// <summary>成功或经过复核的回退路径;其他状态始终为空且不可变。</summary>
public IReadOnlyList<SmoothedPathPoint> Path { get; }
/// <summary>覆盖 <see cref="Path"/> 的方向分段;其他状态始终为空且不可变。</summary>
public IReadOnlyList<SmoothedPathSegment> Segments { get; }
/// <summary>本次平滑的质量和终止诊断;始终非空。</summary>
public PathSmoothingDiagnostics Diagnostics { get; }
/// <summary>创建已通过所有复核的平滑结果。</summary>
public static PathSmoothingResult Success(
SmoothingMethod method,
IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments,
PathSmoothingDiagnostics diagnostics)
{
ValidatePublishedPath(path, segments);
return new PathSmoothingResult(
PathSmoothingStatus.Success,
method,
CopyReadOnly(path),
CopyReadOnly(segments),
diagnostics);
}
/// <summary>创建经过完整复核的原始粗路径回退结果。</summary>
public static PathSmoothingResult Fallback(
SmoothingMethod attemptedMethod,
IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments,
PathSmoothingDiagnostics diagnostics)
{
ValidatePublishedPath(path, segments);
return new PathSmoothingResult(
PathSmoothingStatus.FallbackToCoarsePath,
attemptedMethod,
CopyReadOnly(path),
CopyReadOnly(segments),
diagnostics);
}
/// <summary>创建不发布路径的失败、不可行、取消或输入无效结果。</summary>
public static PathSmoothingResult Failure(PathSmoothingStatus status, PathSmoothingDiagnostics diagnostics)
{
if (status == PathSmoothingStatus.Success || status == PathSmoothingStatus.FallbackToCoarsePath)
throw new ArgumentException("Use Success or Fallback to publish a path.", nameof(status));
if (!Enum.IsDefined(typeof(PathSmoothingStatus), status))
throw new ArgumentOutOfRangeException(nameof(status));
return new PathSmoothingResult(status, null, EmptyPath, EmptySegments, diagnostics);
}
private static void ValidatePublishedPath(
IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments)
{
if (path == null || path.Count == 0)
throw new ArgumentException("Published smoothing results require a non-empty path.", nameof(path));
if (segments == null || segments.Count == 0)
throw new ArgumentException("Published smoothing results require non-empty segments.", nameof(segments));
}
private static IReadOnlyList<T> CopyReadOnly<T>(IReadOnlyList<T> source)
{
var copy = new List<T>(source.Count);
for (int index = 0; index < source.Count; index++)
copy.Add(source[index]);
return new ReadOnlyCollection<T>(copy);
}
}
@@ -0,0 +1,12 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>路径平滑的最终发布状态。</summary>
public enum PathSmoothingStatus
{
Success,
FallbackToCoarsePath,
InvalidInput,
Infeasible,
Failed,
Cancelled,
}
@@ -0,0 +1,11 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>分段五次 Hermite 平滑参数。</summary>
public sealed class PiecewiseQuinticOptions
{
/// <summary>相邻内部结点的目标距离,单位 m。</summary>
public double KnotSpacingMeters { get; set; } = 0.50d;
/// <summary>允许创建内部结点的最小间距,单位 m。</summary>
public double MinimumKnotSpacingMeters { get; set; } = 0.10d;
}
@@ -0,0 +1,66 @@
using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>平滑空间路径上的不可变采样点;位置和长度单位为 m,航向为 rad,曲率为 1/m。</summary>
public sealed class SmoothedPathPoint
{
public SmoothedPathPoint(
double xMeters,
double yMeters,
double headingRadians,
double unwrappedHeadingRadians,
double arcLengthMeters,
TravelDirection direction,
double geometricCurvaturePerMeter,
double vehicleCurvaturePerMeter,
double bodyClearanceMeters,
bool isGearSwitchPoint,
SmoothedPathPointSource source)
{
X = xMeters;
Y = yMeters;
Heading = headingRadians;
UnwrappedHeading = unwrappedHeadingRadians;
ArcLength = arcLengthMeters;
Direction = direction;
GeometricCurvature = geometricCurvaturePerMeter;
VehicleCurvature = vehicleCurvaturePerMeter;
BodyClearance = bodyClearanceMeters;
IsGearSwitchPoint = isGearSwitchPoint;
Source = source;
}
/// <summary>世界 X 坐标,单位 m。</summary>
public double X { get; }
/// <summary>世界 Y 坐标,单位 m。</summary>
public double Y { get; }
/// <summary>归一化的车辆航向,单位 rad。</summary>
public double Heading { get; }
/// <summary>连续展开的车辆航向,单位 rad。</summary>
public double UnwrappedHeading { get; }
/// <summary>从完整路径起点累计的弧长,单位 m。</summary>
public double ArcLength { get; }
/// <summary>该点所在连续方向段的行驶方向。</summary>
public TravelDirection Direction { get; }
/// <summary>按几何弧长计算的有符号路径曲率,单位 1/m。</summary>
public double GeometricCurvature { get; }
/// <summary>车辆模型使用的有符号曲率,单位 1/m。</summary>
public double VehicleCurvature { get; }
/// <summary>扩大车体后的保守净空下界,单位 m。</summary>
public double BodyClearance { get; }
/// <summary>该点是否为新方向段开始的换向点。</summary>
public bool IsGearSwitchPoint { get; }
/// <summary>该点在平滑流程中的来源。</summary>
public SmoothedPathPointSource Source { get; }
}
@@ -0,0 +1,10 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>平滑路径点的来源。</summary>
public enum SmoothedPathPointSource
{
Anchor,
Interpolated,
GearSwitch,
CoarsePathFallback,
}
@@ -0,0 +1,41 @@
using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>平滑路径中方向一致的连续点范围;起止索引均包含在内。</summary>
public sealed class SmoothedPathSegment
{
public SmoothedPathSegment(
int segmentIndex,
TravelDirection direction,
int startIndex,
int endIndex,
bool startsAtGearSwitch,
bool endsAtGearSwitch)
{
SegmentIndex = segmentIndex;
Direction = direction;
StartIndex = startIndex;
EndIndex = endIndex;
StartsAtGearSwitch = startsAtGearSwitch;
EndsAtGearSwitch = endsAtGearSwitch;
}
/// <summary>从零开始的分段序号。</summary>
public int SegmentIndex { get; }
/// <summary>本段行驶方向。</summary>
public TravelDirection Direction { get; }
/// <summary>本段在平滑路径中的起始包含式索引。</summary>
public int StartIndex { get; }
/// <summary>本段在平滑路径中的结束包含式索引。</summary>
public int EndIndex { get; }
/// <summary>本段首点是否为换向后保留的新方向点。</summary>
public bool StartsAtGearSwitch { get; }
/// <summary>本段末点是否紧邻下一方向段的换向对。</summary>
public bool EndsAtGearSwitch { get; }
}
@@ -0,0 +1,9 @@
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing;
/// <summary>支持的粗路径平滑方法。</summary>
public enum SmoothingMethod
{
CubicBSpline,
LocalCubicBezier,
PiecewiseQuintic,
}