267 lines
11 KiB
C#
267 lines
11 KiB
C#
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;
|
|
|
|
/// <summary>正式单算法路径平滑入口,负责输入校验、有限重试与经过复核的粗路径回退。</summary>
|
|
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();
|
|
|
|
/// <summary>执行一次经过完整安全复核的单算法平滑。</summary>
|
|
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<SmoothedPathPoint> fallbackPath,
|
|
out IReadOnlyList<SmoothedPathSegment> 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<SmoothedPathPoint> fallbackPath,
|
|
out IReadOnlyList<SmoothedPathSegment> 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<SmoothedPathPoint> ToFallbackPoints(IReadOnlyList<SmoothedPathPoint> path)
|
|
{
|
|
var points = new List<SmoothedPathPoint>(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<double> 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));
|
|
}
|
|
}
|