添加单车底盘仿真平台并完善运动控制与夹臂功能
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
namespace MyParking.Simulation.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 模拟单个舵轮的转向和驱动响应,不包含真实电机物理模型。
|
||||
/// </summary>
|
||||
public sealed class VirtualSteerWheel
|
||||
{
|
||||
private const double AngleLowerLimitDegrees = -120.0;
|
||||
private const double AngleUpperLimitDegrees = 120.0;
|
||||
private const double MaximumSteeringRateDegreesPerSecond = 90.0;
|
||||
private const double MaximumDriveAccelerationMetersPerSecondSquared = 0.8;
|
||||
private const double AlignmentToleranceDegrees = 1.5;
|
||||
|
||||
public VirtualSteerWheel(
|
||||
string name,
|
||||
double xMeters,
|
||||
double yMeters)
|
||||
{
|
||||
Name = name;
|
||||
XMeters = xMeters;
|
||||
YMeters = yMeters;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public double XMeters { get; }
|
||||
|
||||
public double YMeters { get; }
|
||||
|
||||
public double TargetAngleDegrees { get; private set; }
|
||||
|
||||
public double ActualAngleDegrees { get; private set; }
|
||||
|
||||
public double TargetSpeedMetersPerSecond { get; private set; }
|
||||
|
||||
public double ActualSpeedMetersPerSecond { get; private set; }
|
||||
|
||||
public bool IsAligned =>
|
||||
Math.Abs(TargetAngleDegrees - ActualAngleDegrees) <=
|
||||
AlignmentToleranceDegrees;
|
||||
|
||||
/// <summary>
|
||||
/// 设置期望轮胎速度向量,并在机械限位内选择等价舵角。
|
||||
/// </summary>
|
||||
public bool SetVelocityVector(
|
||||
double vxMetersPerSecond,
|
||||
double vyMetersPerSecond)
|
||||
{
|
||||
var speed = Math.Sqrt(
|
||||
vxMetersPerSecond * vxMetersPerSecond +
|
||||
vyMetersPerSecond * vyMetersPerSecond);
|
||||
|
||||
if (speed < 1e-6)
|
||||
{
|
||||
TargetSpeedMetersPerSecond = 0.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
var desiredAngleDegrees =
|
||||
Math.Atan2(vyMetersPerSecond, vxMetersPerSecond) *
|
||||
180.0 / Math.PI;
|
||||
|
||||
return SetDirectionAndSpeed(
|
||||
desiredAngleDegrees,
|
||||
speed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停车时设置舵轮预对齐方向。
|
||||
/// </summary>
|
||||
public bool PrepareDirection(double targetAngleDegrees)
|
||||
{
|
||||
TargetSpeedMetersPerSecond = 0.0;
|
||||
return SetDirectionAndSpeed(
|
||||
targetAngleDegrees,
|
||||
0.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将驱动目标设置为零,并保持当前舵轮方向。
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
TargetSpeedMetersPerSecond = 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按固定转向速度和驱动加速度更新虚拟反馈。
|
||||
/// </summary>
|
||||
public void Step(double deltaTimeSeconds)
|
||||
{
|
||||
ActualAngleDegrees = MoveTowards(
|
||||
ActualAngleDegrees,
|
||||
TargetAngleDegrees,
|
||||
MaximumSteeringRateDegreesPerSecond *
|
||||
deltaTimeSeconds);
|
||||
|
||||
var allowedTargetSpeed =
|
||||
IsAligned ? TargetSpeedMetersPerSecond : 0.0;
|
||||
|
||||
ActualSpeedMetersPerSecond = MoveTowards(
|
||||
ActualSpeedMetersPerSecond,
|
||||
allowedTargetSpeed,
|
||||
MaximumDriveAccelerationMetersPerSecondSquared *
|
||||
deltaTimeSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复舵轮初始状态。
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
TargetAngleDegrees = 0.0;
|
||||
ActualAngleDegrees = 0.0;
|
||||
TargetSpeedMetersPerSecond = 0.0;
|
||||
ActualSpeedMetersPerSecond = 0.0;
|
||||
}
|
||||
|
||||
private bool SetDirectionAndSpeed(
|
||||
double desiredAngleDegrees,
|
||||
double desiredSpeedMetersPerSecond)
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
(Angle: NormalizeDegrees(desiredAngleDegrees),
|
||||
Speed: desiredSpeedMetersPerSecond),
|
||||
(Angle: NormalizeDegrees(desiredAngleDegrees + 180.0),
|
||||
Speed: -desiredSpeedMetersPerSecond),
|
||||
(Angle: NormalizeDegrees(desiredAngleDegrees - 180.0),
|
||||
Speed: -desiredSpeedMetersPerSecond)
|
||||
};
|
||||
|
||||
var validCandidates = candidates
|
||||
.Where(candidate =>
|
||||
candidate.Angle >= AngleLowerLimitDegrees &&
|
||||
candidate.Angle <= AngleUpperLimitDegrees)
|
||||
.OrderBy(candidate =>
|
||||
Math.Abs(candidate.Angle - ActualAngleDegrees))
|
||||
.ToArray();
|
||||
|
||||
if (validCandidates.Length == 0)
|
||||
{
|
||||
TargetSpeedMetersPerSecond = 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
var selected = validCandidates[0];
|
||||
TargetAngleDegrees = selected.Angle;
|
||||
TargetSpeedMetersPerSecond = selected.Speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static double MoveTowards(
|
||||
double current,
|
||||
double target,
|
||||
double maximumChange)
|
||||
{
|
||||
var difference = target - current;
|
||||
if (Math.Abs(difference) <= maximumChange)
|
||||
return target;
|
||||
|
||||
return current + Math.Sign(difference) * maximumChange;
|
||||
}
|
||||
|
||||
private static double NormalizeDegrees(double angleDegrees)
|
||||
{
|
||||
angleDegrees %= 360.0;
|
||||
if (angleDegrees >= 180.0)
|
||||
angleDegrees -= 360.0;
|
||||
if (angleDegrees < -180.0)
|
||||
angleDegrees += 360.0;
|
||||
return angleDegrees;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user