using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using MultiWheelC.TrajectoryPlanning.CoarsePath; using MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Algorithms; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing; using MultiWheelC.TrajectoryPlanning.PathSmoothing.Validation; using MultiWheelC.TrajectoryPlanning.Utils; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Facade; /// 正式单算法路径平滑入口,负责输入校验、有限重试与经过复核的粗路径回退。 public sealed class PathSmoothingService { private readonly PathSmoothingPreprocessor _preprocessor = new PathSmoothingPreprocessor(); private readonly PathGeometryAnalyzer _analyzer = new PathGeometryAnalyzer(); private readonly SmoothingAlgorithmRunner _runner = new SmoothingAlgorithmRunner(); private readonly SmoothedPathValidator _validator = new SmoothedPathValidator(); private readonly IPathSmoother _bSpline = new CubicBSplineSmoother(); private readonly IPathSmoother _bezier = new LocalCubicBezierSmoother(); private readonly IPathSmoother _quintic = new PiecewiseQuinticSmoother(); /// 执行一次经过完整安全复核的单算法平滑。 public PathSmoothingResult Smooth( PathSmoothingRequest request, CancellationToken cancellationToken = default) { var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); if (!TryValidateRequest(request, out PathSmoothingConfiguration configuration, out string reason)) return Failure(PathSmoothingStatus.InvalidInput, stopwatch, 0, 0d, reason); if (!_preprocessor.TryPrepare(request, out PreparedPath preparedPath, out reason)) return Failure(PathSmoothingStatus.InvalidInput, stopwatch, 0, 0d, reason); if (!RawPathBaselineBuilder.TryCreate( request, preparedPath, _analyzer, configuration.OutputSpacingMeters, _validator, configuration.MaximumCollisionCheckStepMeters, out _, out reason)) { return Failure(PathSmoothingStatus.InvalidInput, stopwatch, 0, 0d, reason); } cancellationToken.ThrowIfCancellationRequested(); var input = new SmoothingAlgorithmInput( preparedPath, request.Map, request.Vehicle, configuration.MaximumCollisionCheckStepMeters, configuration.MinimumClearanceReserveMeters, new SmoothingOptionsSnapshot(configuration)); SmoothingAlgorithmRunner.AlgorithmRunResult runResult = _runner.Run( Resolve(configuration.Method), input, configuration, cancellationToken); int retryCount = GetRetryCount(runResult.AttemptedStrengths); PathSmoothingDiagnostics diagnostics = new PathSmoothingDiagnostics( runResult.Metrics, stopwatch.Elapsed, retryCount, runResult.AcceptedStrength, runResult.Reason); if (runResult.Status == PathSmoothingStatus.Success) { return PathSmoothingResult.Success( configuration.Method, runResult.Path, runResult.Segments, diagnostics); } if (!configuration.AllowFallbackToCoarsePath) return PathSmoothingResult.Failure(runResult.Status, diagnostics); cancellationToken.ThrowIfCancellationRequested(); if (!TryCreateVerifiedFallback( request, configuration, cancellationToken, out IReadOnlyList fallbackPath, out IReadOnlyList fallbackSegments, out PathQualityMetrics fallbackMetrics, out reason)) { return PathSmoothingResult.Failure(runResult.Status, diagnostics); } var fallbackDiagnostics = new PathSmoothingDiagnostics( fallbackMetrics, stopwatch.Elapsed, retryCount, runResult.AcceptedStrength, runResult.Reason); return PathSmoothingResult.Fallback( configuration.Method, fallbackPath, fallbackSegments, fallbackDiagnostics); } catch (OperationCanceledException) { return Failure(PathSmoothingStatus.Cancelled, stopwatch, 0, 0d, "路径平滑已取消。"); } catch (Exception exception) { return Failure(PathSmoothingStatus.Failed, stopwatch, 0, 0d, exception.Message); } } private bool TryCreateVerifiedFallback( PathSmoothingRequest request, PathSmoothingConfiguration configuration, CancellationToken cancellationToken, out IReadOnlyList fallbackPath, out IReadOnlyList fallbackSegments, out PathQualityMetrics fallbackMetrics, out string reason) { fallbackPath = null; fallbackSegments = null; fallbackMetrics = null; reason = string.Empty; // Reprepare from the immutable request instead of reusing the algorithm input: fallback is a // separately published output and must repeat the coarse-path contract validation. if (!_preprocessor.TryPrepare(request, out PreparedPath revalidatedPath, out reason)) return false; cancellationToken.ThrowIfCancellationRequested(); if (!RawPathBaselineBuilder.TryCreate( request, revalidatedPath, _analyzer, configuration.OutputSpacingMeters, _validator, configuration.MaximumCollisionCheckStepMeters, out RawPathBaseline rawPath, out reason)) { return false; } fallbackPath = ToFallbackPoints(rawPath.Path); fallbackSegments = rawPath.Segments; fallbackMetrics = rawPath.Metrics; return true; } private IPathSmoother Resolve(SmoothingMethod method) { return method switch { SmoothingMethod.CubicBSpline => _bSpline, SmoothingMethod.LocalCubicBezier => _bezier, SmoothingMethod.PiecewiseQuintic => _quintic, _ => throw new ArgumentOutOfRangeException(nameof(method)), }; } private static bool TryValidateRequest( PathSmoothingRequest request, out PathSmoothingConfiguration configuration, out string reason) { configuration = null; reason = string.Empty; if (request == null) { reason = "平滑请求为空。"; return false; } configuration = request.Configuration; VehicleParameters vehicle = request.Vehicle; if (configuration == null || request.Map == null || !request.Map.PlanningReady || vehicle == null) { reason = "平滑请求缺少可用的地图、车辆或配置。"; return false; } if (!NumericGuard.IsPositiveFinite(vehicle.LengthMeters) || !NumericGuard.IsPositiveFinite(vehicle.WidthMeters) || !NumericGuard.IsFinite(vehicle.SafetyMarginMeters) || vehicle.SafetyMarginMeters < 0d || !VehicleKinematics.TryGetMaximumCurvaturePerMeter(vehicle, out _)) { reason = "平滑请求中的车辆几何或曲率约束无效。"; return false; } if (!Enum.IsDefined(typeof(SmoothingMethod), configuration.Method)) { reason = "平滑方法无效。"; return false; } if (!NumericGuard.IsPositiveFinite(configuration.OutputSpacingMeters) || !NumericGuard.IsPositiveFinite(configuration.MaximumCollisionCheckStepMeters) || !NumericGuard.IsFinite(configuration.MinimumClearanceReserveMeters) || configuration.MinimumClearanceReserveMeters < 0d || !NumericGuard.IsPositiveFinite(configuration.SmoothingStrength) || !NumericGuard.IsPositiveFinite(configuration.CubicBSpline.EndpointTangentScale) || !IsValidBezierThreshold(configuration.LocalCubicBezier.CornerHeadingThresholdRadians) || !NumericGuard.IsPositiveFinite(configuration.LocalCubicBezier.MaximumWindowLengthMeters) || !NumericGuard.IsPositiveFinite(configuration.LocalCubicBezier.HandleLengthRatio) || !NumericGuard.IsPositiveFinite(configuration.PiecewiseQuintic.KnotSpacingMeters) || !NumericGuard.IsPositiveFinite(configuration.PiecewiseQuintic.MinimumKnotSpacingMeters) || configuration.PiecewiseQuintic.KnotSpacingMeters < configuration.PiecewiseQuintic.MinimumKnotSpacingMeters) { reason = "平滑配置包含非法数值或不满足方法契约。"; return false; } return true; } private static bool IsValidBezierThreshold(double thresholdRadians) { return NumericGuard.IsFinite(thresholdRadians) && thresholdRadians > 0d && thresholdRadians <= Math.PI; } private static IReadOnlyList ToFallbackPoints(IReadOnlyList path) { var points = new List(path.Count); for (int index = 0; index < path.Count; index++) { SmoothedPathPoint point = path[index]; points.Add(new SmoothedPathPoint( point.X, point.Y, point.Heading, point.UnwrappedHeading, point.ArcLength, point.Direction, point.GeometricCurvature, point.VehicleCurvature, point.VehicleCurvatureDerivative, point.BodyClearance, point.IsGearSwitchPoint, SmoothedPathPointSource.CoarsePathFallback)); } return points; } private static int GetRetryCount(IReadOnlyList attemptedStrengths) { return attemptedStrengths == null || attemptedStrengths.Count == 0 ? 0 : attemptedStrengths.Count - 1; } private static PathSmoothingResult Failure( PathSmoothingStatus status, Stopwatch stopwatch, int retryCount, double acceptedStrength, string reason) { return PathSmoothingResult.Failure( status, new PathSmoothingDiagnostics(new PathQualityMetrics(), stopwatch.Elapsed, retryCount, acceptedStrength, reason)); } }