using RobotNet.MapShares; using RobotNet.MapShares.Dtos; using RobotNet.MapShares.Enums; using RobotNet.RobotManager.Services.Planner; using RobotNet.RobotManager.Services.Planner.Space; using RobotNet.RobotShares.Enums; using RobotNet.Shares; using System.Collections.Generic; namespace RobotNet.RobotManager.Services; public enum PathPlanningType { None, Angle, StartDirection, EndDirection, } public class PathPlanner(IConfiguration Configuration, MapManager MapManager) { private readonly double ResolutionSplit = Configuration.GetValue("PathPlanning:ResolutionSplit", 0.1); private readonly PathPlanningType PathPlanningType = Configuration.GetValue("PathPlanning:Type", PathPlanningType.None); private readonly RobotDirection StartDirection = Configuration.GetValue("PathPlanning:StartDirection", RobotDirection.NONE); private readonly RobotDirection EndDirection = Configuration.GetValue("PathPlanning:EndDirection", RobotDirection.NONE); public async Task> Planning(double xRef, double yRef, double thetaRef, NavigationType type, Guid mapId, string nodeName) { var map = await MapManager.GetMapData(mapId); if (!map.IsSuccess) return new(false, map.Message); if (map.Data is null) return new(false, "Dữ liệu bản đồ có lỗi"); var goalNode = map.Data.Nodes.FirstOrDefault(n => n.Name == nodeName); if (goalNode is null) return new(false, "Đích đến không tồn tại"); PathPlannerManager PathPlannerManager = new(); var PathPlanner = PathPlannerManager.GetPathPlanningService(type); PathPlanner.SetData(map.Data.Nodes, map.Data.Edges); if (type == NavigationType.Forklift) return PathPlanner.PathPlanningWithAngle(xRef, yRef, thetaRef, goalNode.Id); else { return PathPlanningType switch { PathPlanningType.None => PathPlanner.PathPlanning(xRef, yRef, thetaRef, goalNode.Id), PathPlanningType.Angle => PathPlanner.PathPlanningWithAngle(xRef, yRef, thetaRef, goalNode.Id), PathPlanningType.StartDirection => PathPlanner.PathPlanningWithStartDirection(xRef, yRef, thetaRef, goalNode.Id, StartDirection), PathPlanningType.EndDirection => PathPlanner.PathPlanningWithFinalDirection(xRef, yRef, thetaRef, goalNode.Id, EndDirection), _ => PathPlanner.PathPlanning(xRef, yRef, thetaRef, goalNode.Id), }; } } public async Task> Planning(Guid startNodeId, double thetaRef, NavigationType type, Guid mapId, Guid goalId) { var map = await MapManager.GetMapData(mapId); if (!map.IsSuccess) return new(false, map.Message); if (map.Data is null) return new(false, "Dữ liệu bản đồ có lỗi"); PathPlannerManager PathPlannerManager = new(); var PathPlanner = PathPlannerManager.GetPathPlanningService(type); PathPlanner.SetData(map.Data.Nodes, map.Data.Edges); if (type == NavigationType.Forklift) return PathPlanner.PathPlanningWithAngle(startNodeId, thetaRef, goalId); else { return PathPlanningType switch { PathPlanningType.None => PathPlanner.PathPlanning(startNodeId, thetaRef, goalId), PathPlanningType.Angle => PathPlanner.PathPlanningWithAngle(startNodeId, thetaRef, goalId), PathPlanningType.StartDirection => PathPlanner.PathPlanningWithStartDirection(startNodeId, thetaRef, goalId, StartDirection), PathPlanningType.EndDirection => PathPlanner.PathPlanningWithFinalDirection(startNodeId, thetaRef, goalId, EndDirection), _ => PathPlanner.PathPlanning(startNodeId, thetaRef, goalId), }; } } public static MessageResult PathSplit(NodeDto[] nodes, EdgeDto[] edges, double resolutionSplit) { if (nodes.Length < 1 || edges.Length == 0) return new(false, "Dữ liệu không hợp lệ"); List pathSplit = []; pathSplit.Add(new() { Id = nodes[0].Id, X = nodes[0].X, Y = nodes[0].Y, Theta = nodes[0].Theta, Direction = nodes[0].Direction, Name = nodes[0].Name, Actions = "CheckingNode" }); foreach (var edge in edges) { var startNode = nodes.FirstOrDefault(n => n.Id == edge.StartNodeId); var endNode = nodes.FirstOrDefault(n => n.Id == edge.EndNodeId); if (startNode is null || endNode is null) return new(false, "Dữ liệu lỗi: Điểm đầu cuối của edge không có trong danh sách nodes"); NodeDto controlNode = new(); var EdgeCaculatorModel = new EdgeCaculatorModel() { X1 = startNode.X, Y1 = startNode.Y, X2 = endNode.X, Y2 = endNode.Y, ControlPoint1X = edge.ControlPoint1X, ControlPoint1Y = edge.ControlPoint1Y, ControlPoint2X = edge.ControlPoint2X, ControlPoint2Y = edge.ControlPoint2Y, TrajectoryDegree = edge.TrajectoryDegree, }; var length = MapEditorHelper.GetEdgeLength(EdgeCaculatorModel); if (length <= 0) continue; double step = resolutionSplit / length; for (double t = step; t <= 1 - step; t += step) { (double x, double y) = MapEditorHelper.Curve(t, EdgeCaculatorModel); pathSplit.Add(new() { Id = Guid.NewGuid(), X = x, Y = y, Theta = startNode.Theta, Direction = startNode.Direction, }); } pathSplit.Add(new() { Id = endNode.Id, X = endNode.X, Y = endNode.Y, Theta = endNode.Theta, Direction = endNode.Direction, Name = endNode.Name, Actions = "CheckingNode" }); } return new(true) { Data = [.. pathSplit] }; } public MessageResult PathSplit(NodeDto[] nodes, EdgeDto[] edges) { if (nodes.Length < 1 || edges.Length == 0) return new(false, "Dữ liệu không hợp lệ"); List pathSplit = []; pathSplit.Add(new() { Id = nodes[0].Id, X = nodes[0].X, Y = nodes[0].Y, Theta = nodes[0].Theta, Direction = nodes[0].Direction, Name = nodes[0].Name, Actions = "CheckingNode" }); foreach (var edge in edges) { var startNode = nodes.FirstOrDefault(n => n.Id == edge.StartNodeId); var endNode = nodes.FirstOrDefault(n => n.Id == edge.EndNodeId); if (startNode is null || endNode is null) return new(false, "Dữ liệu lỗi: Điểm đầu cuối của edge không có trong danh sách nodes"); NodeDto controlNode = new(); var EdgeCaculatorModel = new EdgeCaculatorModel() { X1 = startNode.X, Y1 = startNode.Y, X2 = endNode.X, Y2 = endNode.Y, ControlPoint1X = edge.ControlPoint1X, ControlPoint1Y = edge.ControlPoint1Y, ControlPoint2X = edge.ControlPoint2X, ControlPoint2Y = edge.ControlPoint2Y, TrajectoryDegree = edge.TrajectoryDegree, }; var length = MapEditorHelper.GetEdgeLength(EdgeCaculatorModel); if (length <= 0) continue; double step = ResolutionSplit / length; for (double t = step; t <= 1 - step; t += step) { (double x, double y) = MapEditorHelper.Curve(t, EdgeCaculatorModel); pathSplit.Add(new() { Id = Guid.NewGuid(), X = x, Y = y, Theta = startNode.Theta, Direction = startNode.Direction, }); } pathSplit.Add(new() { Id = endNode.Id, X = endNode.X, Y = endNode.Y, Theta = endNode.Theta, Direction = endNode.Direction, Name = endNode.Name, Actions = "CheckingNode" }); } return new(true) { Data = [.. pathSplit] }; } public static RobotDirection GetRobotStartDirection(NodeDto currentNode, NodeDto nearNode, EdgeDto edge, double robotInNodeAngle) { NodeDto NearNode = MapEditorHelper.GetNearByNode(currentNode, nearNode, edge, 0.1); var RobotNearNode = new NodeDto() { X = currentNode.X + Math.Cos(robotInNodeAngle * Math.PI / 180), Y = currentNode.Y + Math.Sin(robotInNodeAngle * Math.PI / 180), }; return MapEditorHelper.GetAngle(currentNode, NearNode, RobotNearNode) > 89 ? RobotDirection.BACKWARD : RobotDirection.FORWARD; } public static RobotDirection[] GetRobotDirectionInPath(RobotDirection currentDirection, NodeDto[] nodes, EdgeDto[] edges) { RobotDirection[] RobotDirectionInNode = new RobotDirection[nodes.Length]; if (nodes.Length > 0) RobotDirectionInNode[0] = currentDirection; if (nodes.Length > 2) { for (int i = 1; i < nodes.Length - 1; i++) { NodeDto nearLastNode = MapEditorHelper.GetNearByNode(nodes[i], nodes[i - 1], edges[i - 1], 0.1); NodeDto nearFutureNode = MapEditorHelper.GetNearByNode(nodes[i], nodes[i + 1], edges[i], 0.1); ; var angle = MapEditorHelper.GetAngle(nodes[i], nearLastNode, nearFutureNode); if (angle < 89) RobotDirectionInNode[i] = RobotDirectionInNode[i - 1] == RobotDirection.FORWARD ? RobotDirection.BACKWARD : RobotDirection.FORWARD; else RobotDirectionInNode[i] = RobotDirectionInNode[i - 1]; } } if (nodes.Length > 1) RobotDirectionInNode[^1] = RobotDirectionInNode[^2]; return RobotDirectionInNode; } public static double[] GetRobotThetaInPath(NodeDto[] nodes, EdgeDto[] edges) { if (nodes.Length < 2 || edges.Length < 1 || nodes.Length - 1 != edges.Length) return []; double[] RobotThetaInNode = new double[nodes.Length]; if (nodes.Length > 1) { for (int i = 0; i < nodes.Length - 1; i++) { NodeDto nearFutureNode = MapEditorHelper.GetNearByNode(nodes[i], nodes[i + 1], edges[i], 0.1); var angleForward = Math.Atan2(nearFutureNode.Y - nodes[i].Y, nearFutureNode.X - nodes[i].X) * 180 / Math.PI; var angleBackward = Math.Atan2(nodes[i].Y - nearFutureNode.Y, nodes[i].X - nearFutureNode.X) * 180 / Math.PI; RobotThetaInNode[i] = nodes[i].Direction == Direction.FORWARD ? angleForward : angleBackward; } RobotThetaInNode[^1] = RobotThetaInNode[^2]; } return RobotThetaInNode; } public static NodeDto[] CalculatorDirection(NodeDto[] nodes, EdgeDto[] edges) => CalculatorDirection(RobotDirection.FORWARD, nodes, edges); public static NodeDto[] CalculatorDirection(RobotDirection startDirection, NodeDto[] nodes, EdgeDto[] edges) { var directions = GetRobotDirectionInPath(startDirection, nodes, edges); NodeDto[] returnNodes = [.. nodes]; for (int i = 0; i < returnNodes.Length; i++) { returnNodes[i].Direction = MapCompute.GetNodeDirection(directions[i]); } var thetas = GetRobotThetaInPath(returnNodes, edges); for (int i = 0; i < returnNodes.Length; i++) { returnNodes[i].Theta = thetas[i]; } return returnNodes; } public static NodeDto[] CalculatorDirection(double theta, NodeDto[] nodes, EdgeDto[] edges) { if (nodes.Length < 2) return nodes; var RobotNearNode = new NodeDto() { X = nodes[0].X + Math.Cos(theta * Math.PI / 180), Y = nodes[0].Y + Math.Sin(theta * Math.PI / 180), }; nodes[0].Direction = MapEditorHelper.GetAngle(nodes[0], RobotNearNode, nodes[1]) > 89 ? MapShares.Enums.Direction.BACKWARD : MapShares.Enums.Direction.FORWARD; var directions = GetRobotDirectionInPath(MapCompute.GetRobotDirection(nodes[0].Direction), nodes, edges); NodeDto[] returnNodes = [.. nodes]; for (int i = 0; i < returnNodes.Length; i++) { returnNodes[i].Direction = MapCompute.GetNodeDirection(directions[i]); } var thetas = GetRobotThetaInPath(returnNodes, edges); for (int i = 0; i < returnNodes.Length; i++) { returnNodes[i].Theta = thetas[i]; } return returnNodes; } public static NodeDto[] CalculatorDirection(double theta, NodeDto[] nodes) { if (nodes.Length < 2) return nodes; var RobotNearNode = new NodeDto() { X = nodes[0].X + Math.Cos(theta * Math.PI / 180), Y = nodes[0].Y + Math.Sin(theta * Math.PI / 180), }; nodes[0].Direction = MapEditorHelper.GetAngle(nodes[0], RobotNearNode, nodes[1]) > 89 ? MapShares.Enums.Direction.BACKWARD : MapShares.Enums.Direction.FORWARD; for (int i = 1; i < nodes.Length - 1; i++) { if (nodes[i].X == nodes[i - 1].X && nodes[i].Y == nodes[i - 1].Y) nodes[i].Direction = nodes[i - 1].Direction; else if (nodes[i].X == nodes[i + 1].X && nodes[i].Y == nodes[i + 1].Y) nodes[i].Direction = nodes[i - 1].Direction; else { var angle = MapEditorHelper.GetAngle(nodes[i], nodes[i - 1], nodes[i + 1]); nodes[i].Direction = angle > 89 ? nodes[i - 1].Direction : (nodes[i - 1].Direction == MapShares.Enums.Direction.FORWARD ? MapShares.Enums.Direction.BACKWARD : MapShares.Enums.Direction.FORWARD); } } nodes[^1].Direction = nodes[^2].Direction; return nodes; } public async Task>> GetZones(Guid mapId, NodeDto[] nodes) { var map = await MapManager.GetMapData(mapId); if (!map.IsSuccess) return new(false, map.Message); if (map.Data is null) return new(false, "Dữ liệu bản đồ có lỗi"); Dictionary NodeInZones = []; foreach (var node in nodes) { List zones = []; foreach(var zone in map.Data.Zones) { if (MapEditorHelper.IsPointInside(node.X, node.Y, zone)) zones.Add(zone); } if (zones.Count > 0) NodeInZones.Add(node.Id, [..zones]); } return new(true, "") { Data = NodeInZones }; } public static ZoneDto[] GetZones(NodeDto[] nodes, Dictionary zones) { List returnZones = []; foreach (var node in nodes) { if (zones.TryGetValue(node.Id, out ZoneDto[]? zone) && zone is not null && zone.Length > 0) returnZones.AddRange(zone.Where(z => !returnZones.Any(zo => zo.Id == z.Id))); } return [.. returnZones]; } }