Skip to content

Commit 60aa8bd

Browse files
author
Peter Vaiko
committed
feat: Graph pathfinder
1 parent fd65d50 commit 60aa8bd

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
--- A pathfinder based on A* to find the shortest path in a directed graph.
2+
--- The graph is defined by its edges only, there are no nodes needed.
3+
---
4+
--- Edges are represented by polylines, and they can unidirectional or bidirectional.
5+
--- Unidirectional edges can only be entered at one end (at the first vertex of the
6+
--- polyline) and exited at the other (last vertex of the polyline)
7+
---
8+
--- Bidirectional edges can be entered at either end, and exited at the other.
9+
---
10+
--- Edges don't have to be connected, as long as the entry of another edge is close
11+
--- enough to the exit of another, the entry is a valid successor node (of the exit,
12+
--- which is the predecessor)
13+
14+
---@class GraphPathfinder : HybridAStar
15+
GraphPathfinder = CpObject(HybridAStar)
16+
17+
--- An edge of a directed graph
18+
---@class GraphPathfinder.GraphEdge : Polyline
19+
GraphPathfinder.GraphEdge = CpObject(Polyline)
20+
21+
GraphPathfinder.GraphEdge.UNIDIRECTIONAL = {}
22+
GraphPathfinder.GraphEdge.BIDIRECTIONAL = {}
23+
24+
---@param direction table GraphEdge.UNIDIRECTIONAL or GraphEdge.BIDIRECTIONAL
25+
---@param vertices table[] array of tables with x, y (Vector, Vertex, State3D or just plain {x, y}
26+
function GraphPathfinder.GraphEdge:init(direction, vertices)
27+
Polyline.init(self, vertices)
28+
self.direction = direction
29+
end
30+
31+
---@return boolean is this a bidirectional edge?
32+
function GraphPathfinder.GraphEdge:isBidirectional()
33+
return self.direction == GraphPathfinder.GraphEdge.BIDIRECTIONAL
34+
end
35+
36+
---@return Vertex[] array of vertices that can be used to enter this edge (one for
37+
--- unidirectional, two for bidirectional edges)
38+
function GraphPathfinder.GraphEdge:getEntries()
39+
if self:isBidirectional() then
40+
return { self[1], self[#self] }
41+
else
42+
return { self[1] }
43+
end
44+
end
45+
46+
---@param entry Vector
47+
---@return Vector the exit when entered through the given entry
48+
function GraphPathfinder.GraphEdge:getExit(entry)
49+
if entry == self[1] then
50+
return self[#self]
51+
else
52+
return self[1]
53+
end
54+
end
55+
56+
---@param yieldAfter number coroutine yield after so many iterations (number of iterations in one update loop)
57+
---@param maxIterations number maximum iterations before failing
58+
---@param range number when an edge's exit is closer than range to another edge's entry, the
59+
--- two edges are considered as connected (and thus can traverse from one to the other)
60+
---@param graph Vector[] the graph as described in the file header
61+
function GraphPathfinder:init(yieldAfter, maxIterations, range, graph)
62+
HybridAStar.init(self, { }, yieldAfter, maxIterations)
63+
self.range = range
64+
self.graph = graph
65+
self.deltaPosGoal = self.range
66+
self.deltaThetaDeg = 181
67+
self.deltaThetaGoal = math.rad(self.deltaThetaDeg)
68+
self.maxDeltaTheta = math.pi
69+
self.originalDeltaThetaGoal = self.deltaThetaGoal
70+
self.analyticSolverEnabled = false
71+
self.ignoreValidityAtStart = false
72+
end
73+
74+
function GraphPathfinder:getMotionPrimitives(turnRadius, allowReverse)
75+
return GraphPathfinder.GraphMotionPrimitives(self.range, self.graph)
76+
end
77+
78+
--- Motion primitives to use with the graph pathfinder, providing the entries
79+
--- to the next edges.
80+
---@class GraphPathfinder.GraphMotionPrimitives : HybridAStar.MotionPrimitives
81+
GraphPathfinder.GraphMotionPrimitives = CpObject(HybridAStar.MotionPrimitives)
82+
83+
---@param range number when an edge's exit is closer than range to another edge's entry, the
84+
--- two edges are considered as connected (and thus can traverse from one to the other)
85+
---@param graph Vector[] the graph as described in the file header
86+
function GraphPathfinder.GraphMotionPrimitives:init(range, graph)
87+
self.range = range
88+
self.graph = graph
89+
end
90+
91+
---@return table [{x, y, d}] array of the next possible entries, their coordinates and
92+
--- the distance to the entry + the length of the edge
93+
function GraphPathfinder.GraphMotionPrimitives:getPrimitives(node)
94+
local primitives = {}
95+
for _, edge in ipairs(self.graph) do
96+
local entries = edge:getEntries()
97+
for _, entry in ipairs(entries) do
98+
local distanceToEntry = (node - entry):length()
99+
if distanceToEntry <= self.range then
100+
local exit = edge:getExit(entry)
101+
table.insert(primitives, {x = exit.x, y = exit.y, d = edge:getLength() + distanceToEntry} )
102+
end
103+
end
104+
end
105+
return primitives
106+
end
107+
108+
---@return State3D successor for the given primitive
109+
function GraphPathfinder.GraphMotionPrimitives:createSuccessor(node, primitive, hitchLength)
110+
return State3D(primitive.x, primitive.y, 0, node.g, node, node.gear, node.steer,
111+
0, node.d + primitive.d)
112+
end
113+

0 commit comments

Comments
 (0)