Skip to content

Commit 554c19b

Browse files
author
Peter Vaiko
committed
feat: graph pathfinder extension
Works ok for unidirectional edges. Needs more tuning.
1 parent 3ec2cd8 commit 554c19b

3 files changed

Lines changed: 84 additions & 17 deletions

File tree

scripts/geometry/Polyline.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ function Polyline:extendStart(length)
227227
return self
228228
end
229229

230-
---@param length number shorten the Polyline at the last vertex
230+
---@param length number shorten the Polyline starting at the last vertex
231231
function Polyline:cutEnd(length)
232232
if #self == 2 then
233233
local theOnlyEdge = self[1]:getExitEdge()
@@ -248,7 +248,7 @@ function Polyline:cutEnd(length)
248248
end
249249
end
250250
---
251-
---@param length number shorten the polyline at the first vertex
251+
---@param length number shorten the polyline starting at the first vertex
252252
function Polyline:cutStart(length)
253253
if #self == 2 then
254254
local theOnlyEdge = self[1]:getExitEdge()

scripts/pathfinder/GraphPathfinder.lua

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,16 @@ end
154154
---@param range number when an edge's exit is closer than range to another edge's entry, the
155155
--- two edges are considered as connected (and thus can traverse from one to the other)
156156
---@param graph GraphPathfinder.GraphEdge[] Array of edges, the graph as described in the file header
157-
function GraphPathfinder:init(yieldAfter, maxIterations, range, graph)
157+
---@param extension number|nil how far to extend the path after the goal, if nil, no extension is done. Extension is
158+
--- always follows the edge which has the goal on it, and ends where the edge ends if the edge is not long enough to
159+
--- reach the extension distance.
160+
function GraphPathfinder:init(yieldAfter, maxIterations, range, graph, extension)
158161
HybridAStar.init(self, { }, yieldAfter, maxIterations)
159162
self.logger = Logger('GraphPathfinder', Logger.level.debug, CpDebug.DBG_PATHFINDER)
160163
self.range = range
161164
self.transitionRange = range
162165
self.originalGraph = graph
166+
self.extension = extension
163167
self.deltaPosGoal = self.range
164168
self.deltaThetaDeg = 181
165169
self.deltaThetaGoal = math.rad(self.deltaThetaDeg)
@@ -183,6 +187,7 @@ end
183187
function GraphPathfinder:rollUpPath(lastNode, goal)
184188
local edges = {}
185189
local currentNode = lastNode
190+
local lastEdge
186191
self:debug('Goal node at %.2f/%.2f, cost %.1f (%.1f - %.1f)', goal.x, goal.y, lastNode.cost,
187192
self.nodes.lowestCost, self.nodes.highestCost)
188193
while currentNode.pred and currentNode ~= currentNode.pred do
@@ -197,15 +202,43 @@ function GraphPathfinder:rollUpPath(lastNode, goal)
197202
if #edge > 0 then
198203
edge:calculateProperties()
199204
table.insert(edges, 1, edge)
205+
lastEdge = lastEdge or currentNode.edge
200206
end
201207
currentNode = currentNode.pred
202208
end
203209
local path = self:addTransitions(edges)
204210
self:debug('Nodes %d, iterations %d, yields %d, deltaTheta %.1f', #path, self.iterations, self.yields,
205211
math.deg(self.deltaThetaGoal))
212+
if self.extension then
213+
self:debug('Extending path by %.1f meters', self.extension)
214+
self:extendPath(path, lastEdge, self.extension)
215+
end
206216
return path
207217
end
208218

219+
function GraphPathfinder:extendPath(path, lastEdge, extension)
220+
if lastEdge.cutEdge then
221+
local cutEdgeLength = lastEdge.cutEdge:getLength()
222+
if cutEdgeLength > extension then
223+
lastEdge.cutEdge:cutEnd(cutEdgeLength - extension)
224+
else
225+
self:debug('Cut edge not long enough, extending by %.1f meters straight', extension - cutEdgeLength)
226+
lastEdge.cutEdge:extendEnd(extension - cutEdgeLength)
227+
end
228+
-- remove the first vertex of the cut edge, as it is the same as the last vertex of the path
229+
if #lastEdge.cutEdge == 2 then
230+
path:append(lastEdge.cutEdge[2])
231+
path:calculateProperties()
232+
else
233+
lastEdge.cutEdge:cutStartAtIx(2)
234+
path:appendMany(lastEdge.cutEdge)
235+
end
236+
else
237+
self:debug('Last edge has no cut edge, extending straight')
238+
path:extendEnd(extension)
239+
end
240+
end
241+
209242
function GraphPathfinder:start(start, goal, turnRadius, ...)
210243
-- at each run, make a copy of the graph as we'll modify it
211244
---@type GraphPathfinder.GraphEdge[]

