using System; using System.Collections.Generic; using MultiWheelC.TrajectoryPlanning.CoarsePath; using MultiWheelC.TrajectoryPlanning.PathSmoothing; using MultiWheelC.TrajectoryPlanning.Utils; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing; /// 校验粗路径契约、保护换向拓扑并产生统一间距的平滑输入。 public sealed class PathSmoothingPreprocessor { private const double Tolerance = 1e-8d; private readonly ArcLengthResampler _resampler; /// 创建使用默认确定性重采样器的预处理器。 public PathSmoothingPreprocessor() : this(new ArcLengthResampler()) { } /// 创建使用指定确定性弧长重采样器的预处理器。 /// 同一方向段内按 m 为单位弧长重采样的组件,不能为 。 public PathSmoothingPreprocessor(ArcLengthResampler resampler) { _resampler = resampler ?? throw new ArgumentNullException(nameof(resampler)); } /// 校验粗路径契约、保留换向拓扑,并按配置间距拆分和重采样方向段。 /// 不可变平滑请求;粗路径位置/弧长单位为 m、航向为 rad,且必须携带地图、车辆和配置。 /// 成功时为包含只读、同方向严格递增弧长段的预处理路径;失败时为 。 /// 失败时说明输入、拓扑或采样间距约束;成功时为空字符串。 /// 所有输入、方向分段、换向锚点和重采样检查通过时为 ;否则为 且不产生部分路径。 public bool TryPrepare(PathSmoothingRequest request, out PreparedPath preparedPath, out string reason) { preparedPath = null; reason = string.Empty; if (request == null || request.Map == null || request.Vehicle == null || request.Configuration == null || request.CoarsePath == null || request.Segments == null || request.CoarsePath.Count == 0 || request.Segments.Count == 0) { reason = "平滑请求缺少粗路径、方向分段、地图、车辆或配置。"; return false; } PathSmoothingConfiguration configuration = request.Configuration; if (!NumericGuard.IsPositiveFinite(configuration.OutputSpacingMeters)) { reason = "平滑输出采样间距无效。"; return false; } if (!ValidatePathPoints(request.CoarsePath, out reason) || !ValidateSegments(request.CoarsePath, request.Segments, out reason)) { return false; } var preparedSegments = new List(request.Segments.Count); for (int segmentIndex = 0; segmentIndex < request.Segments.Count; segmentIndex++) { PathSegment sourceSegment = request.Segments[segmentIndex]; double segmentStartArcLength = request.CoarsePath[sourceSegment.StartIndex].ArcLength; var segmentPoints = new List(sourceSegment.EndIndex - sourceSegment.StartIndex + 1); for (int pointIndex = sourceSegment.StartIndex; pointIndex <= sourceSegment.EndIndex; pointIndex++) { CoarsePathPoint point = request.CoarsePath[pointIndex]; bool isGearSwitch = point.IsGearSwitchPoint; segmentPoints.Add(new SmoothingPoint2D( point.X, point.Y, point.ArcLength - segmentStartArcLength, point.Heading, point.UnwrappedHeading, point.BodyClearance, isGearSwitch, isGearSwitch ? SmoothedPathPointSource.GearSwitch : SmoothedPathPointSource.Anchor)); } var unresampled = new PreparedDirectionSegment( sourceSegment.SegmentIndex, sourceSegment.Direction, segmentPoints, sourceSegment.StartsAtGearSwitch, sourceSegment.EndsAtGearSwitch, request.CoarsePath[sourceSegment.StartIndex].VehicleCurvature); if (!_resampler.TryResample(unresampled, configuration.OutputSpacingMeters, out PreparedDirectionSegment resampled, out reason)) return false; preparedSegments.Add(new PreparedDirectionSegment( resampled.SegmentIndex, resampled.Direction, resampled.Points, resampled.StartsAtGearSwitch, resampled.EndsAtGearSwitch, unresampled.StartVehicleCurvaturePerMeter)); } preparedPath = new PreparedPath(preparedSegments); return true; } private static bool ValidatePathPoints(IReadOnlyList path, out string reason) { reason = string.Empty; CoarsePathPoint first = path[0]; if (!IsValidPoint(first) || first.IsGearSwitchPoint || Math.Abs(first.ArcLength) > Tolerance) { reason = "粗路径首点无效。"; return false; } for (int index = 1; index < path.Count; index++) { CoarsePathPoint previous = path[index - 1]; CoarsePathPoint current = path[index]; if (!IsValidPoint(current) || current.ArcLength + Tolerance < previous.ArcLength) { reason = "粗路径包含非法数值或非递增弧长。"; return false; } double expectedHeadingDelta = AngleMath.ShortestSignedDifference(previous.Heading, current.Heading); if (!NumericGuard.IsFinite(expectedHeadingDelta) || Math.Abs((current.UnwrappedHeading - previous.UnwrappedHeading) - expectedHeadingDelta) > Tolerance) { reason = "粗路径展开航向不连续。"; return false; } bool duplicatePoseAndArc = Math.Abs(current.X - previous.X) <= Tolerance && Math.Abs(current.Y - previous.Y) <= Tolerance && Math.Abs(current.ArcLength - previous.ArcLength) <= Tolerance && Math.Abs(AngleMath.ShortestSignedDifference(previous.Heading, current.Heading)) <= Tolerance; if (duplicatePoseAndArc) { if (previous.Direction == current.Direction || !current.IsGearSwitchPoint) { reason = "粗路径包含非法的重复点。"; return false; } } else if (current.IsGearSwitchPoint || current.ArcLength <= previous.ArcLength + Tolerance) { reason = "粗路径普通点必须有正弧长增量且不得标记为换向点。"; return false; } } return true; } private static bool ValidateSegments( IReadOnlyList path, IReadOnlyList segments, out string reason) { reason = string.Empty; int expectedStartIndex = 0; for (int segmentIndex = 0; segmentIndex < segments.Count; segmentIndex++) { PathSegment segment = segments[segmentIndex]; if (segment == null || segment.SegmentIndex != segmentIndex || segment.StartIndex != expectedStartIndex || segment.StartIndex < 0 || segment.EndIndex < segment.StartIndex || segment.EndIndex >= path.Count || segment.StartsAtGearSwitch != path[segment.StartIndex].IsGearSwitchPoint) { reason = "粗路径方向分段索引或起始换向标记无效。"; return false; } for (int pointIndex = segment.StartIndex; pointIndex <= segment.EndIndex; pointIndex++) { if (path[pointIndex].Direction != segment.Direction) { reason = "粗路径方向分段包含不同方向的点。"; return false; } } bool hasNextSegment = segmentIndex + 1 < segments.Count; bool expectedEndsAtGearSwitch = hasNextSegment && segment.EndIndex + 1 < path.Count && path[segment.EndIndex + 1].IsGearSwitchPoint; if (segment.EndsAtGearSwitch != expectedEndsAtGearSwitch) { reason = "粗路径方向分段末尾换向标记无效。"; return false; } expectedStartIndex = segment.EndIndex + 1; } if (expectedStartIndex != path.Count) { reason = "粗路径方向分段未完整覆盖全部点。"; return false; } return true; } private static bool IsValidPoint(CoarsePathPoint point) { return point != null && NumericGuard.IsFinite(point.X) && NumericGuard.IsFinite(point.Y) && NumericGuard.IsFinite(point.Heading) && NumericGuard.IsFinite(point.UnwrappedHeading) && NumericGuard.IsFinite(point.ArcLength) && point.ArcLength >= 0d && NumericGuard.IsFinite(point.VehicleCurvature) && NumericGuard.IsFinite(point.BodyClearance) && point.BodyClearance >= 0d && (point.Direction == TravelDirection.Forward || point.Direction == TravelDirection.Reverse) && Enum.IsDefined(typeof(CoarsePathPointSource), point.Source) && Math.Abs(AngleMath.ShortestSignedDifference(point.Heading, AngleMath.NormalizeRadians(point.Heading))) <= Tolerance; } }