Skip to content

Commit 3ec2cd8

Browse files
author
Peter Vaiko
committed
refactor: preparing for the new feature
Fixed a bug in the Slider which caused callstacks and refactored the GraphPathfinder to allow for extending later.
1 parent 61a9947 commit 3ec2cd8

6 files changed

Lines changed: 112 additions & 35 deletions

File tree

scripts/courseGenerator/geometry/Slider.lua

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ local Slider = CpObject(CourseGenerator.LineSegment)
66
---@param d number distance from polyline[ix] for the initial position
77
function Slider:init(polyline, ix, d)
88
self.polyline = polyline
9-
self._isAtEnd = false
109
self:set(ix, 0)
1110
self:move(d or 0)
1211
end
1312

13+
--- The slider's position is defined by the index of a vertex and the distance from that vertex on
14+
--- the exit edge, therefore, d is always positive.
1415
function Slider:set(ix, d)
1516
-- index of the current vertex
1617
self.ix = ix
@@ -19,7 +20,12 @@ function Slider:set(ix, d)
1920
self.base = Vector(self:vertex().x, self:vertex().y)
2021
-- unit vector pointing to the exit heading of the vertex
2122
self.slope = Vector(1, 0)
22-
self.slope:setHeading(self:vertex():getExitHeading())
23+
local exitHeading = self:vertex():getExitHeading()
24+
if exitHeading then
25+
-- leave the slope as it was when there is no exit heading. This can happen if the caller did not
26+
-- properly ran calculateProperties() on the polyline.
27+
self.slope:setHeading(exitHeading)
28+
end
2329
-- move to the current offset
2430
self:offset(d, 0)
2531
end
@@ -58,17 +64,17 @@ function Slider:move(d)
5864
end
5965

6066
local function backward()
61-
local entryEdge = self:vertex(ix):getEntryEdge()
62-
if not entryEdge then
63-
-- reached the start of polyline
64-
dRemaining = 0
65-
endReached = true
66-
return false
67-
end
6867
if dRemaining > offset then
69-
dRemaining = dRemaining - offset
70-
ix = ix - 1
71-
offset = entryEdge:getLength()
68+
if self:vertex(ix):getEntryEdge() == nil then
69+
-- reached the start of polyline
70+
offset = 0
71+
endReached = true
72+
return false
73+
else
74+
dRemaining = dRemaining - offset
75+
ix = ix - 1
76+
offset = self:vertex(ix):getExitEdge():getLength()
77+
end
7278
else
7379
offset = offset - dRemaining
7480
return false