scripts/pathfinder/test/GraphPathfinderTest.lua

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require('AnalyticHelper') -- for the test cases
2525
require('PathfinderUtil')
2626
require('HybridAStar')
2727
require('GraphPathfinder')
28+
lu.EPS = 0.01
2829

2930
local GraphEdge = GraphPathfinder.GraphEdge
3031
local TestConstraints = CpObject(PathfinderConstraintInterface)
@@ -416,37 +417,70 @@ function TestWithTransitions:testTransition()
416417
start = State3D(-5, 0, 0, 0)
417418
goal = State3D(210, 205, 0, 0)
418419
done, path, _ = runPathfinder()
419-
printPath()
420420
lu.assertIsTrue(done)
421421
lu.assertEquals(#path, 14)
422422
-- path contains all points of the edge it goes through
423423
path[1]:assertAlmostEquals(Vector(0, 0))
424-
path[3]:assertAlmostEquals(Vector(200, 0))
424+
path[3]:assertAlmostEquals(Vector(203.8, 0))
425425
-- here's the arch
426-
path[13]:assertAlmostEquals(Vector(210, 10))
426+
path[13]:assertAlmostEquals(Vector(210, 100))
427427
path[#path]:assertAlmostEquals(Vector(210, 200))
428428
end
429429

430-
TestExtension = {}
431-
function TestExtension:testExtension()
430+
-- z to make it run last
431+
TestzExtension = {}
432+
function TestzExtension:testExtension()
432433
local edge = GraphEdge(GraphEdge.UNIDIRECTIONAL, {})
433-
for i = 0, 100, 5 do
434+
for i = 0, 30, 5 do
434435
edge:append(Vertex(i, i / 5))
435436
end
437+
local len = edge:getLength()
436438
local graph = { edge }
437-
pathfinder = GraphPathfinder(math.huge, 500, 20, graph)
439+
edge = GraphEdge(GraphEdge.UNIDIRECTIONAL, {})
440+
for i = 35, 100, 5 do
441+
edge:append(Vertex(i, i / 5))
442+
end
443+
len = len + edge:getLength() + 5 / math.cos(math.atan(1/5))
444+
table.insert(graph, edge)
445+
lu.assertAlmostEquals(len, 101.98)
446+
pathfinder = GraphPathfinder(math.huge, 500, 20, graph, 5)
438447
start = State3D(-5, 0, 0, 0)
439-
goal = State3D(50, 10, 0, 0)
448+
goal = State3D(50, 0, 0, 0)
440449
done, path, _ = runPathfinder()
441-
printPath()
442450
lu.assertIsTrue(done)
443-
lu.assertEquals(#path, 90)
451+
lu.assertAlmostEquals(path:getLength(), len / 2 + 5)
452+
lu.assertEquals(#path, 11)
444453
-- path contains all points of the edge it goes through
445454
path[1]:assertAlmostEquals(Vector(0, 0))
446-
path[41]:assertAlmostEquals(Vector(200, 0))
447-
-- here's the arch
448-
path[52]:assertAlmostEquals(Vector(210, 10))
449-
path[#path]:assertAlmostEquals(Vector(210, 200))
455+
path[6]:assertAlmostEquals(Vector(25, 5))
456+
path[10]:assertAlmostEquals(Vector(50, 10))
457+
path[11]:assertAlmostEquals(Vector(54.9, 10.98))
458+
459+
pathfinder = GraphPathfinder(math.huge, 500, 20, graph, 15)
460+
done, path, _ = runPathfinder()
461+
lu.assertAlmostEquals(path:getLength(), len / 2 + 15)
462+
lu.assertEquals(#path, 12)
463+
path[11]:assertAlmostEquals(Vector(55, 11))
464+
path[12]:assertAlmostEquals(Vector(64.71, 12.94))
465+
466+
-- cut edge not long enough
467+
pathfinder = GraphPathfinder(math.huge, 500, 20, graph, 55)
468+
done, path, _ = runPathfinder()
469+
lu.assertAlmostEquals(path:getLength(), len / 2 + 55)
470+
lu.assertEquals(#path, 20)
471+
path[18]:assertAlmostEquals(Vector(90, 18))
472+
path[19]:assertAlmostEquals(Vector(95, 19))
473+
path[20]:assertAlmostEquals(Vector(103.93, 20.79))
474+
475+
-- no cut edge
476+
pathfinder = GraphPathfinder(math.huge, 500, 20, graph, 5)
477+
goal = State3D(100, 10, 0, 0)
478+
done, path, _ = runPathfinder()
479+
lu.assertAlmostEquals(path:getLength(), len + 5)
480+
lu.assertEquals(#path, 20)
481+
path[18]:assertAlmostEquals(Vector(90, 18))
482+
path[19]:assertAlmostEquals(Vector(95, 19))
483+
path[20]:assertAlmostEquals(Vector(104.90, 20.98))
450484

451485
end
452486

0 commit comments

Comments
 (0)