using SimpleCore; using SimpleCore.PropType; using StandardScene.Magnetic.Protocol; using System; using System.Collections.Generic; using System.Linq; namespace StandardScene.Magnetic.Tasking { /// /// 将站点路径与站点/边 fields 合并为 FASS 2.0 0xB1 节点序列。 /// 对齐 backend CarRequestService.GetSendNodes 的组包规则。 /// public static class Fass2TaskBuilder { public const byte StartStopPass = 1; public const byte StartStopStop = 2; public const byte StartStopPrecision = 22; public static Fass2TaskPlan BuildPath(int startSiteId, int goalSiteId, Fass2TaskBuildOptions options, Func resolveNodeId) { if (resolveNodeId == null) { throw new ArgumentNullException(nameof(resolveNodeId)); } options ??= new Fass2TaskBuildOptions(); var siteIds = Fass2PathFinder.GetSitesBetween(startSiteId, goalSiteId); if (siteIds.Count == 0) { throw new InvalidOperationException($"no path from site {startSiteId} to {goalSiteId}"); } var nodes = BuildNodesForSites(siteIds, options, resolveNodeId); return new Fass2TaskPlan { StartSiteId = startSiteId, GoalSiteId = goalSiteId, SiteIds = siteIds, Nodes = nodes, Batches = SplitBatches(nodes, options.MaxNodesPerFrame), FieldsSignature = Fass2SiteFieldReader.BuildFieldsSignature(siteIds, 0, SimpleLib.GetSite) }; } public static Fass2NodeMessage[] BuildSegment(int srcSiteId, int dstSiteId, Fass2TaskBuildOptions options, Func resolveNodeId) { return BuildPath(srcSiteId, dstSiteId, options, resolveNodeId).Nodes.ToArray(); } public static IReadOnlyList SplitBatches(IReadOnlyList nodes, int maxPerFrame = 10) { if (nodes == null || nodes.Count == 0) { return Array.Empty(); } if (maxPerFrame <= 0 || maxPerFrame > 10) { maxPerFrame = 10; } var batches = new List(); for (var offset = 0; offset < nodes.Count; offset += maxPerFrame) { var count = Math.Min(maxPerFrame, nodes.Count - offset); var batch = new Fass2NodeMessage[count]; for (var i = 0; i < count; i++) { batch[i] = nodes[offset + i]; } batches.Add(batch); } return batches; } private static List BuildNodesForSites(IReadOnlyList siteIds, Fass2TaskBuildOptions options, Func resolveNodeId) { var nodes = new List(siteIds.Count); for (var i = 0; i < siteIds.Count; i++) { var siteId = siteIds[i]; var site = SimpleLib.GetSite(siteId); var siteAction = Fass2SiteFieldReader.Read(site); var isLast = i == siteIds.Count - 1; var prevSiteId = i > 0 ? siteIds[i - 1] : siteId; Fass2TrackMotionData trackMotion = null; Site prevSite = null; if (i > 0) { prevSite = SimpleLib.GetSite(prevSiteId); var track = FindTrack(prevSiteId, siteId); trackMotion = Fass2TrackFieldReader.Read(track); } var node = new Fass2NodeMessage { Node = resolveNodeId(siteId), StartStop = ResolveStartStop(siteAction, isLast), Distance = i > 0 ? ComputeEdgeDistance(prevSite, site) : (ushort)0, Speed = SpeedToProtocol(trackMotion?.Speed ?? options.DefaultSpeed, options.CarSpeed), Orientation = ResolveOrientation(prevSite, site, trackMotion), Byroad = siteAction.Byroad ?? trackMotion?.Byroad ?? 0, Direction = siteAction.Direction ?? trackMotion?.Direction ?? 0, Lift = siteAction.Lift ?? 0, Roll = siteAction.Roll ?? 0, Charge = siteAction.Charge ?? 0, Obstacle = siteAction.Obstacle ?? 0, Audio = siteAction.Audio ?? 0, Light = siteAction.Light ?? 0, Rest = siteAction.Rest ?? 0, Clamp = siteAction.Clamp ?? 0, Tray = siteAction.Tray ?? 0, Shutdown = siteAction.Shutdown ?? 0 }; if (siteAction.Orientation.HasValue) { node.Orientation = siteAction.Orientation.Value; } nodes.Add(node); } return nodes; } private static byte ResolveStartStop(Fass2SiteActionData siteAction, bool isLast) { if (siteAction.StartStop.HasValue) { return siteAction.StartStop.Value; } if (isLast) { return siteAction.PrecisionStop ? StartStopPrecision : StartStopStop; } return StartStopPass; } private static byte ResolveOrientation(Site src, Site dst, Fass2TrackMotionData trackMotion) { if (trackMotion?.Orientation != null) { return trackMotion.Orientation.Value; } if (src == null || dst == null) { return 0; } var angle = trackMotion != null && trackMotion.EnableCarAbsoluteDirection ? trackMotion.CarAbsoluteDirection : Math.Atan2(dst.y - src.y, dst.x - src.x) / Math.PI * 180.0; if (trackMotion?.Reverse == true) { angle += 180; } angle += trackMotion?.CarDirectionBias ?? 0; angle %= 360; if (angle < 0) { angle += 360; } return (byte)((int)Math.Round(angle / 360.0 * 256) % 256); } private static ushort ComputeEdgeDistance(Site src, Site dst) { var dx = dst.x - src.x; var dy = dst.y - src.y; var length = Math.Sqrt(dx * dx + dy * dy); return (ushort)Math.Min(ushort.MaxValue, Math.Max(1, Math.Round(length))); } private static ushort SpeedToProtocol(double trackSpeed, double carSpeed) { var value = trackSpeed <= 0 ? carSpeed : trackSpeed; var protocolSpeed = value <= 1 ? (int)Math.Round(value * 600) : (int)Math.Round(value * 10); return (ushort)Math.Max(1, Math.Min(10000, protocolSpeed)); } private static Track FindTrack(int fromSiteId, int toSiteId) { foreach (var track in SimpleLib.GetAllTracks()) { if (track.direction == 0) { if ((track.siteA == fromSiteId && track.siteB == toSiteId) || (track.siteB == fromSiteId && track.siteA == toSiteId)) { return track; } continue; } if (track.direction == 1 && track.siteA == fromSiteId && track.siteB == toSiteId) { return track; } if (track.direction == 2 && track.siteB == fromSiteId && track.siteA == toSiteId) { return track; } } return null; } } }