using System; using MyParking.Shared; namespace MultiWheelC.Control.Allocation { /// /// 在对称前后GCP方向命令与车体中心刚体速度之间执行纯几何转换。 /// public static class GcpKinematics { private const double ParallelDirectionTolerance = 1e-9; private const double StopSpeedDeadbandMetersPerSecond = 1e-6; /// /// 将有符号中心速度和前后GCP方向转换为真实车体坐标系中的Twist2D。 /// public static Twist2D ToBodyTwist( GcpMotionCommand command, double controlPointRadiusMeters) { NumericGuard.EnsureFinitePositive( controlPointRadiusMeters, nameof(controlPointRadiusMeters)); if (Math.Abs(command.SpeedMetersPerSecond) <= StopSpeedDeadbandMetersPerSecond) { return Twist2D.Zero; } var frontAngleRadians = command.FrontAngleRadians; var rearAngleRadians = command.RearAngleRadians; var directionDeterminant = Math.Sin( rearAngleRadians - frontAngleRadians); if (Math.Abs(directionDeterminant) <= ParallelDirectionTolerance) { var averageDirectionRadians = Math.Atan2( Math.Sin(frontAngleRadians) + Math.Sin(rearAngleRadians), Math.Cos(frontAngleRadians) + Math.Cos(rearAngleRadians)); return new Twist2D( command.SpeedMetersPerSecond * Math.Cos(averageDirectionRadians), command.SpeedMetersPerSecond * Math.Sin(averageDirectionRadians), 0.0); } var frontCosine = Math.Cos(frontAngleRadians); var frontSine = Math.Sin(frontAngleRadians); var rearCosine = Math.Cos(rearAngleRadians); var rearSine = Math.Sin(rearAngleRadians); // 两个GCP速度方向的法线交点就是瞬时旋转中心,坐标位于真实车体系。 var rotationCenterXMeters = controlPointRadiusMeters * (frontCosine * rearSine + frontSine * rearCosine) / directionDeterminant; var rotationCenterYMeters = -2.0 * controlPointRadiusMeters * frontCosine * rearCosine / directionDeterminant; var centerRadiusMeters = Math.Sqrt( rotationCenterXMeters * rotationCenterXMeters + rotationCenterYMeters * rotationCenterYMeters); if (!NumericGuard.IsFinite(centerRadiusMeters) || centerRadiusMeters <= 0.0) { throw new InvalidOperationException( "前后GCP方向不能生成有效的车体中心旋转半径。"); } // 用有符号速度决定绕ICR的实际转向,避免倒车时把同一组GCP轴向解释成反向运动。 var requestedDirectionSign = Math.Sign(command.SpeedMetersPerSecond); var positiveAngularFrontVelocityXMetersPerSecond = rotationCenterYMeters; var positiveAngularFrontVelocityYMetersPerSecond = controlPointRadiusMeters - rotationCenterXMeters; var frontDirectionAlignment = positiveAngularFrontVelocityXMetersPerSecond * requestedDirectionSign * frontCosine + positiveAngularFrontVelocityYMetersPerSecond * requestedDirectionSign * frontSine; var omegaSign = frontDirectionAlignment >= 0.0 ? 1.0 : -1.0; var omegaRadiansPerSecond = omegaSign * Math.Abs(command.SpeedMetersPerSecond) / centerRadiusMeters; return new Twist2D( omegaRadiansPerSecond * rotationCenterYMeters, -omegaRadiansPerSecond * rotationCenterXMeters, omegaRadiansPerSecond); } } }