using System; using System.Collections.Generic; using MyParking.Shared; // 夹紧后只执行一次 → 建立固定布局 namespace MultiWheelC.Fleet { // 建立编队时使用的一辆成员车世界位姿快照。 public readonly struct FleetMemberPose { public FleetMemberPose( int vehicleId, Pose2D poseInWorld) { if (vehicleId <= 0) { throw new ArgumentOutOfRangeException( nameof(vehicleId), "编队成员车号必须大于零。"); } NumericGuard.EnsureFinite( poseInWorld, nameof(poseInWorld)); VehicleId = vehicleId; PoseInWorld = new Pose2D( poseInWorld.XMeters, poseInWorld.YMeters, AngleMath.NormalizeRadians( poseInWorld.YawRadians)); } public int VehicleId { get; } public Pose2D PoseInWorld { get; } } // 保存布局建立时的车队世界位姿和固定成员布局。 public readonly struct FleetLayoutCaptureResult { public FleetLayoutCaptureResult( Pose2D fleetPoseInWorld, FleetLayout layout) { NumericGuard.EnsureFinite( fleetPoseInWorld, nameof(fleetPoseInWorld)); FleetPoseInWorld = new Pose2D( fleetPoseInWorld.XMeters, fleetPoseInWorld.YMeters, AngleMath.NormalizeRadians( fleetPoseInWorld.YawRadians)); Layout = layout ?? throw new ArgumentNullException( nameof(layout)); } public Pose2D FleetPoseInWorld { get; } public FleetLayout Layout { get; } } // 根据同一世界坐标系中的成员位姿建立车队几何中心和固定布局。 public static class FleetLayoutCapture { public static FleetLayoutCaptureResult Capture( IReadOnlyList memberPoses, int leaderVehicleId) { if (memberPoses == null) { throw new ArgumentNullException( nameof(memberPoses)); } if (memberPoses.Count == 0) { throw new ArgumentException( "建立编队布局至少需要一辆成员车。", nameof(memberPoses)); } if (leaderVehicleId <= 0) { throw new ArgumentOutOfRangeException( nameof(leaderVehicleId), "主车车号必须大于零。"); } var vehicleIds = new HashSet(); var centerXMeters = 0.0; var centerYMeters = 0.0; var leaderFound = false; var leaderYawRadians = 0.0; for (var index = 0; index < memberPoses.Count; index++) { var memberPose = memberPoses[index]; if (memberPose.VehicleId <= 0) { throw new ArgumentOutOfRangeException( nameof(memberPoses), $"第{index}辆成员车的车号必须大于零。"); } NumericGuard.EnsureFinite( memberPose.PoseInWorld, $"{nameof(memberPoses)}[{index}]." + nameof(FleetMemberPose.PoseInWorld)); if (!vehicleIds.Add(memberPose.VehicleId)) { throw new ArgumentException( $"成员位姿包含重复车号{memberPose.VehicleId}。", nameof(memberPoses)); } centerXMeters += memberPose.PoseInWorld.XMeters; centerYMeters += memberPose.PoseInWorld.YMeters; if (memberPose.VehicleId == leaderVehicleId) { leaderFound = true; leaderYawRadians = memberPose.PoseInWorld.YawRadians; } } if (!leaderFound) { throw new ArgumentException( $"成员位姿中不存在主车{leaderVehicleId}。", nameof(leaderVehicleId)); } centerXMeters /= memberPoses.Count; centerYMeters /= memberPoses.Count; NumericGuard.EnsureFinite( centerXMeters, nameof(centerXMeters)); NumericGuard.EnsureFinite( centerYMeters, nameof(centerYMeters)); var fleetPoseInWorld = new Pose2D( centerXMeters, centerYMeters, AngleMath.NormalizeRadians( leaderYawRadians)); var worldPoseInFleet = FrameTransform2D.Inverse( fleetPoseInWorld); var vehicleLayouts = new VehicleLayout[memberPoses.Count]; for (var index = 0; index < memberPoses.Count; index++) { var memberPose = memberPoses[index]; var poseInFleet = FrameTransform2D.Compose( worldPoseInFleet, memberPose.PoseInWorld); vehicleLayouts[index] = new VehicleLayout( memberPose.VehicleId, poseInFleet); } return new FleetLayoutCaptureResult( fleetPoseInWorld, new FleetLayout(vehicleLayouts)); } } }