using System; using System.Collections.Generic; using System.Collections.ObjectModel; using MultiWheelC.TrajectoryPlanning.PathSmoothing; using MultiWheelC.TrajectoryPlanning.Utils; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing; /// 按单一方向段的弧长线性插值并保留精确锚点的确定性重采样器。 public sealed class ArcLengthResampler { private const double Tolerance = 1e-10d; /// 以目标间距重采样单一方向段;段首/段末精确锚点始终原样保留。 /// 同一方向段内按严格递增局部弧长排序的点集合;位置和弧长单位为 m,航向为 rad。 /// 目标采样间距,单位 m,必须为有限正数。 /// 成功时为只读重采样点集合;失败时为空只读集合。 /// 失败时描述非法点、退化几何或间距;成功时为空字符串。 /// 输入属于一个有效同方向区间且可安全插值时为 ;否则为 public bool TryResample( IReadOnlyList points, double spacingMeters, out IReadOnlyList resampled, out string reason) { resampled = EmptyPoints(); reason = string.Empty; if (points == null || points.Count == 0 || !NumericGuard.IsPositiveFinite(spacingMeters)) { reason = "重采样点集或采样间距无效。"; return false; } for (int index = 0; index < points.Count; index++) { if (!IsValidPoint(points[index])) { reason = "重采样输入包含非法数值。"; return false; } if (index == 0) continue; SmoothingPoint2D previous = points[index - 1]; SmoothingPoint2D current = points[index]; if (current.ArcLength <= previous.ArcLength + Tolerance) { reason = "同一方向段的弧长必须严格增加。"; return false; } double distance = Distance(previous, current); if (!NumericGuard.IsFinite(distance) || distance <= Tolerance) { reason = "同一方向段中不允许重复位姿或数值溢出的距离。"; return false; } } if (points.Count == 1) { resampled = CopyReadOnly(points); return true; } var output = new List { points[0] }; double firstArc = points[0].ArcLength; double finalArc = points[points.Count - 1].ArcLength; int rightIndex = 1; for (double targetArc = firstArc + spacingMeters; targetArc < finalArc - Tolerance; targetArc += spacingMeters) { while (rightIndex < points.Count - 1 && points[rightIndex].ArcLength < targetArc) rightIndex++; SmoothingPoint2D left = points[rightIndex - 1]; SmoothingPoint2D right = points[rightIndex]; double ratio = (targetArc - left.ArcLength) / (right.ArcLength - left.ArcLength); double unwrappedHeading = left.UnwrappedHeading + ratio * (right.UnwrappedHeading - left.UnwrappedHeading); output.Add(new SmoothingPoint2D( left.X + ratio * (right.X - left.X), left.Y + ratio * (right.Y - left.Y), targetArc, AngleMath.NormalizeRadians(unwrappedHeading), unwrappedHeading, Math.Min(left.BodyClearance, right.BodyClearance), false, SmoothedPathPointSource.Interpolated)); } // Appending the original object, rather than interpolating at the final arc, preserves the exact endpoint. output.Add(points[points.Count - 1]); resampled = new ReadOnlyCollection(output); return true; } /// 重采样完整预处理方向段并保留段编号、方向和换向拓扑标记。 /// 待重采样的单一前进或倒车方向段。 /// 目标局部弧长间距,单位 m。 /// 成功时为新不可变预处理方向段;失败时为 。 /// 失败原因;成功时为空字符串。 /// 底层点集重采样成功时为 ;否则为 public bool TryResample( PreparedDirectionSegment segment, double spacingMeters, out PreparedDirectionSegment resampled, out string reason) { resampled = null; reason = string.Empty; if (segment == null) { reason = "待重采样方向段为空。"; return false; } if (!TryResample(segment.Points, spacingMeters, out IReadOnlyList points, out reason)) return false; resampled = new PreparedDirectionSegment( segment.SegmentIndex, segment.Direction, points, segment.StartsAtGearSwitch, segment.EndsAtGearSwitch, segment.StartVehicleCurvaturePerMeter); return true; } private static bool IsValidPoint(SmoothingPoint2D point) { return point != null && NumericGuard.IsFinite(point.X) && NumericGuard.IsFinite(point.Y) && NumericGuard.IsFinite(point.ArcLength) && NumericGuard.IsFinite(point.Heading) && NumericGuard.IsFinite(point.UnwrappedHeading) && NumericGuard.IsFinite(point.BodyClearance) && point.BodyClearance >= 0d; } private static double Distance(SmoothingPoint2D left, SmoothingPoint2D right) { double x = right.X - left.X; double y = right.Y - left.Y; return Math.Sqrt(x * x + y * y); } private static IReadOnlyList CopyReadOnly(IReadOnlyList source) { var copy = new List(source.Count); for (int index = 0; index < source.Count; index++) copy.Add(source[index]); return new ReadOnlyCollection(copy); } private static IReadOnlyList EmptyPoints() { return new ReadOnlyCollection(new List()); } }