From 2d252ff3095da7078bd66d22e5ebef625ef745c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E8=96=84=E4=BA=91?= Date: Mon, 3 Aug 2026 22:49:05 +0800 Subject: [PATCH] feat: build static EM lateral corridors --- .../EMPlanner/Corridor/LateralInterval.cs | 29 ++ .../EMPlanner/Corridor/StaticCorridor.cs | 29 ++ .../Corridor/StaticCorridorBuilder.cs | 289 ++++++++++++++++++ .../CorridorChecks.cs | 182 +++++++++++ .../EMPlannerVerificationHost/Program.cs | 16 +- 5 files changed, 540 insertions(+), 5 deletions(-) create mode 100644 ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/LateralInterval.cs create mode 100644 ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridor.cs create mode 100644 ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridorBuilder.cs create mode 100644 ClumsyPilot/tests/EMPlannerVerificationHost/CorridorChecks.cs diff --git a/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/LateralInterval.cs b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/LateralInterval.cs new file mode 100644 index 0000000..47c798d --- /dev/null +++ b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/LateralInterval.cs @@ -0,0 +1,29 @@ +using System; + +namespace MultiWheelC.TrajectoryPlanning.EMPlanner; + +/// Connected free lateral interval at one exact reference-S station. +public sealed class LateralInterval +{ + public LateralInterval(double referenceS, double minimumL, double maximumL, double seedL) + { + if (!IsFinite(referenceS) || !IsFinite(minimumL) || !IsFinite(maximumL) || !IsFinite(seedL) || + minimumL > maximumL || seedL < minimumL - 1e-12d || seedL > maximumL + 1e-12d) + throw new ArgumentOutOfRangeException(nameof(referenceS)); + + ReferenceS = referenceS; + MinimumL = minimumL; + MaximumL = maximumL; + SeedL = seedL; + } + + public double ReferenceS { get; } + public double MinimumL { get; } + public double MaximumL { get; } + public double SeedL { get; } + + private static bool IsFinite(double value) + { + return !double.IsNaN(value) && !double.IsInfinity(value); + } +} diff --git a/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridor.cs b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridor.cs new file mode 100644 index 0000000..eebf384 --- /dev/null +++ b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace MultiWheelC.TrajectoryPlanning.EMPlanner; + +/// Immutable lateral hard bounds for one already-selected topological corridor. +public sealed class StaticCorridor +{ + public StaticCorridor(IReadOnlyList stations) + { + if (stations == null || stations.Count == 0) + throw new ArgumentException("A static corridor requires stations.", nameof(stations)); + + var copy = new List(stations.Count); + double previousS = double.NegativeInfinity; + for (int index = 0; index < stations.Count; index++) + { + LateralInterval station = stations[index]; + if (station == null || station.ReferenceS < previousS) + throw new ArgumentException("Static corridor stations must be non-null and sorted.", nameof(stations)); + copy.Add(station); + previousS = station.ReferenceS; + } + Stations = new ReadOnlyCollection(copy); + } + + public IReadOnlyList Stations { get; } +} diff --git a/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridorBuilder.cs b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridorBuilder.cs new file mode 100644 index 0000000..58aa867 --- /dev/null +++ b/ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/StaticCorridorBuilder.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using MultiWheelC.TrajectoryPlanning.CoarsePath; +using MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle; +using MultiWheelC.TrajectoryPlanning.Mapping; + +namespace MultiWheelC.TrajectoryPlanning.EMPlanner; + +/// Builds only the seed-connected static free-space corridor of a direction segment. +public sealed class StaticCorridorBuilder +{ + private const double Epsilon = 1e-12d; + private const double ReconstructionDenominator = 1e-12d; + + private readonly FootprintCollisionChecker _collisionChecker; + + public StaticCorridorBuilder() + : this(new FootprintCollisionChecker()) + { + } + + public StaticCorridorBuilder(FootprintCollisionChecker collisionChecker) + { + _collisionChecker = collisionChecker ?? throw new ArgumentNullException(nameof(collisionChecker)); + } + + public bool TryBuild(DirectionSegmentView segment, double startReferenceS, double endReferenceS, + IReadOnlyList seed, PlanningGridMap map, VehicleParameters vehicle, + CorridorConfiguration configuration, out StaticCorridor corridor, out string failureReason) + { + corridor = null; + failureReason = string.Empty; + if (!TryValidateInput(segment, startReferenceS, endReferenceS, map, vehicle, configuration, out failureReason)) + return false; + if (!TryReadSeeds(seed, segment, out List seeds, out failureReason)) + return false; + + var stations = new List(); + LateralInterval previous = null; + foreach (double referenceS in CreateStations(startReferenceS, endReferenceS, + configuration.LongitudinalSampleSpacingMeters)) + { + FrenetReferencePoint reference; + try + { + reference = ReferencePathInterpolator.Interpolate(segment, referenceS); + } + catch (ArgumentException) + { + failureReason = "Reference interpolation failed at S=" + referenceS + "."; + return false; + } + + double seedL = GetSeedL(seeds, referenceS); + if (seedL < -configuration.MaximumLateralOffsetMeters - Epsilon || + seedL > configuration.MaximumLateralOffsetMeters + Epsilon) + { + failureReason = "Seed lateral offset is outside configured bounds at S=" + referenceS + "."; + return false; + } + + if (!TrySelectSeedConnectedInterval(reference, seedL, map, vehicle, configuration, + out double minimumL, out double maximumL)) + { + failureReason = "Seed-connected free interval disappeared at S=" + referenceS + "."; + return false; + } + + var selected = new LateralInterval(referenceS, minimumL, maximumL, seedL); + if (previous != null && Math.Max(previous.MinimumL, selected.MinimumL) > + Math.Min(previous.MaximumL, selected.MaximumL) + Epsilon) + { + failureReason = "Seed-connected corridor loses overlap at S=" + referenceS + "."; + return false; + } + + stations.Add(selected); + previous = selected; + } + + corridor = new StaticCorridor(stations); + return true; + } + + private static bool TryValidateInput(DirectionSegmentView segment, double startReferenceS, double endReferenceS, + PlanningGridMap map, VehicleParameters vehicle, CorridorConfiguration configuration, out string failureReason) + { + failureReason = string.Empty; + if (segment == null || map == null || vehicle == null || configuration == null || !map.PlanningReady) + { + failureReason = "A planning-ready map, vehicle, segment, and corridor configuration are required."; + return false; + } + if (!IsFinite(startReferenceS) || !IsFinite(endReferenceS) || startReferenceS < 0d || + endReferenceS < startReferenceS || endReferenceS > segment.LengthMeters + Epsilon) + { + failureReason = "Requested corridor S anchors are invalid."; + return false; + } + if (!IsPositiveFinite(configuration.LongitudinalSampleSpacingMeters) || + !IsPositiveFinite(configuration.LateralSampleSpacingMeters) || + !IsFinite(configuration.MaximumLateralOffsetMeters) || configuration.MaximumLateralOffsetMeters < 0d || + !IsFinite(configuration.AdditionalClearanceReserveMeters) || configuration.AdditionalClearanceReserveMeters < 0d || + !IsPositiveFinite(configuration.MaximumCollisionCheckStepMeters)) + { + failureReason = "Corridor configuration is invalid."; + return false; + } + if (!IsPositiveFinite(vehicle.LengthMeters) || !IsPositiveFinite(vehicle.WidthMeters) || + !IsFinite(vehicle.SafetyMarginMeters) || vehicle.SafetyMarginMeters < 0d) + { + failureReason = "Vehicle footprint dimensions are invalid."; + return false; + } + return true; + } + + private static bool TryReadSeeds(IReadOnlyList input, DirectionSegmentView segment, + out List seeds, out string failureReason) + { + seeds = new List(); + failureReason = string.Empty; + if (input == null) + return true; + + for (int index = 0; index < input.Count; index++) + { + FrenetProjection projection = input[index]; + if (projection == null || projection.ReferencePoint == null || + projection.ReferenceS < -Epsilon || projection.ReferenceS > segment.LengthMeters + Epsilon || + !IsFinite(projection.LateralOffset)) + { + failureReason = "Corridor seeds must belong to the selected direction segment."; + return false; + } + seeds.Add(new SeedSample(projection.ReferenceS, projection.LateralOffset)); + } + seeds.Sort((left, right) => left.ReferenceS.CompareTo(right.ReferenceS)); + return true; + } + + private static IEnumerable CreateStations(double startReferenceS, double endReferenceS, double spacing) + { + yield return startReferenceS; + for (double candidate = startReferenceS + spacing; candidate < endReferenceS - Epsilon; candidate += spacing) + yield return candidate; + if (endReferenceS > startReferenceS + Epsilon) + yield return endReferenceS; + } + + private bool TrySelectSeedConnectedInterval(FrenetReferencePoint reference, double seedL, PlanningGridMap map, + VehicleParameters vehicle, CorridorConfiguration configuration, out double minimumL, out double maximumL) + { + minimumL = 0d; + maximumL = 0d; + List samples = CreateLateralSamples(seedL, configuration.MaximumLateralOffsetMeters, + configuration.LateralSampleSpacingMeters); + int index = 0; + while (index < samples.Count) + { + if (!IsCollisionFree(reference, samples[index], map, vehicle, configuration)) + { + index++; + continue; + } + + double groupMinimum = samples[index]; + double groupMaximum = samples[index]; + bool containsSeed = Math.Abs(samples[index] - seedL) <= Epsilon; + index++; + while (index < samples.Count && samples[index] - groupMaximum <= configuration.LateralSampleSpacingMeters + Epsilon) + { + if (!IsCollisionFree(reference, samples[index], map, vehicle, configuration)) + { + index++; + break; + } + groupMaximum = samples[index]; + containsSeed |= Math.Abs(samples[index] - seedL) <= Epsilon; + index++; + } + + if (containsSeed) + { + minimumL = groupMinimum; + maximumL = groupMaximum; + return true; + } + } + return false; + } + + private static List CreateLateralSamples(double seedL, double maximumOffset, double spacing) + { + var samples = new List(); + for (double candidate = -maximumOffset; candidate < maximumOffset - Epsilon; candidate += spacing) + AddSortedUnique(samples, candidate); + AddSortedUnique(samples, maximumOffset); + AddSortedUnique(samples, -maximumOffset); + AddSortedUnique(samples, seedL); + samples.Sort(); + return samples; + } + + private bool IsCollisionFree(FrenetReferencePoint reference, double lateralOffset, PlanningGridMap map, + VehicleParameters vehicle, CorridorConfiguration configuration) + { + if (!FrenetTransform.TryReconstruct(reference, lateralOffset, 0d, ReconstructionDenominator, out Pose2D pose)) + return false; + + if (IsObviouslyClear(pose, map, vehicle, configuration.AdditionalClearanceReserveMeters)) + return true; + return _collisionChecker.IsPoseCollisionFree(pose, map, vehicle, + configuration.AdditionalClearanceReserveMeters, out _); + } + + private static bool IsObviouslyClear(Pose2D pose, PlanningGridMap map, VehicleParameters vehicle, + double additionalClearanceReserveMeters) + { + double totalMargin = vehicle.SafetyMarginMeters + additionalClearanceReserveMeters; + double halfLength = vehicle.LengthMeters / 2d + totalMargin; + double halfWidth = vehicle.WidthMeters / 2d + totalMargin; + double radius = Math.Sqrt(halfLength * halfLength + halfWidth * halfWidth); + if (!IsFinite(radius) || map.GetConservativeObstacleDistanceMeters(pose.X, pose.Y) <= radius) + return false; + + double cosine = Math.Cos(pose.Heading); + double sine = Math.Sin(pose.Heading); + for (int longitudinalSign = -1; longitudinalSign <= 1; longitudinalSign += 2) + for (int lateralSign = -1; lateralSign <= 1; lateralSign += 2) + { + double x = pose.X + longitudinalSign * halfLength * cosine - lateralSign * halfWidth * sine; + double y = pose.Y + longitudinalSign * halfLength * sine + lateralSign * halfWidth * cosine; + if (!map.TryWorldToGrid(x, y, out _, out _)) + return false; + } + return true; + } + + private static double GetSeedL(IReadOnlyList seeds, double referenceS) + { + if (seeds.Count == 0) + return 0d; + if (referenceS <= seeds[0].ReferenceS) + return seeds[0].LateralOffset; + for (int index = 1; index < seeds.Count; index++) + { + SeedSample upper = seeds[index]; + if (referenceS <= upper.ReferenceS) + { + SeedSample lower = seeds[index - 1]; + double span = upper.ReferenceS - lower.ReferenceS; + return span <= Epsilon ? upper.LateralOffset : lower.LateralOffset + + (upper.LateralOffset - lower.LateralOffset) * (referenceS - lower.ReferenceS) / span; + } + } + return seeds[seeds.Count - 1].LateralOffset; + } + + private static void AddSortedUnique(List samples, double value) + { + for (int index = 0; index < samples.Count; index++) + if (Math.Abs(samples[index] - value) <= Epsilon) + return; + samples.Add(value); + } + + private static bool IsPositiveFinite(double value) + { + return IsFinite(value) && value > 0d; + } + + private static bool IsFinite(double value) + { + return !double.IsNaN(value) && !double.IsInfinity(value); + } + + private sealed class SeedSample + { + public SeedSample(double referenceS, double lateralOffset) + { + ReferenceS = referenceS; + LateralOffset = lateralOffset; + } + + public double ReferenceS { get; } + public double LateralOffset { get; } + } +} diff --git a/ClumsyPilot/tests/EMPlannerVerificationHost/CorridorChecks.cs b/ClumsyPilot/tests/EMPlannerVerificationHost/CorridorChecks.cs new file mode 100644 index 0000000..4d64dd7 --- /dev/null +++ b/ClumsyPilot/tests/EMPlannerVerificationHost/CorridorChecks.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using EMPlannerVerificationHost; +using MultiWheelC.TrajectoryPlanning.CoarsePath; +using MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle; +using MultiWheelC.TrajectoryPlanning.Mapping; +using MultiWheelC.TrajectoryPlanning.PathSmoothing; + +namespace MultiWheelC.TrajectoryPlanning.EMPlanner; + +internal static class CorridorChecks +{ + public static void Run() + { + VerifiesEmptyAndNarrowedCorridors(); + VerifiesSeedConnectedIntervalIsPreserved(); + VerifiesDisappearingSeedIntervalFails(); + } + + private static void VerifiesEmptyAndNarrowedCorridors() + { + DirectionSegmentView segment = CreateStraightSegment(); + CorridorConfiguration configuration = EmPlannerConfiguration.CreateDefault().Corridor; + VehicleParameters vehicle = CreateVehicle(); + var builder = new StaticCorridorBuilder(); + + Verification.True(builder.TryBuild(segment, 0.2d, 1.8d, Array.Empty(), + CreateMap(Array.Empty()), vehicle, configuration, out StaticCorridor empty, out string emptyReason), + "empty map corridor succeeds: " + emptyReason); + VerifyAnchors(empty, 0.2d, 1.8d); + for (int index = 1; index + 1 < empty.Stations.Count; index++) + { + Verification.NearlyEqual(-0.3d, empty.Stations[index].MinimumL, "empty map minimum l"); + Verification.NearlyEqual(0.3d, empty.Stations[index].MaximumL, "empty map maximum l"); + } + + IMapObstacle[] leftNarrowing = + { + new AxisAlignedRectangleObstacle(800f, 1200f, 150f, 400f), + }; + PlanningGridMap narrowedMap = CreateMap(leftNarrowing); + IReadOnlyList seed = CreateSeed(segment, -0.2d); + Verification.True(builder.TryBuild(segment, 0.2d, 1.8d, seed, narrowedMap, vehicle, configuration, + out StaticCorridor narrowed, out string narrowedReason), "narrowed corridor succeeds: " + narrowedReason); + LateralInterval narrowedStation = FindStation(narrowed, 1d); + Verification.True(narrowedStation.MaximumL < 0.3d, "left obstacle narrows positive-l side"); + VerifyAcceptedSamplesUseExactFootprints(segment, narrowed, narrowedMap, vehicle, configuration); + } + + private static void VerifiesSeedConnectedIntervalIsPreserved() + { + DirectionSegmentView segment = CreateStraightSegment(); + CorridorConfiguration configuration = EmPlannerConfiguration.CreateDefault().Corridor; + VehicleParameters vehicle = CreateVehicle(); + IMapObstacle[] split = + { + new AxisAlignedRectangleObstacle(800f, 1200f, -50f, 50f), + }; + IReadOnlyList seed = CreateSeed(segment, -0.2d); + var builder = new StaticCorridorBuilder(); + + Verification.True(builder.TryBuild(segment, 0.2d, 1.8d, seed, CreateMap(split), vehicle, configuration, + out StaticCorridor corridor, out string reason), "split corridor succeeds for left seed: " + reason); + for (int index = 0; index < corridor.Stations.Count; index++) + { + LateralInterval station = corridor.Stations[index]; + Verification.True(station.MinimumL <= station.SeedL && station.SeedL <= station.MaximumL, + "chosen interval contains seed at station " + index); + } + Verification.True(FindStation(corridor, 1d).MaximumL < 0d, + "split station retains seed-connected left interval instead of right interval"); + } + + private static void VerifiesDisappearingSeedIntervalFails() + { + DirectionSegmentView segment = CreateStraightSegment(); + CorridorConfiguration configuration = EmPlannerConfiguration.CreateDefault().Corridor; + VehicleParameters vehicle = CreateVehicle(); + IMapObstacle[] removesLeft = + { + new AxisAlignedRectangleObstacle(800f, 1200f, -400f, -50f), + }; + var builder = new StaticCorridorBuilder(); + + Verification.True(!builder.TryBuild(segment, 0.2d, 1.8d, CreateSeed(segment, -0.2d), CreateMap(removesLeft), + vehicle, configuration, out _, out string failureReason), + "disappearing seed-connected interval does not switch sides"); + Verification.True(failureReason.IndexOf("S=", StringComparison.Ordinal) >= 0, + "failure identifies the first failed reference-s station: " + failureReason); + } + + private static void VerifyAnchors(StaticCorridor corridor, double startS, double endS) + { + Verification.NearlyEqual(startS, corridor.Stations[0].ReferenceS, "first station is exact requested start"); + Verification.NearlyEqual(endS, corridor.Stations[corridor.Stations.Count - 1].ReferenceS, + "last station is exact requested end"); + } + + private static void VerifyAcceptedSamplesUseExactFootprints(DirectionSegmentView segment, StaticCorridor corridor, + PlanningGridMap map, VehicleParameters vehicle, CorridorConfiguration configuration) + { + var checker = new FootprintCollisionChecker(); + for (int stationIndex = 0; stationIndex < corridor.Stations.Count; stationIndex++) + { + LateralInterval station = corridor.Stations[stationIndex]; + FrenetReferencePoint reference = ReferencePathInterpolator.Interpolate(segment, station.ReferenceS); + for (double l = station.MinimumL; l <= station.MaximumL + 1e-12d; l += configuration.LateralSampleSpacingMeters) + { + Verification.True(FrenetTransform.TryReconstruct(reference, l, 0d, 0.2d, out Pose2D pose), + "accepted sample reconstructs"); + Verification.True(checker.IsPoseCollisionFree(pose, map, vehicle, configuration.AdditionalClearanceReserveMeters, + out _), "accepted sample passes exact rotated footprint"); + } + } + } + + private static DirectionSegmentView CreateStraightSegment() + { + var points = new List + { + Point(0d, 0d), + Point(1d, 1d), + Point(2d, 2d), + }; + return new DirectionSegmentView(0, TravelDirection.Forward, points, + new ReferenceBoundary(0, 0d, EmBoundaryType.None, 0d), + new ReferenceBoundary(0, 2d, EmBoundaryType.Goal, 2d), 0d); + } + + private static IReadOnlyList CreateSeed(DirectionSegmentView segment, double l) + { + return new FrenetProjection[] + { + new FrenetProjection(ReferencePathInterpolator.Interpolate(segment, 0.2d), l, 0d, 0d), + new FrenetProjection(ReferencePathInterpolator.Interpolate(segment, 1.8d), l, 0d, 0d), + }; + } + + private static SmoothedPathPoint Point(double x, double s) + { + return new SmoothedPathPoint(x, 0d, 0d, 0d, s, TravelDirection.Forward, 0d, 0d, 0d, 1d, + false, SmoothedPathPointSource.Anchor); + } + + private static VehicleParameters CreateVehicle() + { + return new VehicleParameters + { + LengthMeters = 0.10d, + WidthMeters = 0.10d, + SafetyMarginMeters = 0d, + MaximumCurvaturePerMeter = 1d, + }; + } + + private static PlanningGridMap CreateMap(IReadOnlyList obstacles) + { + IMapObstacleSource[] sources = obstacles.Count == 0 + ? Array.Empty() + : new IMapObstacleSource[] { new ManualObstacleSource("corridor-test", 1L, true, obstacles) }; + var result = new PlanningMapFactory().Create(new PlanningMapRequest + { + Bounds = new MapBoundsMm(-1000f, 3000f, -1000f, 1000f), + ResolutionMm = 20f, + ObstacleSources = sources, + AllowExplicitEmptyMap = obstacles.Count == 0, + }); + Verification.True(result.Succeeded && result.Map != null && result.Map.PlanningReady, + "corridor test map builds: " + result.FailureReason); + return result.Map!; + } + + private static LateralInterval FindStation(StaticCorridor corridor, double referenceS) + { + for (int index = 0; index < corridor.Stations.Count; index++) + { + if (Math.Abs(corridor.Stations[index].ReferenceS - referenceS) <= 1e-12d) + return corridor.Stations[index]; + } + throw new InvalidOperationException("Requested corridor station was not sampled."); + } +} diff --git a/ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs b/ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs index 79aaf36..2d07268 100644 --- a/ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs +++ b/ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs @@ -6,29 +6,35 @@ internal static class Program { private static int Main(string[] args) { - if (args.Length != 1 || (args[0] != "foundation" && args[0] != "segmentation" && args[0] != "frenet")) + if (args.Length != 1 || (args[0] != "foundation" && args[0] != "segmentation" && args[0] != "frenet" && + args[0] != "corridor" && args[0] != "all-foundation")) { - Console.Error.WriteLine("Usage: EMPlannerVerificationHost foundation|segmentation|frenet"); + Console.Error.WriteLine("Usage: EMPlannerVerificationHost foundation|segmentation|frenet|corridor|all-foundation"); return 2; } try { - if (args[0] == "foundation") + if (args[0] == "foundation" || args[0] == "all-foundation") { MultiWheelC.TrajectoryPlanning.EMPlanner.FoundationChecks.Run(); Console.WriteLine("PASS foundation"); } - else if (args[0] == "segmentation") + if (args[0] == "segmentation" || args[0] == "all-foundation") { MultiWheelC.TrajectoryPlanning.EMPlanner.SegmentationChecks.Run(); Console.WriteLine("PASS segmentation"); } - else + if (args[0] == "frenet" || args[0] == "all-foundation") { MultiWheelC.TrajectoryPlanning.EMPlanner.FrenetChecks.Run(); Console.WriteLine("PASS frenet"); } + if (args[0] == "corridor" || args[0] == "all-foundation") + { + MultiWheelC.TrajectoryPlanning.EMPlanner.CorridorChecks.Run(); + Console.WriteLine("PASS corridor"); + } return 0; } catch (Exception exception)