using RobotNet.MapShares; using RobotNet.MapShares.Dtos; using RobotNet.MapShares.Enums; using RobotNet.RobotManager.Services.Planner.Space; using RobotNet.RobotShares.Enums; using RobotNet.Shares; namespace RobotNet.RobotManager.Services.Planner.ForkliftV2; public class SSEAStarPathPlanner(List Nodes, List Edges) { private NodeDto? GetOnNode(double x, double y, double limitDistance, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested == true) return null; KDTree KDTree = new(Nodes); return KDTree.FindNearest(x, y, limitDistance); } //public EdgeDto? GetClosesEdge(double x, double y, double limitDistance, CancellationToken? cancellationToken = null) //{ // if (cancellationToken?.IsCancellationRequested == true) return null; // RTree RTree = new(Nodes, Edges); // return RTree.FindNearest(x, y, limitDistance); //} public EdgeDto? GetClosesEdge(double x, double y, double limitDistance, CancellationToken? cancellationToken = null) { double minDistance = double.MaxValue; EdgeDto? edgeResult = null; foreach (var edge in Edges) { if (cancellationToken is not null && cancellationToken.Value.IsCancellationRequested) return null; var startNode = Nodes.FirstOrDefault(node => node.Id == edge.StartNodeId); var endNode = Nodes.FirstOrDefault(node => node.Id == edge.EndNodeId); if (startNode is null || endNode is null) continue; var getDistance = MapCompute.DistanceToEdge(x, y, edge, startNode, endNode); if (getDistance.IsSuccess) { if (getDistance.Data < minDistance) { minDistance = getDistance.Data; edgeResult = edge; } } } if (minDistance <= limitDistance) return edgeResult; else return null; } public List GetNegativeNode(Guid nodeId, CancellationToken? cancellationToken = null) { var node = Nodes.FirstOrDefault(p => p.Id == nodeId); if (node is null) return []; var listNodesNegative = new List(); var listPaths = Edges.Where(p => p.EndNodeId == nodeId || p.StartNodeId == nodeId); foreach (var path in listPaths) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; if (path.StartNodeId == node.Id && (path.DirectionAllowed == DirectionAllowed.Both || path.DirectionAllowed == DirectionAllowed.Forward)) { var nodeAdd = Nodes.FirstOrDefault(p => p.Id == path.EndNodeId); if (nodeAdd != null) listNodesNegative.Add(nodeAdd); continue; } if (path.EndNodeId == node.Id && (path.DirectionAllowed == DirectionAllowed.Both || path.DirectionAllowed == DirectionAllowed.Backward)) { var nodeAdd = Nodes.FirstOrDefault(p => p.Id == path.StartNodeId); if (nodeAdd != null) listNodesNegative.Add(nodeAdd); continue; } } return listNodesNegative; } private double GetNegativeCost(SSEAStarNode currentNode, SSEAStarNode negativeNode) { var negativeEdges = Edges.Where(e => e.StartNodeId == currentNode.Id && e.EndNodeId == negativeNode.Id || e.StartNodeId == negativeNode.Id && e.EndNodeId == currentNode.Id).ToList(); double minDistance = double.MaxValue; foreach (var edge in negativeEdges) { 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 0; var distance = MapEditorHelper.GetEdgeLength(new() { 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, }); if (distance < minDistance) minDistance = distance; } return minDistance != double.MaxValue ? minDistance : 0; } private List GetNegativeAStarNode(SSEAStarNode nodeCurrent, NodeDto endNode, CancellationToken? cancellationToken = null) { var possiblePointNegative = new List(); if (nodeCurrent.Id == endNode.Id) return possiblePointNegative; var listNodesNegative = GetNegativeNode(nodeCurrent.Id, cancellationToken); foreach (var item in listNodesNegative) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; if (nodeCurrent.Parent is null) continue; var nodeDtoCurrent = Nodes.FirstOrDefault(n => n.Id == nodeCurrent.Id); var nodeDtoNegative = Nodes.FirstOrDefault(n => n.Id == item.Id); var nodeDtoParent = Nodes.FirstOrDefault(n => n.Id == nodeCurrent.Parent.Id); var negativeEdge = Edges.FirstOrDefault(e => e.StartNodeId == nodeCurrent.Id && e.EndNodeId == item.Id || e.EndNodeId == nodeCurrent.Id && e.StartNodeId == item.Id); var parentEdge = Edges.FirstOrDefault(e => e.StartNodeId == nodeCurrent.Id && e.EndNodeId == nodeCurrent.Parent.Id || e.EndNodeId == nodeCurrent.Id && e.StartNodeId == nodeCurrent.Parent.Id); if (nodeDtoCurrent is null || nodeDtoNegative is null || negativeEdge is null) continue; var nearNodeNevgative = MapEditorHelper.GetNearByNode(nodeDtoCurrent, nodeDtoNegative, negativeEdge, 0.01); var nearNodeParent = nodeDtoParent is not null && parentEdge is not null ? MapEditorHelper.GetNearByNode(nodeDtoCurrent, nodeDtoParent, parentEdge, 0.01) : new() { Id = nodeCurrent.Parent.Id, X = nodeCurrent.Parent.X, Y = nodeCurrent.Parent.Y, Name = nodeCurrent.Parent.Name }; var angle = MapEditorHelper.GetAngle(nodeDtoCurrent, nearNodeNevgative, nearNodeParent); RobotDirection direction = angle < 89 ? nodeCurrent.Direction == RobotDirection.FORWARD ? RobotDirection.BACKWARD : RobotDirection.FORWARD : nodeCurrent.Direction; var nodeNegative = new SSEAStarNode { Id = item.Id, X = item.X, Y = item.Y, Name = item.Name, Direction = direction, Parent = nodeCurrent }; var cost = GetNegativeCost(nodeCurrent, nodeNegative); cost = cost > 0 ? cost : Math.Sqrt(Math.Pow(nodeCurrent.X - nodeNegative.X, 2) + Math.Pow(nodeCurrent.Y - nodeNegative.Y, 2)); nodeNegative.Cost = cost + nodeCurrent.Cost + (direction == RobotDirection.BACKWARD ? cost * Math.Sqrt(2) / 2 : 0.0); var distance = Math.Abs(endNode.X - nodeNegative.X) + Math.Abs(endNode.Y - nodeNegative.Y); nodeNegative.Heuristic = distance + (direction == RobotDirection.BACKWARD ? distance : 0.0); possiblePointNegative.Add(nodeNegative); } if (nodeCurrent.NegativeNodes is not null && nodeCurrent.NegativeNodes.Count > 0) possiblePointNegative.AddRange(nodeCurrent.NegativeNodes); return possiblePointNegative; } public List Find(SSEAStarNode startNode, NodeDto endNode, RobotDirection goalDirection, CancellationToken? cancellationToken = null) { try { var activeNodes = new PriorityQueue((a, b) => a.TotalCost.CompareTo(b.TotalCost)); var visitedNodes = new HashSet(); var path = new List(); var shortestPath = new HashSet(); activeNodes.Enqueue(startNode); while (activeNodes.Count > 0 && (!cancellationToken.HasValue || !cancellationToken.Value.IsCancellationRequested)) { var checkNode = activeNodes.Dequeue(); if (checkNode.Id == endNode.Id) { if (checkNode.Direction == goalDirection) { var node = checkNode; while (node != null) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; path.Add(node); node = node.Parent; } return path; } else { var node = checkNode; while (node != null) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; shortestPath.Add(node); node = node.Parent; } } } visitedNodes.Add(checkNode); var listNodeNegative = GetNegativeAStarNode(checkNode, endNode, cancellationToken); foreach (var node in listNodeNegative) { if (visitedNodes.TryGetValue(node, out SSEAStarNode? value) && value is not null) { if (value.TotalCost > node.TotalCost || shortestPath.Any(n => n.Id == node.Id) && value.Parent is not null && value.Parent.Heuristic < checkNode.Heuristic) { visitedNodes.Remove(value); activeNodes.Enqueue(node); } continue; } var activeNode = activeNodes.Items.FirstOrDefault(n => n.Id == node.Id && n.Direction == node.Direction); if (activeNode is not null && activeNode.TotalCost > node.TotalCost) { activeNodes.Items.Remove(activeNode); activeNodes.Enqueue(node); } else { activeNodes.Enqueue(node); } } } return []; } catch { return []; } } public List Find(SSEAStarNode startNode, NodeDto endNode, CancellationToken? cancellationToken = null) { try { var activeNodes = new PriorityQueue((a, b) => a.TotalCost.CompareTo(b.TotalCost)); var visitedNodes = new HashSet(); var path = new List(); var shortestPath = new HashSet(); activeNodes.Enqueue(startNode); while (activeNodes.Count > 0 && (!cancellationToken.HasValue || !cancellationToken.Value.IsCancellationRequested)) { var checkNode = activeNodes.Dequeue(); if (checkNode.Id == endNode.Id) { if (checkNode.Parent is not null) { var nodeParentDto = Nodes.FirstOrDefault(n => n.Id == checkNode.Parent.Id); var edge = Edges.FirstOrDefault(e => e.StartNodeId == checkNode.Id && e.EndNodeId == checkNode.Parent.Id || e.EndNodeId == checkNode.Id && e.StartNodeId == checkNode.Parent.Id); if (edge is not null && nodeParentDto is not null) { var nearParent = MapEditorHelper.GetNearByNode(endNode, nodeParentDto, edge, 0.01); var nearGoalNode = new NodeDto() { X = endNode.X + Math.Cos(endNode.Theta * Math.PI / 180), Y = endNode.Y + Math.Sin(endNode.Theta * Math.PI / 180), }; var angle = MapEditorHelper.GetAngle(endNode, nearParent, nearGoalNode); RobotDirection goalDirection = angle < 89 ? RobotDirection.BACKWARD : RobotDirection.FORWARD; if (checkNode.Direction == goalDirection) { var returnNode = checkNode; while (returnNode != null) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; path.Add(returnNode); returnNode = returnNode.Parent; } return path; } } } var node = checkNode; while (node != null) { if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return []; shortestPath.Add(node); node = node.Parent; } } visitedNodes.Add(checkNode); var listNodeNegative = GetNegativeAStarNode(checkNode, endNode, cancellationToken); foreach (var node in listNodeNegative) { if (visitedNodes.TryGetValue(node, out SSEAStarNode? value) && value is not null) { if (value.TotalCost > node.TotalCost || shortestPath.Any(n => n.Id == node.Id) && value.Parent is not null && value.Parent.Heuristic < checkNode.Heuristic) { visitedNodes.Remove(value); activeNodes.Enqueue(node); } continue; } var activeNode = activeNodes.Items.FirstOrDefault(n => n.Id == node.Id && n.Direction == node.Direction); if (activeNode is not null && activeNode.TotalCost > node.TotalCost) { activeNodes.Items.Remove(activeNode); activeNodes.Enqueue(node); } else { activeNodes.Enqueue(node); } } } return []; } catch { return []; } } private SSEAStarNode GetClosesNode(NodeDto closesNode, NodeDto goal, double theta, CancellationToken? cancellationToken = null) { SSEAStarNode closesAStarNode = new() { Id = closesNode.Id, X = closesNode.X, Y = closesNode.Y, Name = closesNode.Name, }; foreach (var negativeNode in GetNegativeNode(closesAStarNode.Id, cancellationToken)) { SSEAStarNode closesAStarNodeParent = new() { Id = closesNode.Id, X = closesNode.X, Y = closesNode.Y, Name = closesNode.Name, }; var RobotNearNode = new NodeDto() { X = closesAStarNode.X + Math.Cos(theta * Math.PI / 180), Y = closesAStarNode.Y + Math.Sin(theta * Math.PI / 180), }; var angle = MapEditorHelper.GetAngle(closesNode, negativeNode, RobotNearNode); RobotDirection direction = angle < 91 ? RobotDirection.FORWARD : RobotDirection.BACKWARD; var cost = GetNegativeCost(closesAStarNode, new() { Id = negativeNode.Id, X = negativeNode.X, Y = negativeNode.Y }); cost = cost > 0 ? cost : Math.Sqrt(Math.Pow(closesAStarNode.X - negativeNode.X, 2) + Math.Pow(closesAStarNode.Y - negativeNode.Y, 2)); cost += direction == RobotDirection.BACKWARD ? cost * Math.Sqrt(2) / 2 : 0.0; closesAStarNodeParent.Direction = direction; closesAStarNode.NegativeNodes.Add(new() { Id = negativeNode.Id, X = negativeNode.X, Y = negativeNode.Y, Name = negativeNode.Name, Direction = direction, Cost = cost, Heuristic = Math.Abs(goal.X - negativeNode.X) + Math.Abs(goal.Y - negativeNode.Y), Parent = closesAStarNodeParent, }); } return closesAStarNode; } private static SSEAStarNode[] GetClosesEdge(EdgeDto closesEdge, NodeDto goal, NodeDto startNodeForward, NodeDto startNodeBackward, SSEAStarNode robotNode, double theta) { List negativeNodes = []; if (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Backward) { SSEAStarNode closesAStarNodeParent = new() { Id = robotNode.Id, X = robotNode.X, Y = robotNode.Y, Name = robotNode.Name, }; var RobotNearNode = new NodeDto() { X = robotNode.X + Math.Cos(theta * Math.PI / 180), Y = robotNode.Y + Math.Sin(theta * Math.PI / 180), }; var angle = MapEditorHelper.GetAngle(new() { X = robotNode.X, Y = robotNode.Y, Theta = theta }, startNodeForward, RobotNearNode); RobotDirection direction = angle < 91 ? RobotDirection.FORWARD : RobotDirection.BACKWARD; double cost = Math.Sqrt(Math.Pow(robotNode.X - startNodeBackward.X, 2) + Math.Pow(robotNode.Y - startNodeBackward.Y, 2)); cost += direction == RobotDirection.BACKWARD ? cost * Math.Sqrt(2) / 2 : 0.0; closesAStarNodeParent.Direction = direction; negativeNodes.Add(new() { Id = startNodeForward.Id, X = startNodeForward.X, Y = startNodeForward.Y, Name = startNodeForward.Name, Direction = direction, Cost = cost, Heuristic = Math.Abs(goal.X - startNodeForward.X) + Math.Abs(goal.Y - startNodeForward.Y), Parent = closesAStarNodeParent, }); } if (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Forward) { SSEAStarNode closesAStarNodeParent = new() { Id = robotNode.Id, X = robotNode.X, Y = robotNode.Y, Name = robotNode.Name, }; var RobotNearNode = new NodeDto() { X = robotNode.X + Math.Cos(theta * Math.PI / 180), Y = robotNode.Y + Math.Sin(theta * Math.PI / 180), }; var angle = MapEditorHelper.GetAngle(new() { X = robotNode.X, Y = robotNode.Y, Theta = theta }, startNodeBackward, RobotNearNode); RobotDirection direction = angle < 91 ? RobotDirection.FORWARD : RobotDirection.BACKWARD; double cost = Math.Sqrt(Math.Pow(robotNode.X - startNodeBackward.X, 2) + Math.Pow(robotNode.Y - startNodeBackward.Y, 2)); cost += direction == RobotDirection.BACKWARD ? cost * Math.Sqrt(2) / 2 : 0.0; closesAStarNodeParent.Direction = direction; negativeNodes.Add(new() { Id = startNodeBackward.Id, X = startNodeBackward.X, Y = startNodeBackward.Y, Name = startNodeBackward.Name, Direction = direction, Cost = cost, Heuristic = Math.Abs(goal.X - startNodeBackward.X) + Math.Abs(goal.Y - startNodeBackward.Y), Parent = closesAStarNodeParent, }); } return [.. negativeNodes]; } public MessageResult> PlanningWithFinalDirection(double x, double y, double theta, NodeDto goal, RobotDirection goalDirection, double maxDistanceToEdge, double maxDistanceToNode, CancellationToken? cancellationToken = null) { var Path = new List(); SSEAStarNode RobotNode = new() { Id = Guid.NewGuid(), X = x, Y = y, Name = "RobotCurrentNode", }; var closesNode = GetOnNode(x, y, maxDistanceToNode); if (closesNode is not null) { if (closesNode.Id == goal.Id) return new(true, "Robot đang đứng tại điểm đích") { Data = [goal] }; RobotNode = GetClosesNode(closesNode, goal, theta); } else { var closesEdge = GetClosesEdge(x, y, maxDistanceToEdge, cancellationToken); if (closesEdge is null) { return new(false, "Robot đang quá xa tuyến đường"); } var startNodeForward = Nodes.FirstOrDefault(p => p.Id == closesEdge.StartNodeId); var startNodeBackward = Nodes.FirstOrDefault(p => p.Id == closesEdge.EndNodeId); if (startNodeForward is null || startNodeBackward is null) { return new(false, "Dữ liệu lỗi: điểm chặn của edge gần nhất không tồn tại"); } if (startNodeForward.Id == goal.Id && (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Backward)) { Path.Add(startNodeBackward); Path.Add(startNodeForward); return new(true) { Data = Path }; } if (startNodeBackward.Id == goal.Id && (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Forward)) { Path.Add(startNodeForward); Path.Add(startNodeBackward); return new(true) { Data = Path }; } RobotNode.NegativeNodes.AddRange(GetClosesEdge(closesEdge, goal, startNodeForward, startNodeBackward, RobotNode, theta)); } if (RobotNode.NegativeNodes.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{x}, {y}]"); var path = Find(RobotNode, goal, goalDirection, cancellationToken); if (cancellationToken is not null && cancellationToken.Value.IsCancellationRequested) return new(false, "Yêu cầu hủy bỏ"); if (path is null || path.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{x}, {y}]"); path.Reverse(); foreach (var node in path) { if (node.Id == path.First().Id) { Path.Add(new() { Id = node.Id, Name = node.Name, X = node.X, Y = node.Y, Direction = MapCompute.GetNodeDirection(node.Direction), }); continue; } var nodedb = Nodes.FirstOrDefault(p => p.Id == node.Id); if (nodedb is null) return new(false, "Dữ liệu bản đồ có lỗi"); nodedb.Direction = MapCompute.GetNodeDirection(node.Direction); Path.Add(nodedb); } return new(true) { Data = Path }; } public MessageResult> PlanningWithGoalAngle(double x, double y, double theta, NodeDto goal, double maxDistanceToEdge, double maxDistanceToNode, CancellationToken? cancellationToken = null) { var Path = new List(); SSEAStarNode RobotNode = new() { Id = Guid.NewGuid(), X = x, Y = y, Name = "RobotCurrentNode", }; var closesNode = GetOnNode(x, y, maxDistanceToNode); if (closesNode is not null) { if (closesNode.Id == goal.Id) return new(true, "Robot đang đứng tại điểm đích") { Data = [goal] }; RobotNode = GetClosesNode(closesNode, goal, theta); } else { var closesEdge = GetClosesEdge(x, y, maxDistanceToEdge, cancellationToken); if (closesEdge is null) { return new(false, "Robot đang quá xa tuyến đường"); } var startNodeForward = Nodes.FirstOrDefault(p => p.Id == closesEdge.StartNodeId); var startNodeBackward = Nodes.FirstOrDefault(p => p.Id == closesEdge.EndNodeId); if (startNodeForward is null || startNodeBackward is null) { return new(false, "Dữ liệu lỗi: điểm chặn của edge gần nhất không tồn tại"); } if (startNodeForward.Id == goal.Id && (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Backward)) { Path.Add(startNodeBackward); Path.Add(startNodeForward); return new(true) { Data = Path }; } if (startNodeBackward.Id == goal.Id && (closesEdge.DirectionAllowed == DirectionAllowed.Both || closesEdge.DirectionAllowed == DirectionAllowed.Forward)) { Path.Add(startNodeForward); Path.Add(startNodeBackward); return new(true) { Data = Path }; } RobotNode.NegativeNodes.AddRange(GetClosesEdge(closesEdge, goal, startNodeForward, startNodeBackward, RobotNode, theta)); } if (RobotNode.NegativeNodes.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{x}, {y}]"); var path = Find(RobotNode, goal, cancellationToken); if (cancellationToken is not null && cancellationToken.Value.IsCancellationRequested) return new(false, "Yêu cầu hủy bỏ"); if (path is null || path.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{x}, {y}]"); path.Reverse(); foreach (var node in path) { if (node.Id == path.First().Id) { Path.Add(new() { Id = node.Id, Name = node.Name, X = node.X, Y = node.Y, Direction = MapCompute.GetNodeDirection(node.Direction), }); continue; } var nodedb = Nodes.FirstOrDefault(p => p.Id == node.Id); if (nodedb is null) return new(false, "Dữ liệu bản đồ có lỗi"); nodedb.Direction = MapCompute.GetNodeDirection(node.Direction); Path.Add(nodedb); } return new(true) { Data = Path }; } public MessageResult PlanningWithFinalDirection(NodeDto startNode, double theta, NodeDto goal, RobotDirection goalDirection, CancellationToken? cancellationToken = null) { var Path = new List(); SSEAStarNode RobotNode = GetClosesNode(startNode, goal, theta); var path = Find(RobotNode, goal, goalDirection, cancellationToken); if (cancellationToken is not null && cancellationToken.Value.IsCancellationRequested) return new(false, "Yêu cầu hủy bỏ"); if (path is null || path.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{startNode.Name} - {startNode.Id}]"); path.Reverse(); foreach (var node in path) { var nodedb = Nodes.FirstOrDefault(p => p.Id == node.Id); if (nodedb is null) return new(false, "Dữ liệu bản đồ có lỗi"); nodedb.Direction = MapCompute.GetNodeDirection(node.Direction); Path.Add(nodedb); } return new(true) { Data = [.. Path] }; } public MessageResult PlanningWithGoalAngle(NodeDto startNode, double theta, NodeDto goal, CancellationToken? cancellationToken = null) { var Path = new List(); SSEAStarNode RobotNode = GetClosesNode(startNode, goal, theta); var path = Find(RobotNode, goal, cancellationToken); if (cancellationToken is not null && cancellationToken.Value.IsCancellationRequested) return new(false, "Yêu cầu hủy bỏ"); if (path is null || path.Count < 1) return new(false, $"Đường đi đến {goal.Name} - {goal.Id} không tồn tại từ [{startNode.Name} - {startNode.Id}]"); path.Reverse(); foreach (var node in path) { var nodedb = Nodes.FirstOrDefault(p => p.Id == node.Id); if (nodedb is null) return new(false, "Dữ liệu bản đồ có lỗi"); nodedb.Direction = MapCompute.GetNodeDirection(node.Direction); Path.Add(nodedb); } return new(true) { Data = [.. Path] }; } }