2026-06-28 15:04:55 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using CartActivator;
|
|
|
|
|
|
using CycleGUI;
|
|
|
|
|
|
using CycleGUI.API;
|
2026-06-28 19:09:24 +08:00
|
|
|
|
using FundamentalLib;
|
2026-06-28 15:04:55 +08:00
|
|
|
|
using Medulla.Types;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MultiWheelM;
|
|
|
|
|
|
|
|
|
|
|
|
public partial class CartDefinition
|
|
|
|
|
|
{
|
|
|
|
|
|
private bool _fleetRemoteActive;
|
|
|
|
|
|
private DateTime _fleetDiagLastStick = DateTime.MinValue;
|
|
|
|
|
|
|
2026-06-28 19:09:24 +08:00
|
|
|
|
// 写盘诊断日志(Medulla 侧)。改用 DLog 落盘:同一 topic 的日志会归到以 topic 命名的文件夹,
|
|
|
|
|
|
// 由 DLog 统一管理落点/滚动,无需自行维护文件句柄与 session 清空逻辑。
|
2026-06-28 15:04:55 +08:00
|
|
|
|
private void FleetDiag(string msg)
|
|
|
|
|
|
{
|
2026-06-28 19:09:24 +08:00
|
|
|
|
DLog.Log($"car{CarNum} {msg}", "FleetDiagMedulla");
|
2026-06-28 15:04:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ZeroFleetManualFields()
|
|
|
|
|
|
{
|
|
|
|
|
|
MultiVehicleManualEnabled = false;
|
|
|
|
|
|
MultiVehicleManualMode = 0;
|
|
|
|
|
|
MultiVehicleManualVx = 0;
|
|
|
|
|
|
MultiVehicleManualVy = 0;
|
|
|
|
|
|
MultiVehicleManualVth = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[IOObjectUtility]
|
|
|
|
|
|
public void FleetRemote()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_fleetRemoteActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
GUI.GetPanelInstancingObject(this)?.BringToFront();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_fleetRemoteActive = true;
|
|
|
|
|
|
var fleetOn = false;
|
2026-06-28 19:09:24 +08:00
|
|
|
|
// 运动模式开关:互斥由用户操作保证,代码用 if/else 优先级确保同一时刻只有一个模式生效。
|
|
|
|
|
|
var crabOn = false; // 蟹行(四轮同向平移)
|
|
|
|
|
|
var rotateOn = false; // 原地旋转(绕车队中心)
|
|
|
|
|
|
// 速度比例 0~1,作用于摇杆输出的线速度/横移/角速度。
|
2026-06-28 15:04:55 +08:00
|
|
|
|
var speedRatio = 1f;
|
|
|
|
|
|
UseGesture? manip = null;
|
|
|
|
|
|
|
2026-06-28 19:09:24 +08:00
|
|
|
|
// 0=常规(前进+转向) 1=蟹行 2=原地旋转。crab 优先于 rotate(同时打开时蟹行生效)。
|
|
|
|
|
|
int CurrentMode() => crabOn ? 1 : rotateOn ? 2 : 0;
|
|
|
|
|
|
void ApplyMode() => MultiVehicleManualMode = fleetOn ? CurrentMode() : 0;
|
|
|
|
|
|
|
2026-06-28 15:04:55 +08:00
|
|
|
|
void CleanupFleetRemote()
|
|
|
|
|
|
{
|
|
|
|
|
|
manip?.End();
|
|
|
|
|
|
manip = null;
|
|
|
|
|
|
fleetOn = false;
|
2026-06-28 19:09:24 +08:00
|
|
|
|
crabOn = false;
|
|
|
|
|
|
rotateOn = false;
|
2026-06-28 15:04:55 +08:00
|
|
|
|
ZeroFleetManualFields();
|
|
|
|
|
|
_fleetRemoteActive = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
manip = new UseGesture();
|
|
|
|
|
|
|
2026-06-28 19:09:24 +08:00
|
|
|
|
manip.AddWidget(new UseGesture.StickWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_stick",
|
|
|
|
|
|
text = "速度摇杆",
|
|
|
|
|
|
position = "37.5%+10px, 18%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 32%-10px",
|
|
|
|
|
|
bounceBack = true,
|
|
|
|
|
|
keyboard = "Up,Down,Left,Right",
|
|
|
|
|
|
joystick = "Axis0,Axis1",
|
|
|
|
|
|
OnValue = (pos, manipulating) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!fleetOn)
|
|
|
|
|
|
{
|
|
|
|
|
|
MultiVehicleManualVx = 0;
|
|
|
|
|
|
MultiVehicleManualVy = 0;
|
|
|
|
|
|
MultiVehicleManualVth = 0;
|
|
|
|
|
|
if ((DateTime.Now - _fleetDiagLastStick).TotalMilliseconds >= 200)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fleetDiagLastStick = DateTime.Now;
|
|
|
|
|
|
FleetDiag($"STICK(ignored,fleetOff) pos=({pos.X:0.00},{pos.Y:0.00}) manip={manipulating}");
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var ratio = Math.Clamp(speedRatio, 0, 1);
|
|
|
|
|
|
var px = Math.Clamp(pos.X, -1, 1);
|
|
|
|
|
|
var py = Math.Clamp(pos.Y, -1, 1);
|
|
|
|
|
|
var mode = CurrentMode();
|
|
|
|
|
|
MultiVehicleManualMode = mode;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 原地旋转:pos.X(左右) → 角速度(deg/s),绕车队中心。
|
|
|
|
|
|
MultiVehicleManualVx = 0;
|
|
|
|
|
|
MultiVehicleManualVy = 0;
|
|
|
|
|
|
MultiVehicleManualVth = px * MaxManualAngularSpeed * ratio;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (mode == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 蟹行:pos.Y → 前后向线速度,pos.X → 横向线速度(m/s)。
|
|
|
|
|
|
MultiVehicleManualVx = py * MaxManualSpeed * ratio;
|
|
|
|
|
|
MultiVehicleManualVy = px * MaxManualSpeed * ratio;
|
|
|
|
|
|
MultiVehicleManualVth = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 常规:pos.Y → 线速度(m/s),pos.X → 转向角(deg)。
|
|
|
|
|
|
MultiVehicleManualVx = py * MaxManualSpeed * ratio;
|
|
|
|
|
|
MultiVehicleManualVy = 0;
|
|
|
|
|
|
MultiVehicleManualVth = px * MaxManualAngularSpeed * ratio;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((DateTime.Now - _fleetDiagLastStick).TotalMilliseconds >= 200)
|
|
|
|
|
|
{
|
|
|
|
|
|
_fleetDiagLastStick = DateTime.Now;
|
|
|
|
|
|
FleetDiag($"STICK mode={mode} pos=({pos.X:0.00},{pos.Y:0.00}) manip={manipulating} ratio={ratio:0.00} " +
|
|
|
|
|
|
$"-> Vx={MultiVehicleManualVx:0.000} Vy={MultiVehicleManualVy:0.000} Vth={MultiVehicleManualVth:0.0} en={MultiVehicleManualEnabled}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
manip.AddWidget(new UseGesture.ToggleWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_crab",
|
|
|
|
|
|
text = "蟹行模式",
|
|
|
|
|
|
position = "12.5%+10px, 52%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 10%-10px",
|
|
|
|
|
|
OnValue = b =>
|
|
|
|
|
|
{
|
|
|
|
|
|
crabOn = b;
|
|
|
|
|
|
ApplyMode();
|
|
|
|
|
|
FleetDiag($"TOGGLE crab={b} -> mode={MultiVehicleManualMode}");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
manip.AddWidget(new UseGesture.ToggleWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_rotate",
|
|
|
|
|
|
text = "原地旋转",
|
|
|
|
|
|
position = "50%+10px, 52%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 10%-10px",
|
|
|
|
|
|
OnValue = b =>
|
|
|
|
|
|
{
|
|
|
|
|
|
rotateOn = b;
|
|
|
|
|
|
ApplyMode();
|
|
|
|
|
|
FleetDiag($"TOGGLE rotate={b} -> mode={MultiVehicleManualMode}");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-28 15:04:55 +08:00
|
|
|
|
manip.AddWidget(new UseGesture.ToggleWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_enable",
|
|
|
|
|
|
text = "车队联动",
|
2026-06-28 19:09:24 +08:00
|
|
|
|
position = "12.5%+10px, 64%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 10%-10px",
|
2026-06-28 15:04:55 +08:00
|
|
|
|
OnValue = b =>
|
|
|
|
|
|
{
|
|
|
|
|
|
fleetOn = b;
|
|
|
|
|
|
MultiVehicleManualEnabled = b;
|
2026-06-28 19:09:24 +08:00
|
|
|
|
ApplyMode();
|
2026-06-28 15:04:55 +08:00
|
|
|
|
if (!b)
|
|
|
|
|
|
ZeroFleetManualFields();
|
2026-06-28 19:09:24 +08:00
|
|
|
|
FleetDiag($"TOGGLE fleetOn={b} -> ManualEnabled={MultiVehicleManualEnabled} mode={MultiVehicleManualMode}");
|
2026-06-28 15:04:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
manip.AddWidget(new UseGesture.ThrottleWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_speed_ratio",
|
|
|
|
|
|
text = "速度比例",
|
2026-06-28 19:09:24 +08:00
|
|
|
|
position = "50%+10px, 64%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 10%-10px",
|
2026-06-28 15:04:55 +08:00
|
|
|
|
bounceBack = false,
|
|
|
|
|
|
OnValue = (val, _) => speedRatio = Math.Clamp(val, 0, 1)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
manip.AddWidget(new UseGesture.ButtonWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
name = "fleet_stop",
|
|
|
|
|
|
text = "急停/归零",
|
2026-06-28 19:09:24 +08:00
|
|
|
|
position = "31.25%+10px, 76%+10px",
|
|
|
|
|
|
size = "37.5%-10px, 10%-10px",
|
|
|
|
|
|
// OnPressed 是电平触发:每帧都会带当前按下状态回调一次(未按下时为 false)。
|
|
|
|
|
|
// 必须判断 pressed,否则会每帧无条件清零,把摇杆刚写入的速度立刻抹平 → 整车不动。
|
|
|
|
|
|
OnPressed = pressed =>
|
2026-06-28 15:04:55 +08:00
|
|
|
|
{
|
2026-06-28 19:09:24 +08:00
|
|
|
|
if (!pressed) return;
|
2026-06-28 15:04:55 +08:00
|
|
|
|
MultiVehicleManualVx = 0;
|
|
|
|
|
|
MultiVehicleManualVy = 0;
|
|
|
|
|
|
MultiVehicleManualVth = 0;
|
|
|
|
|
|
FleetDiag("BUTTON stop/zero");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
manip.ChangeState(new SetAppearance { drawGuizmo = false });
|
|
|
|
|
|
manip.Start();
|
|
|
|
|
|
|
|
|
|
|
|
GUI.PromptOrBringToFront(pb =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pb.Closing())
|
|
|
|
|
|
{
|
|
|
|
|
|
CleanupFleetRemote();
|
|
|
|
|
|
pb.Panel.Exit();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pb.Panel.TopMost(true)
|
|
|
|
|
|
.SetDefaultDocking(Panel.Docking.None)
|
|
|
|
|
|
.ShowTitle("车队联动遥控")
|
2026-06-28 19:09:24 +08:00
|
|
|
|
.InitSize(340, 190)
|
2026-06-28 15:04:55 +08:00
|
|
|
|
.InitPos(false, -32, 32, 1, 0, 1, 0);
|
|
|
|
|
|
|
|
|
|
|
|
pb.SeparatorText("状态");
|
2026-06-28 19:09:24 +08:00
|
|
|
|
var modeName = MultiVehicleManualMode == 2 ? "原地旋转" : MultiVehicleManualMode == 1 ? "蟹行" : "常规";
|
|
|
|
|
|
pb.Label($"车队联动: {MultiVehicleManualEnabled} 模式: {modeName}");
|
2026-06-28 15:04:55 +08:00
|
|
|
|
pb.Label($"速度比例: {speedRatio:0.00}");
|
2026-06-28 19:09:24 +08:00
|
|
|
|
pb.Label($"Vx={MultiVehicleManualVx:0.000} Vy={MultiVehicleManualVy:0.000} m/s, Vth={MultiVehicleManualVth:0.0}");
|
2026-06-28 15:04:55 +08:00
|
|
|
|
pb.Label($"优先级: {CartActivator.CartDefinition.currentPriority} ({CartActivator.CartDefinition.currentPriorityDesc})");
|
|
|
|
|
|
pb.Label("请勿同时打开「手动控制」面板");
|
|
|
|
|
|
pb.Panel.Repaint();
|
|
|
|
|
|
}, instancingObject: this);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|