using System; using System.Collections.Generic; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing; using MultiWheelC.TrajectoryPlanning.Utils; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.LocalG2; /// 只替换一个方向段内局部窗口,并保持其余方向拓扑不变。 internal sealed class LocalG2PathSplicer { internal bool TryReplace( PreparedPath currentPath, LocalG2CandidateGeometry candidate, out PreparedPath replacedPath, out string reason) { replacedPath = null; reason = string.Empty; if (currentPath == null || candidate == null || candidate.SegmentIndex < 0 || candidate.SegmentIndex >= currentPath.Segments.Count || candidate.RegionPoints == null || candidate.RegionPoints.Count < 2) { reason = "局部 G2 拼接输入无效。"; return false; } PreparedDirectionSegment source = currentPath.Segments[candidate.SegmentIndex]; if (source == null || source.SegmentIndex != candidate.SegmentIndex || !PathReferenceInterpolator.TryInterpolateByArcLength(source.Points, candidate.StartArcLengthMeters, out SmoothingPoint2D start, out reason) || !PathReferenceInterpolator.TryInterpolateByArcLength(source.Points, candidate.EndArcLengthMeters, out SmoothingPoint2D end, out reason) || !SamePosition(candidate.RegionPoints[0], start) || !SamePosition(candidate.RegionPoints[candidate.RegionPoints.Count - 1], end)) { if (string.IsNullOrEmpty(reason)) reason = "局部 G2 候选端点不匹配原始窗口。"; return false; } var combined = new List(); for (int index = 0; index < source.Points.Count; index++) { SmoothingPoint2D point = source.Points[index]; if (point.ArcLength < candidate.StartArcLengthMeters) AddWithoutNonGearDuplicates(combined, point); } // Candidate endpoints are only validated within a numerical tolerance. The replacement // itself must use the exact source-window endpoints, including a possible gear marker. AddWithoutNonGearDuplicates(combined, start); for (int index = 1; index < candidate.RegionPoints.Count - 1; index++) AddWithoutNonGearDuplicates(combined, candidate.RegionPoints[index]); AddWithoutNonGearDuplicates(combined, end); for (int index = 0; index < source.Points.Count; index++) { SmoothingPoint2D point = source.Points[index]; if (point.ArcLength > candidate.EndArcLengthMeters) AddWithoutNonGearDuplicates(combined, point); } if (!TryRecalculateArcLengths(combined, out IReadOnlyList localPoints, out reason)) return false; var segments = new List(currentPath.Segments.Count); for (int index = 0; index < currentPath.Segments.Count; index++) { PreparedDirectionSegment segment = currentPath.Segments[index]; if (index != candidate.SegmentIndex) { segments.Add(segment); continue; } segments.Add(new PreparedDirectionSegment(segment.SegmentIndex, segment.Direction, localPoints, segment.StartsAtGearSwitch, segment.EndsAtGearSwitch, segment.StartVehicleCurvaturePerMeter)); } replacedPath = new PreparedPath(segments); return true; } private static bool TryRecalculateArcLengths( IReadOnlyList points, out IReadOnlyList recalculated, out string reason) { recalculated = null; reason = string.Empty; if (points == null || points.Count < 2) { reason = "局部 G2 拼接后方向段没有足够点。"; return false; } var output = new List(points.Count); double arcLength = 0d; for (int index = 0; index < points.Count; index++) { SmoothingPoint2D point = points[index]; if (!IsValid(point)) { reason = "局部 G2 拼接点包含非法数值。"; return false; } if (index > 0) { double distance = Distance(points[index - 1], point); if (!NumericGuard.IsPositiveFinite(distance)) { reason = "局部 G2 拼接后存在重复非换向点。"; return false; } arcLength += distance; } output.Add(new SmoothingPoint2D(point.X, point.Y, arcLength, point.Heading, point.UnwrappedHeading, point.BodyClearance, point.IsGearSwitchPoint, point.Source)); } recalculated = output; return true; } private static void AddWithoutNonGearDuplicates(List output, SmoothingPoint2D point) { if (output.Count == 0) { output.Add(point); return; } SmoothingPoint2D previous = output[output.Count - 1]; if (!previous.IsGearSwitchPoint && !point.IsGearSwitchPoint && SamePosition(previous, point)) return; output.Add(point); } private static bool IsValid(SmoothingPoint2D point) => point != null && NumericGuard.IsFinite(point.X) && NumericGuard.IsFinite(point.Y) && NumericGuard.IsFinite(point.Heading) && NumericGuard.IsFinite(point.UnwrappedHeading) && NumericGuard.IsFinite(point.BodyClearance) && point.BodyClearance >= 0d; private static bool SamePosition(SmoothingPoint2D left, SmoothingPoint2D right) => left != null && right != null && Distance(left, right) <= 1e-9d; private static double Distance(SmoothingPoint2D left, SmoothingPoint2D right) { double dx = right.X - left.X; double dy = right.Y - left.Y; return Math.Sqrt(dx * dx + dy * dy); } }