using System; using System.Collections.Generic; using MultiWheelC.TrajectoryPlanning.CoarsePath; using MultiWheelC.TrajectoryPlanning.Utils; namespace MultiWheelC.TrajectoryPlanning.EMPlanner; /// Reconstructs world geometry and actual path arc length from a lateral candidate. public sealed class LateralGeometryEvaluator { public bool TryEvaluate(LateralPlanningInput input, LateralCandidate candidate, out LateralPath path, out string failureReason) { path = null; failureReason = string.Empty; if (!HasMatchingStations(input, candidate, out failureReason)) return false; try { List samples = Reconstruct(input, candidate, out failureReason); if (samples == null) return false; CalculateActualPathSAndCurvatureDerivative(samples, out failureReason); if (failureReason.Length != 0) return false; var points = new List(samples.Count); for (int index = 0; index < samples.Count; index++) { GeometrySample sample = samples[index]; double dddl = candidate.DDDL[Math.Min(index, candidate.DDDL.Count - 1)]; points.Add(new LateralPathPoint(sample.ReferenceS, sample.PathS, sample.L, sample.DL, sample.DDL, dddl, sample.X, sample.Y, sample.VehicleYaw, sample.GeometricCurvature, sample.VehicleCurvature, sample.VehicleCurvatureDerivative)); } path = new LateralPath(points, false); return true; } catch (ArgumentException exception) { failureReason = exception.Message; return false; } } private static List Reconstruct(LateralPlanningInput input, LateralCandidate candidate, out string failureReason) { failureReason = string.Empty; double minimumDenominator = input.Configuration.Frenet.MinimumFrenetDenominator; if (!IsFinite(minimumDenominator) || minimumDenominator <= 0d) { failureReason = "The minimum Frenet denominator is invalid."; return null; } double directionSign = input.ReferenceSegment.Direction == TravelDirection.Forward ? 1d : -1d; var samples = new List(candidate.ReferenceStations.Count); for (int index = 0; index < candidate.ReferenceStations.Count; index++) { FrenetReferencePoint reference = ReferencePathInterpolator.Interpolate(input.ReferenceSegment, candidate.ReferenceStations[index]); double l = candidate.L[index]; double dl = candidate.DL[index]; double ddl = candidate.DDL[index]; double denominator = 1d - reference.GeometricCurvature * l; if (!IsFinite(denominator) || denominator < minimumDenominator) { failureReason = "Frenet denominator is below the hard minimum at station " + index + "."; return null; } double travelYaw = reference.TravelYaw + Math.Atan2(dl, denominator); double vehicleYaw = input.ReferenceSegment.Direction == TravelDirection.Forward ? AngleMath.NormalizeRadians(travelYaw) : AngleMath.NormalizeRadians(travelYaw + Math.PI); double x = reference.X - l * Math.Sin(reference.TravelYaw); double y = reference.Y + l * Math.Cos(reference.TravelYaw); double geometricCurvature = CalculateGeometricCurvature(reference, l, dl, ddl, directionSign * reference.VehicleCurvatureDerivative); double vehicleCurvature = directionSign * geometricCurvature; if (!IsFinite(travelYaw) || !IsFinite(vehicleYaw) || !IsFinite(x) || !IsFinite(y) || !IsFinite(geometricCurvature) || !IsFinite(vehicleCurvature)) { failureReason = "Reconstructed lateral geometry is non-finite at station " + index + "."; return null; } samples.Add(new GeometrySample(candidate.ReferenceStations[index], l, dl, ddl, x, y, travelYaw, vehicleYaw, geometricCurvature, vehicleCurvature)); } return samples; } private static void CalculateActualPathSAndCurvatureDerivative(IReadOnlyList samples, out string failureReason) { failureReason = string.Empty; samples[0].PathS = 0d; for (int index = 1; index < samples.Count; index++) { double dx = samples[index].X - samples[index - 1].X; double dy = samples[index].Y - samples[index - 1].Y; double chord = Math.Sqrt(dx * dx + dy * dy); if (!IsFinite(chord) || chord <= 0d) { failureReason = "Reconstructed path S is not strictly increasing at station " + index + "."; return; } samples[index].PathS = samples[index - 1].PathS + chord; } for (int index = 0; index < samples.Count; index++) { int lower = index == 0 ? 0 : index - 1; int upper = index == samples.Count - 1 ? samples.Count - 1 : index + 1; double span = samples[upper].PathS - samples[lower].PathS; if (!IsFinite(span) || span <= 0d) { failureReason = "Path-S curvature derivative span is invalid at station " + index + "."; return; } double derivative = (samples[upper].VehicleCurvature - samples[lower].VehicleCurvature) / span; if (!IsFinite(derivative)) { failureReason = "Vehicle curvature derivative is non-finite at station " + index + "."; return; } samples[index].VehicleCurvatureDerivative = derivative; } } private static bool HasMatchingStations(LateralPlanningInput input, LateralCandidate candidate, out string failureReason) { failureReason = string.Empty; if (input == null || candidate == null) { failureReason = "Lateral input and candidate are required."; return false; } if (candidate.ReferenceStations.Count != input.ReferenceStations.Count) { failureReason = "Candidate station count does not match the lateral input."; return false; } for (int index = 0; index < input.ReferenceStations.Count; index++) { if (Math.Abs(candidate.ReferenceStations[index] - input.ReferenceStations[index]) > 1e-12d) { failureReason = "Candidate stations do not match the lateral input."; return false; } } return true; } internal static double CalculateGeometricCurvature(FrenetReferencePoint reference, double l, double dl, double ddl, double referenceCurvatureDerivative) { double a = 1d - reference.GeometricCurvature * l; double denominatorSquared = a * a + dl * dl; double numerator = a * a * reference.GeometricCurvature + a * ddl + referenceCurvatureDerivative * l * dl + 2d * reference.GeometricCurvature * dl * dl; return numerator / (denominatorSquared * Math.Sqrt(denominatorSquared)); } private static bool IsFinite(double value) { return !double.IsNaN(value) && !double.IsInfinity(value); } private sealed class GeometrySample { public GeometrySample(double referenceS, double l, double dl, double ddl, double x, double y, double travelYaw, double vehicleYaw, double geometricCurvature, double vehicleCurvature) { ReferenceS = referenceS; L = l; DL = dl; DDL = ddl; X = x; Y = y; TravelYaw = travelYaw; VehicleYaw = vehicleYaw; GeometricCurvature = geometricCurvature; VehicleCurvature = vehicleCurvature; } public double ReferenceS { get; } public double L { get; } public double DL { get; } public double DDL { get; } public double X { get; } public double Y { get; } public double TravelYaw { get; } public double VehicleYaw { get; } public double GeometricCurvature { get; } public double VehicleCurvature { get; } public double PathS { get; set; } public double VehicleCurvatureDerivative { get; set; } } }