CC-navigation/aStar.lua
2016-08-13 08:57:08 +01:00

166 lines
4.9 KiB
Lua

if not pQueue then
if not os.loadAPI("pQueue") then
error("could not load pQueue API")
end
end
-- a very basic map API used to store node information
local mapMethods = {
get = function(self, tVector)
if self.map[tVector.x] and self.map[tVector.x][tVector.y] then
return self.map[tVector.x][tVector.y][tVector.z]
end
end,
set = function(self, tVector, value)
if not self.map[tVector.x] then
self.map[tVector.x] = {}
end
if not self.map[tVector.x][tVector.y] then
self.map[tVector.x][tVector.y] = {}
end
self.map[tVector.x][tVector.y][tVector.z] = value
return self.map[tVector.x][tVector.y][tVector.z]
end,
getOrSet = function(self, tVector, value)
if self.map[tVector.x] and self.map[tVector.x][tVector.y] and self.map[tVector.x][tVector.y][tVector.z] ~= nil then
return self.map[tVector.x][tVector.y][tVector.z], false
else
return self:set(tVector, value), true
end
end,
}
local mapMetatable = {__index = mapMethods}
function newMap()
return setmetatable({map = {}}, mapMetatable)
end
local function makePath(nodes, start, startEnd, goalStart, goal)
local current, path = startEnd, {}
while not vectorEquals(current, start) do
table.insert(path, current)
current = nodes:get(current)[1]
end
current = goalStart
while not vectorEquals(current, goal) do
table.insert(path, 1, current)
current = nodes:get(current)[1]
end
table.insert(path, 1, goal)
return path
end
function vectorEquals(a, b) -- the comparison function used in pQueue
return a.x == b.x and a.y == b.y and a.z == b.z
end
local posZ = vector.new(0, 0, 1)
local negX = vector.new(-1, 0, 0)
local negZ = vector.new(0, 0, -1)
local posX = vector.new(1, 0, 0)
local posY = vector.new(0, 1, 0)
local negY = vector.new(0, -1, 0)
function adjacent(u)
return {
u + posZ,
u + negX,
u + negZ,
u + posX,
u + posY,
u + negY,
}
end
function distance(a, b) -- 1-norm/manhattan metric
return math.abs(a.x - b.x) + math.abs(a.y - b.y) + math.abs(a.z - b.z)
end
function compute(distanceFunction, start, goal)
if type(distanceFunction) ~= "function" then
error("aStar new: distanceFunction must be of type function", 2)
end
local distanceFunc = distanceFunction -- is this necessary?
-- node data structure is {parent node, true cost from startNode/goalNode, whether in closed list, search direction this node was found in, whether in open list}
local nodes = newMap()
nodes:set(start, {start + vector.new(0, 0, -1), 0, false, true, true})
nodes:set(goal, {goal + vector.new(0, 0, -1), 0, false, false, true})
local openStartSet = pQueue.new()
openStartSet:insert(start, distance(start, goal))
local openGoalSet = pQueue.new()
openGoalSet:insert(goal, distance(start, goal))
local yieldCount = 0
local activeOpenSet, pendingOpenSet = openStartSet, openGoalSet
local forwardSearch, lastNode, switch = true, false, false
local current, currNode, parent
local baseCost
local newCost
local nbrNode, newNode
local preHeuristic
while not openStartSet:isEmpty() and not openGoalSet:isEmpty() do
--yield every so often to avoid getting timed out
yieldCount = yieldCount + 1
if yieldCount > 200 then
os.pullEvent(os.queueEvent("yield"))
yieldCount = 0
end
if switch then --switch the search direction
activeOpenSet, pendingOpenSet = pendingOpenSet, activeOpenSet
forwardSearch = not forwardSearch
lastNode = false
end
current = activeOpenSet:pop()
currNode = nodes:get(current)
parent = current - currNode[1]
currNode[3], currNode[5], switch = true, false, true
for _, neighbour in ipairs(adjacent(current)) do
baseCost = distanceFunc(current, neighbour)
if baseCost < math.huge then -- if not graph:get(neighbour) then
newCost = currNode[2] + baseCost
nbrNode, newNode = nodes:getOrSet(neighbour, {current, newCost, false, forwardSearch, false})
if switch and ((not lastNode) or vectorEquals(lastNode, neighbour)) then
switch = false
end
if not newNode then
if forwardSearch ~= nbrNode[4] then -- nbrNode has been discovered in the opposite search direction
if nbrNode[3] then -- and is in the closed list so has been expanded already
return makePath(nodes, start, (forwardSearch and current) or neighbour, (forwardSearch and neighbour) or current, goal)
end
elseif newCost < nbrNode[2] then
if nbrNode[5] then
activeOpenSet:remove(neighbour, vectorEquals)
nbrNode[5] = false
end
nbrNode[3] = false
end
end
if (newNode or (forwardSearch ~= nbrNode[4] and not nbrNode[5] and not nbrNode[3])) and newCost < math.huge then
nbrNode[1] = current
nbrNode[2] = newCost
nbrNode[4] = currNode[4]
nbrNode[5] = true
preHeuristic = distance(neighbour, (forwardSearch and goal) or start)
activeOpenSet:insert(neighbour, newCost + preHeuristic + 0.0001*(preHeuristic + parent.length(parent:cross(neighbour - current))))
end
end
end
lastNode = current
end
return false
end