scripts/courseGenerator/test/PolylineTest.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,35 @@ function testSplitEdges()
424424
p[3]:assertAlmostEquals(Vertex(85, 0))
425425
p[2]:assertAlmostEquals(Vertex(95, 0))
426426
p[1]:assertAlmostEquals(Vertex(105, 0))
427+
428+
p = Polyline({
429+
Vertex(100, 100),
430+
Vertex(110, 100),
431+
Vertex(120, 100),
432+
Vertex(130, 100),
433+
Vertex(140, 100),
434+
Vertex(150, 100)
435+
})
436+
p:splitEdges(5)
437+
lu.assertEquals(#p, 11)
438+
p[1]:assertAlmostEquals(Vertex(100, 100))
439+
p[2]:assertAlmostEquals(Vertex(105, 100))
440+
p[10]:assertAlmostEquals(Vertex(145, 100))
441+
p[11]:assertAlmostEquals(Vertex(150, 100))
442+
end
443+
444+
function testEnsureMinimumRadius()
445+
local p = Polyline({
446+
Vertex(9.33, -13.66),
447+
Vertex(24.13, -2.36),
448+
Vertex(36.79, -13.78),
449+
Vertex(41.12, -17.94),
450+
Vertex(45.41, -22.13)
451+
})
452+
p:ensureMinimumRadius(9.3)
453+
lu.assertEquals(#p, 18)
454+
p[1]:assertAlmostEquals(Vertex(9.33, -13.66))
455+
p[18]:assertAlmostEquals(Vertex(45.41, -22.13))
427456
end
428457

429458
os.exit(lu.LuaUnit.run())

scripts/courseGenerator/test/SliderTest.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,21 @@ function testSlider()
4848
s:move(-1)
4949
s:assertAlmostEquals(CourseGenerator.LineSegment(0, 0, 0, 1))
5050
end
51+
52+
function testShortPolyline()
53+
local p = Polyline({
54+
Vertex(0, 0),
55+
Vertex(100, 0),
56+
Vertex(100, 100),
57+
})
58+
local s = CourseGenerator.Slider(p, 2, 0)
59+
s:assertAlmostEquals(CourseGenerator.LineSegment(100, 0, 100, 1))
60+
s:move(1)
61+
s:assertAlmostEquals(CourseGenerator.LineSegment(100, 1, 100, 2))
62+
s:move(-1)
63+
s:assertAlmostEquals(CourseGenerator.LineSegment(100, 0, 100, 1))
64+
s:move(-1)
65+
s:assertAlmostEquals(CourseGenerator.LineSegment(99, 0, 100, 0))
66+
end
67+
5168
os.exit(lu.LuaUnit.run())

scripts/pathfinder/GraphPathfinder.lua

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ function GraphPathfinder.GraphEdge:getExit(entry)
6868
end
6969
end
7070

71+
-- if the vertex is the first or last vertex of the edge, we can use it directly as the entry/exit,
72+
-- otherwise, we split the edge at the vertex so we can use it as an entry/exit point.
73+
---@return GraphPathfinder.GraphEdge|nil new edge if the edge was split, nil otherwise
74+
function GraphPathfinder.GraphEdge:splitWhenNeeded(closestVertex)
75+
if closestVertex.ix ~= 1 and closestVertex.ix ~= #self then
76+
local newEdge = GraphPathfinder.GraphEdge(self:getDirection())
77+
for j = closestVertex.ix, #self do
78+
newEdge:append(self[j])
79+
end
80+
newEdge:calculateProperties()
81+
self:cutEndAtIx(closestVertex.ix)
82+
-- remember the piece we cut off, we may need it if the path must be extended after the goal
83+
self.cutEdge = newEdge
84+
return newEdge
85+
end
86+
end
87+
7188
function GraphPathfinder.GraphEdge:rollUpIterator(entry)
7289
local from, to, step
7390
if entry == self[1] then
@@ -191,6 +208,7 @@ end
191208

192209
function GraphPathfinder:start(start, goal, turnRadius, ...)
193210
-- at each run, make a copy of the graph as we'll modify it
211+
---@type GraphPathfinder.GraphEdge[]
194212
self.graph = {}
195213
for _, e in ipairs(self.originalGraph) do
196214
if #e > 1 then
@@ -238,24 +256,6 @@ end
238256
---@return State3D the entry vertex of the graph, closest to start
239257
---@return State3D the exit vertex of the graph, closest to goal
240258
function GraphPathfinder:createGraphEntryAndExit(start, goal)
241-
242-
local function splitEdgeWhenNeeded(edge, closestVertex)
243-
-- if the vertex is the first or last vertex of the edge, we can use it directly as the entry/exit,
244-
-- otherwise, we split the edge at the vertex so we can use it as an entry/exit point.
245-
if closestVertex.ix ~= 1 and closestVertex.ix ~= #edge then
246-
self.logger:debug('Graph entry/exit found and split at vertex %d, x: %.1f y: %.1f',
247-
closestVertex.ix, closestVertex.x, closestVertex.y)
248-
local newEdge = GraphPathfinder.GraphEdge(edge:getDirection())
249-
for j = closestVertex.ix, #edge do
250-
newEdge:append(edge[j])
251-
end
252-
newEdge:calculateProperties()
253-
table.insert(self.graph, newEdge)
254-
edge:cutEndAtIx(closestVertex.ix)
255-
return newEdge
256-
end
257-
end
258-
259259
-- find the edges closest to node. If the closest vertex isn't the first or last vertex of the edge, we split the
260260
-- edge at that vertex so we can use it as an entry/exit point.
261261
local function findClosestEdges(node, isEntry)
@@ -275,8 +275,11 @@ function GraphPathfinder:createGraphEntryAndExit(start, goal)
275275
for i = 1, #self.graph do
276276
local edge = self.graph[i]
277277
local vertex, d = edge:findClosestVertexToPoint(node)
278-
local newEdge = splitEdgeWhenNeeded(edge, vertex)
278+
local newEdge = edge:splitWhenNeeded(vertex)
279279
if newEdge then
280+
self.logger:debug('Graph entry/exit found and split at vertex %d, x: %.1f y: %.1f',
281+
vertex.ix, vertex.x, vertex.y)
282+
table.insert(self.graph, newEdge)
280283
addToClosestEdge(newEdge, newEdge[1], d)
281284
end
282285
addToClosestEdge(edge, vertex, d)
@@ -327,7 +330,6 @@ function GraphPathfinder:addTransitions(edges)
327330
end
328331
path:appendMany(edges[#edges]) -- append the last edge, this will be the exit edge
329332
path:calculateProperties()
330-
path:splitEdges(5)
331333
path:ensureMinimumRadius(self.turnRadius, false, 0.5)
332334
return path
333335
end
@@ -341,7 +343,7 @@ GraphPathfinder.GraphMotionPrimitives = CpObject(HybridAStar.MotionPrimitives)
341343
--- two edges are considered as connected (and thus can traverse from one to the other)
342344
---@param graph Vector[] the graph as described in the file header
343345
function GraphPathfinder.GraphMotionPrimitives:init(range, graph)
344-
self.logger = Logger('GraphMotionPrimitives', Logger.level.trace, CpDebug.DBG_PATHFINDER)
346+
self.logger = Logger('GraphMotionPrimitives', Logger.level.debug, CpDebug.DBG_PATHFINDER)
345347
self.range = range
346348
self.graph = graph
347349
end

scripts/pathfinder/State3D.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ function State3D:__tostring()
207207
steer = 'Straight'
208208
end
209209
local gear = self.gear == Gear.Forward and 'Forward' or 'Backward'
210-
local tTrailer = self.tTrailer and string.format(' (%d)', math.deg(self.tTrailer)) or ''
211-
result = string.format('x: %.2f y:%.2f t:%d%s gear:%s steer:%s g:%.4f h:%.4f c:%.4f closed:%s open:%s',
210+
local tTrailer = self.tTrailer and string.format(' (%.0f)', math.deg(self.tTrailer)) or ''
211+
result = string.format('x: %.2f y:%.2f t:%.0f%s gear:%s steer:%s g:%.4f h:%.4f c:%.4f closed:%s open:%s',
212212
self.x, self.y, math.deg(self.t), tTrailer, gear, steer,
213213
self.g, self.h, self.cost, tostring(self.closed), tostring(self.onOpenList))
214214
return result

scripts/pathfinder/test/GraphPathfinderTest.lua

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,13 +418,36 @@ function TestWithTransitions:testTransition()
418418
done, path, _ = runPathfinder()
419419
printPath()
420420
lu.assertIsTrue(done)
421+
lu.assertEquals(#path, 14)
422+
-- path contains all points of the edge it goes through
423+
path[1]:assertAlmostEquals(Vector(0, 0))
424+
path[3]:assertAlmostEquals(Vector(200, 0))
425+
-- here's the arch
426+
path[13]:assertAlmostEquals(Vector(210, 10))
427+
path[#path]:assertAlmostEquals(Vector(210, 200))
428+
end
429+
430+
TestExtension = {}
431+
function TestExtension:testExtension()
432+
local edge = GraphEdge(GraphEdge.UNIDIRECTIONAL, {})
433+
for i = 0, 100, 5 do
434+
edge:append(Vertex(i, i / 5))
435+
end
436+
local graph = { edge }
437+
pathfinder = GraphPathfinder(math.huge, 500, 20, graph)
438+
start = State3D(-5, 0, 0, 0)
439+
goal = State3D(50, 10, 0, 0)
440+
done, path, _ = runPathfinder()
441+
printPath()
442+
lu.assertIsTrue(done)
421443
lu.assertEquals(#path, 90)
422444
-- path contains all points of the edge it goes through
423445
path[1]:assertAlmostEquals(Vector(0, 0))
424446
path[41]:assertAlmostEquals(Vector(200, 0))
425447
-- here's the arch
426448
path[52]:assertAlmostEquals(Vector(210, 10))
427449
path[#path]:assertAlmostEquals(Vector(210, 200))
450+
428451
end
429452

430453
os.exit(lu.LuaUnit.run())

0 commit comments

Comments
 (0)