using System; using MultiWheelC.Control.Abstractions; namespace MultiWheelC.Control.Allocation { /// /// 将车体中心目标曲率按对称前后转向策略转换为旧版底盘的前后GCP方向。 /// public sealed class AckermannGcpAllocator { /// /// 创建使用指定GCP半间距和最大GCP转角的对称转向分配器。 /// public AckermannGcpAllocator( double controlPointRadiusMeters, double maximumGcpAngleRadians) { EnsureFinitePositive( controlPointRadiusMeters, nameof(controlPointRadiusMeters)); EnsureFinitePositive( maximumGcpAngleRadians, nameof(maximumGcpAngleRadians)); if (maximumGcpAngleRadians >= Math.PI / 2.0) { throw new ArgumentOutOfRangeException( nameof(maximumGcpAngleRadians), "最大GCP转角必须小于π/2,避免曲率换算出现奇异值。"); } ControlPointRadiusMeters = controlPointRadiusMeters; MaximumGcpAngleRadians = maximumGcpAngleRadians; } /// /// 获取车体中心到前、后GCP的距离,单位为m。 /// public double ControlPointRadiusMeters { get; } /// /// 获取前后GCP允许的最大转角绝对值,单位为rad。 /// public double MaximumGcpAngleRadians { get; } /// /// 获取当前GCP几何和转角限制允许的最大车体中心曲率,单位为1/m。 /// public double MaximumCurvaturePerMeter => Math.Tan(MaximumGcpAngleRadians) / ControlPointRadiusMeters; /// /// 将纵向命令速度和车体中心目标曲率分配为前后GCP运动命令。 /// public GcpMotionCommand Allocate( double speedMetersPerSecond, LateralControlCommand lateralCommand) { EnsureFinite( speedMetersPerSecond, nameof(speedMetersPerSecond)); var limitedCurvaturePerMeter = Clamp( lateralCommand .TargetCurvaturePerMeter, -MaximumCurvaturePerMeter, MaximumCurvaturePerMeter); var frontAngleRadians = Math.Atan( limitedCurvaturePerMeter * ControlPointRadiusMeters); var rearAngleRadians = -frontAngleRadians; return new GcpMotionCommand( speedMetersPerSecond, frontAngleRadians, rearAngleRadians); } /// /// 将数值限制在指定闭区间内。 /// private static double Clamp( double value, double minimum, double maximum) { return Math.Max( minimum, Math.Min(maximum, value)); } /// /// 检查参数是否为正有限值。 /// private static void EnsureFinitePositive( double value, string parameterName) { EnsureFinite(value, parameterName); if (value <= 0.0) { throw new ArgumentOutOfRangeException( parameterName, "GCP分配参数必须是正有限值。"); } } /// /// 检查参数或命令是否为有限值。 /// private static void EnsureFinite( double value, string parameterName) { if (double.IsNaN(value) || double.IsInfinity(value)) { throw new ArgumentOutOfRangeException( parameterName, "GCP分配参数和命令必须是有限值。"); } } } }