using RobotNet.MapShares; using RobotNet.MapShares.Dtos; using RobotNet.RobotManager.Services.Planner.Space; using RobotNet.RobotShares.Dtos; using RobotNet.RobotShares.Enums; namespace RobotNet.RobotManager.Services.Traffic; public class Agent { public IRobotController Robot { get; set; } = null!; public AgentModel AgentModel { get; set; } = new(); public Guid MapId { get; set; } public int InNodeIndex { get; set; } public TrafficNodeDto InNode { get; set; } = new(); public int ReleaseNodeIndex => Nodes.IndexOf(ReleaseNode); public TrafficNodeDto ReleaseNode { get; set; } = new(); public TrafficNodeDto GoalNode { get; set; } = new(); public List Nodes { get; set; } = []; public List Edges { get; set; } = []; public List SubNodes { get; set; } = []; public List SubEdges { get; set; } = []; public List GivewayNodes { get; set; } = []; public List GivewayEdges { get; set; } = []; public TrafficSolutionState State { get; set; } public RefreshPathState RefreshPathState { get; set; } = RefreshPathState.Compeleted; public string Message { get; set; } = ""; private static double GetDistance(List nodes) { double distance = 0; for (int i = 0; i < nodes.Count - 1; i++) { distance += Math.Sqrt(Math.Pow(nodes[i].X - nodes[i + 1].X, 2) + Math.Pow(nodes[i].Y - nodes[i + 1].Y, 2)); } return distance; } public bool Checking(double trafficAvoidableNodeMax, double trafficDistanceMax) { if (State != TrafficSolutionState.GiveWay && State != TrafficSolutionState.LoopResolve && RefreshPathState != RefreshPathState.Created && RefreshPathState != RefreshPathState.Refreshing) { if (ReleaseNodeIndex != -1 && InNodeIndex <= ReleaseNodeIndex && InNodeIndex < Nodes.Count) { var releaseNodes = Nodes.GetRange(InNodeIndex + 1, ReleaseNodeIndex - InNodeIndex); if (releaseNodes.Where(n => n.IsAvoidableNode).Count() < trafficAvoidableNodeMax) return true; var distance = GetDistance([.. Nodes.GetRange(InNodeIndex, ReleaseNodeIndex - InNodeIndex + 1)]); distance -= Math.Sqrt(Math.Pow(Robot.VisualizationMsg.AgvPosition.X - Nodes[InNodeIndex].X, 2) + Math.Pow(Robot.VisualizationMsg.AgvPosition.Y - Nodes[InNodeIndex].Y, 2)); if (distance < trafficDistanceMax) return true; } } return false; } public TrafficNodeDto[] GetChekingNodes(double trafficAvoidableNodeMax, double trafficDistanceMin) { List releaseNodes = []; double distance = 0; distance -= Math.Sqrt(Math.Pow(Nodes[InNodeIndex].X - Robot.VisualizationMsg.AgvPosition.X, 2) + Math.Pow(Nodes[InNodeIndex].Y - Robot.VisualizationMsg.AgvPosition.Y, 2)); int index = InNodeIndex < ReleaseNodeIndex ? InNodeIndex + 1 : InNodeIndex; for (; index < Nodes.Count; index++) { releaseNodes.Add(Nodes[index]); if (index < Nodes.Count - 1) { var remainingNodes = Nodes.GetRange(index + 1, Nodes.Count - (index + 1)); if (!remainingNodes.Any(n => n.IsAvoidableNode)) { index = Nodes.Count; break; } } if (index > 0) distance += Math.Sqrt(Math.Pow(Nodes[index].X - Nodes[index - 1].X, 2) + Math.Pow(Nodes[index].Y - Nodes[index - 1].Y, 2)); if (distance < trafficDistanceMin || !Nodes[index].IsAvoidableNode) continue; if (releaseNodes.Where(n => n.IsAvoidableNode).Count() >= trafficAvoidableNodeMax) break; } return index > ReleaseNodeIndex ? [.. Nodes.GetRange(ReleaseNodeIndex, index - ReleaseNodeIndex + (index < Nodes.Count ? 1 : 0))] : []; } public (TrafficSolutionState state, string message) UpdateGiveWay(TrafficNodeDto[] giveWayNodes, PathPlanner pathPlanner, MapManager map) { try { if (giveWayNodes.Length < 2) return (TrafficSolutionState.UnableResolve, "Lộ trình tránh đường không hợp lệ"); if (!giveWayNodes.Any(n => !Nodes.Contains(n))) { var giveNodeIndex = Nodes.IndexOf(giveWayNodes[^1]); if (giveNodeIndex >= ReleaseNodeIndex) { ReleaseNode = giveWayNodes[^1]; State = TrafficSolutionState.Complete; return (TrafficSolutionState.Complete, ""); } } var giveWayIndex = Nodes.IndexOf(giveWayNodes[0]); var releaseNodeInGivewaysIndex = Array.FindIndex(giveWayNodes, n=> n.Id == ReleaseNode.Id); if (giveWayIndex != -1) { List subGiveWayNodes = []; List subGiveWayEdges = []; if (releaseNodeInGivewaysIndex != -1) subGiveWayNodes = giveWayNodes.ToList().GetRange(releaseNodeInGivewaysIndex, giveWayNodes.Length - releaseNodeInGivewaysIndex); else if (giveWayIndex == ReleaseNodeIndex) subGiveWayNodes = [.. giveWayNodes]; else if (giveWayIndex < ReleaseNodeIndex) { subGiveWayNodes = Nodes.GetRange(giveWayIndex + 1, ReleaseNodeIndex - giveWayIndex); subGiveWayNodes.Reverse(); subGiveWayNodes.AddRange(giveWayNodes); } else { subGiveWayNodes = Nodes.GetRange(ReleaseNodeIndex, giveWayIndex - ReleaseNodeIndex); subGiveWayNodes.AddRange(giveWayNodes); } List edges = [..map.GetEdges(MapId, [..subGiveWayNodes.Select(n => new NodeDto { Id = n.Id, Name = n.Name, X = n.X, Y = n.Y, MapId = MapId, })])]; if (edges.Count > 0) { double releaseTheta; if (ReleaseNodeIndex > 0) { var (nearNodeX, nearNodeY) = MapEditorHelper.Curve(0.9, new EdgeCaculatorModel { TrajectoryDegree = Edges[ReleaseNodeIndex - 1].TrajectoryDegree, ControlPoint1X = Edges[ReleaseNodeIndex - 1].ControlPoint1X, ControlPoint1Y = Edges[ReleaseNodeIndex - 1].ControlPoint1Y, ControlPoint2X = Edges[ReleaseNodeIndex - 1].ControlPoint2X, ControlPoint2Y = Edges[ReleaseNodeIndex - 1].ControlPoint2Y, X1 = Nodes[ReleaseNodeIndex - 1].X, Y1 = Nodes[ReleaseNodeIndex - 1].Y, X2 = Nodes[ReleaseNodeIndex].X, Y2 = Nodes[ReleaseNodeIndex].Y, }); var relaseForward = Math.Atan2(Nodes[ReleaseNodeIndex].Y - nearNodeY, Nodes[ReleaseNodeIndex].X - nearNodeX) * 180 / Math.PI; var releaseBackward = Math.Atan2(nearNodeY - Nodes[ReleaseNodeIndex].Y, nearNodeX - Nodes[ReleaseNodeIndex].X) * 180 / Math.PI; releaseTheta = Nodes[ReleaseNodeIndex - 1].Direction == RobotDirection.FORWARD ? relaseForward : releaseBackward; } else { var (nearNodeX, nearNodeY) = MapEditorHelper.Curve(0.1, new EdgeCaculatorModel { TrajectoryDegree = Edges[ReleaseNodeIndex].TrajectoryDegree, ControlPoint1X = Edges[ReleaseNodeIndex].ControlPoint1X, ControlPoint1Y = Edges[ReleaseNodeIndex].ControlPoint1Y, ControlPoint2X = Edges[ReleaseNodeIndex].ControlPoint2X, ControlPoint2Y = Edges[ReleaseNodeIndex].ControlPoint2Y, X1 = Nodes[ReleaseNodeIndex].X, Y1 = Nodes[ReleaseNodeIndex].Y, X2 = Nodes[ReleaseNodeIndex + 1].X, Y2 = Nodes[ReleaseNodeIndex + 1].Y, }); var releaseBackward = Math.Atan2(Nodes[ReleaseNodeIndex].Y - nearNodeY, Nodes[ReleaseNodeIndex].X - nearNodeX) * 180 / Math.PI; var relaseForward = Math.Atan2(nearNodeY - Nodes[ReleaseNodeIndex].Y, nearNodeX - Nodes[ReleaseNodeIndex].X) * 180 / Math.PI; releaseTheta = Nodes[0].Direction == RobotDirection.FORWARD ? relaseForward : releaseBackward; } List nodes = [..PathPlanner.CalculatorDirection(releaseTheta, [..subGiveWayNodes.Select(n => new NodeDto { Id = n.Id, X = n.X, Y = n.Y, Name = n.Name, MapId = MapId, })], [..edges])]; GivewayNodes = [.. nodes.Select(n => new TrafficNodeDto { Id = n.Id, X = n.X, Y = n.Y, Name = n.Name, Direction = MapCompute.GetRobotDirection(n.Direction), })]; //Console.WriteLine($"{Robot.SerialNumber} giveway: [{string.Join(",", GivewayNodes.Select(n => $"({n.Name} - {n.Direction})"))}]"); foreach (var node in GivewayNodes) { node.IsAvoidableNode = map.GetNegativeNodes(MapId, node.Id).Length > 2; if (node.IsAvoidableNode) { node.AvoidablePaths = [.. map.GetNegativePaths(MapId, new() { Id = node.Id, X = node.X, Y = node.Y, Name = node.Name }, 2) .Where(path => path.Length > 0) .Select(path => path.Select(n => new TrafficNodeDto { Id = n.Id, X = n.X, Y = n.Y, Name = n.Name }).ToArray())]; } } GivewayEdges = [.. edges.Select(n => new TrafficEdgeDto { Id = n.Id, ControlPoint1X = n.ControlPoint1X, ControlPoint1Y = n.ControlPoint1Y, ControlPoint2X = n.ControlPoint2X, ControlPoint2Y = n.ControlPoint2Y, StartNodeId = n.StartNodeId, EndNodeId = n.EndNodeId, TrajectoryDegree = n.TrajectoryDegree, })]; State = TrafficSolutionState.GiveWay; var angleForward = Math.Atan2(GivewayNodes[^1].Y - GivewayNodes[^2].Y, GivewayNodes[^1].X - GivewayNodes[^2].X) * 180 / Math.PI; var angleBackward = Math.Atan2(GivewayNodes[^2].Y - GivewayNodes[^1].Y, GivewayNodes[^2].X - GivewayNodes[^1].X) * 180 / Math.PI; RefreshPath(GivewayNodes[^1], GivewayNodes[^1].Direction == RobotDirection.FORWARD ? angleForward : angleBackward, pathPlanner, map); return (TrafficSolutionState.GiveWay, ""); } return (TrafficSolutionState.UnableResolve, $"Không tìm thấy edge yêu cầu phải tránh [{GivewayNodes[0].Name} - {GivewayNodes[1].Name}]"); } return (TrafficSolutionState.UnableResolve, $"Không tìm thấy điểm xung đột {GivewayNodes[0].Name}"); } catch (Exception ex) { return (TrafficSolutionState.UnableResolve, $"Cập nhật lộ trình tránh đường xảy ra lỗi: {ex.Message}"); } } private void RefreshPath(TrafficNodeDto currentNode, double theta, PathPlanner planner, MapManager map) { RefreshPathState = RefreshPathState.Created; var plannerTask = Task.Run(async () => { RefreshPathState = RefreshPathState.Refreshing; var path = await planner.Planning(currentNode.Id, theta, AgentModel.NavigationType, MapId, GoalNode.Id); if (!path.IsSuccess) { RefreshPathState = RefreshPathState.Error; Message = path.Message; return; } if (path.Data.Nodes is null || path.Data.Edges is null || path.Data.Edges.Length == 0 || path.Data.Nodes.Length < 2) { RefreshPathState = RefreshPathState.Error; Message = $"Đường dẫn tới đích [{GoalNode.Name} - {GoalNode.Id}] từ [{currentNode.Name} - {currentNode.Id}] không tồn tại"; return; } SubEdges = [..path.Data.Edges.Select(n => new TrafficEdgeDto() { Id = n.Id, StartNodeId = n.StartNodeId, EndNodeId = n.EndNodeId, TrajectoryDegree = n.TrajectoryDegree, ControlPoint1X = n.ControlPoint1X, ControlPoint1Y = n.ControlPoint1Y, ControlPoint2X = n.ControlPoint2X, ControlPoint2Y = n.ControlPoint2Y, })]; SubNodes = [..path.Data.Nodes.Select(n => new TrafficNodeDto() { Id = n.Id, Name = n.Name, X = n.X, Y = n.Y, Direction = MapCompute.GetRobotDirection(n.Direction) })]; foreach (var node in SubNodes) { node.IsAvoidableNode = map.GetNegativeNodes(MapId, node.Id).Length > 2; if (node.IsAvoidableNode) { node.AvoidablePaths = [.. map.GetNegativePaths(MapId, new() { Id = node.Id, X = node.X, Y = node.Y, Name = node.Name }, 2) .Where(path => path.Length > 0) .Select(path => path.Select(n => new TrafficNodeDto { Id = n.Id, X = n.X, Y = n.Y, Name = n.Name }).ToArray())]; } } //Console.WriteLine($"{Robot.SerialNumber} refresh path: [{string.Join(",", SubNodes.Select(n => $"({n.Name} - {n.Direction})"))}]"); RefreshPathState = RefreshPathState.Compeleted; }); } }