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