mirror of
https://github.com/kepler155c/opus
synced 2025-01-23 13:46:53 +00:00
proxy + pathfinding optimization
This commit is contained in:
parent
7fd93e8a8b
commit
cac15722b8
@ -24,9 +24,9 @@ function Routine:terminate()
|
||||
end
|
||||
|
||||
function Routine:resume(event, ...)
|
||||
if coroutine.status(self.co) == 'running' then
|
||||
return
|
||||
end
|
||||
--if coroutine.status(self.co) == 'running' then
|
||||
--return
|
||||
--end
|
||||
|
||||
if not self.co then
|
||||
error('Cannot resume a dead routine')
|
||||
|
@ -1,105 +0,0 @@
|
||||
-- Various assertion function for API methods argument-checking
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependancies
|
||||
local _PATH = (...):gsub('%.core.assert$','')
|
||||
local Utils = require (_PATH .. '.core.utils')
|
||||
|
||||
-- Local references
|
||||
local lua_type = type
|
||||
local floor = math.floor
|
||||
local concat = table.concat
|
||||
local next = next
|
||||
local pairs = pairs
|
||||
local getmetatable = getmetatable
|
||||
|
||||
-- Is I an integer ?
|
||||
local function isInteger(i)
|
||||
return lua_type(i) ==('number') and (floor(i)==i)
|
||||
end
|
||||
|
||||
-- Override lua_type to return integers
|
||||
local function type(v)
|
||||
return isInteger(v) and 'int' or lua_type(v)
|
||||
end
|
||||
|
||||
-- Does the given array contents match a predicate type ?
|
||||
local function arrayContentsMatch(t,...)
|
||||
local n_count = Utils.arraySize(t)
|
||||
if n_count < 1 then return false end
|
||||
local init_count = t[0] and 0 or 1
|
||||
local n_count = (t[0] and n_count-1 or n_count)
|
||||
local types = {...}
|
||||
if types then types = concat(types) end
|
||||
for i=init_count,n_count,1 do
|
||||
if not t[i] then return false end
|
||||
if types then
|
||||
if not types:match(type(t[i])) then return false end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Checks if arg is a valid array map
|
||||
local function isMap(m)
|
||||
if not arrayContentsMatch(m, 'table') then return false end
|
||||
local lsize = Utils.arraySize(m[next(m)])
|
||||
for k,v in pairs(m) do
|
||||
if not arrayContentsMatch(m[k], 'string', 'int') then return false end
|
||||
if Utils.arraySize(v)~=lsize then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Checks if s is a valid string map
|
||||
local function isStringMap(s)
|
||||
if lua_type(s) ~= 'string' then return false end
|
||||
local w
|
||||
for row in s:gmatch('[^\n\r]+') do
|
||||
if not row then return false end
|
||||
w = w or #row
|
||||
if w ~= #row then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Does instance derive straight from class
|
||||
local function derives(instance, class)
|
||||
return getmetatable(instance) == class
|
||||
end
|
||||
|
||||
-- Does instance inherits from class
|
||||
local function inherits(instance, class)
|
||||
return (getmetatable(getmetatable(instance)) == class)
|
||||
end
|
||||
|
||||
-- Is arg a boolean
|
||||
local function isBoolean(b)
|
||||
return (b==true or b==false)
|
||||
end
|
||||
|
||||
-- Is arg nil ?
|
||||
local function isNil(n)
|
||||
return (n==nil)
|
||||
end
|
||||
|
||||
local function matchType(value, types)
|
||||
return types:match(type(value))
|
||||
end
|
||||
|
||||
return {
|
||||
arrayContentsMatch = arrayContentsMatch,
|
||||
derives = derives,
|
||||
inherits = inherits,
|
||||
isInteger = isInteger,
|
||||
isBool = isBoolean,
|
||||
isMap = isMap,
|
||||
isStrMap = isStringMap,
|
||||
isOutOfRange = isOutOfRange,
|
||||
isNil = isNil,
|
||||
type = type,
|
||||
matchType = matchType
|
||||
}
|
||||
|
||||
end
|
@ -1,98 +0,0 @@
|
||||
--- Heuristic functions for search algorithms.
|
||||
-- A <a href="http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html">distance heuristic</a>
|
||||
-- provides an *estimate of the optimal distance cost* from a given location to a target.
|
||||
-- As such, it guides the pathfinder to the goal, helping it to decide which route is the best.
|
||||
--
|
||||
-- This script holds the definition of some built-in heuristics available through jumper.
|
||||
--
|
||||
-- Distance functions are internally used by the `pathfinder` to evaluate the optimal path
|
||||
-- from the start location to the goal. These functions share the same prototype:
|
||||
-- local function myHeuristic(nodeA, nodeB)
|
||||
-- -- function body
|
||||
-- end
|
||||
-- Jumper features some built-in distance heuristics, namely `MANHATTAN`, `EUCLIDIAN`, `DIAGONAL`, `CARDINTCARD`.
|
||||
-- You can also supply your own heuristic function, following the same template as above.
|
||||
|
||||
|
||||
local abs = math.abs
|
||||
local sqrt = math.sqrt
|
||||
local sqrt2 = sqrt(2)
|
||||
local max, min = math.max, math.min
|
||||
|
||||
local Heuristics = {}
|
||||
--- Manhattan distance.
|
||||
-- <br/>This heuristic is the default one being used by the `pathfinder` object.
|
||||
-- <br/>Evaluates as <code>distance = |dx|+|dy|</code>
|
||||
-- @class function
|
||||
-- @tparam node nodeA a node
|
||||
-- @tparam node nodeB another node
|
||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
||||
-- @usage
|
||||
-- -- First method
|
||||
-- pathfinder:setHeuristic('MANHATTAN')
|
||||
-- -- Second method
|
||||
-- local Distance = require ('jumper.core.heuristics')
|
||||
-- pathfinder:setHeuristic(Distance.MANHATTAN)
|
||||
function Heuristics.MANHATTAN(nodeA, nodeB)
|
||||
local dx = abs(nodeA._x - nodeB._x)
|
||||
local dy = abs(nodeA._y - nodeB._y)
|
||||
local dz = abs(nodeA._z - nodeB._z)
|
||||
return (dx + dy + dz)
|
||||
end
|
||||
|
||||
--- Euclidian distance.
|
||||
-- <br/>Evaluates as <code>distance = squareRoot(dx*dx+dy*dy)</code>
|
||||
-- @class function
|
||||
-- @tparam node nodeA a node
|
||||
-- @tparam node nodeB another node
|
||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
||||
-- @usage
|
||||
-- -- First method
|
||||
-- pathfinder:setHeuristic('EUCLIDIAN')
|
||||
-- -- Second method
|
||||
-- local Distance = require ('jumper.core.heuristics')
|
||||
-- pathfinder:setHeuristic(Distance.EUCLIDIAN)
|
||||
function Heuristics.EUCLIDIAN(nodeA, nodeB)
|
||||
local dx = nodeA._x - nodeB._x
|
||||
local dy = nodeA._y - nodeB._y
|
||||
local dz = nodeA._z - nodeB._z
|
||||
return sqrt(dx*dx+dy*dy+dz*dz)
|
||||
end
|
||||
|
||||
--- Diagonal distance.
|
||||
-- <br/>Evaluates as <code>distance = max(|dx|, abs|dy|)</code>
|
||||
-- @class function
|
||||
-- @tparam node nodeA a node
|
||||
-- @tparam node nodeB another node
|
||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
||||
-- @usage
|
||||
-- -- First method
|
||||
-- pathfinder:setHeuristic('DIAGONAL')
|
||||
-- -- Second method
|
||||
-- local Distance = require ('jumper.core.heuristics')
|
||||
-- pathfinder:setHeuristic(Distance.DIAGONAL)
|
||||
function Heuristics.DIAGONAL(nodeA, nodeB)
|
||||
local dx = abs(nodeA._x - nodeB._x)
|
||||
local dy = abs(nodeA._y - nodeB._y)
|
||||
return max(dx,dy)
|
||||
end
|
||||
|
||||
--- Cardinal/Intercardinal distance.
|
||||
-- <br/>Evaluates as <code>distance = min(dx, dy)*squareRoot(2) + max(dx, dy) - min(dx, dy)</code>
|
||||
-- @class function
|
||||
-- @tparam node nodeA a node
|
||||
-- @tparam node nodeB another node
|
||||
-- @treturn number the distance from __nodeA__ to __nodeB__
|
||||
-- @usage
|
||||
-- -- First method
|
||||
-- pathfinder:setHeuristic('CARDINTCARD')
|
||||
-- -- Second method
|
||||
-- local Distance = require ('jumper.core.heuristics')
|
||||
-- pathfinder:setHeuristic(Distance.CARDINTCARD)
|
||||
function Heuristics.CARDINTCARD(nodeA, nodeB)
|
||||
local dx = abs(nodeA._x - nodeB._x)
|
||||
local dy = abs(nodeA._y - nodeB._y)
|
||||
return min(dx,dy) * sqrt2 + max(dx,dy) - min(dx,dy)
|
||||
end
|
||||
|
||||
return Heuristics
|
@ -1,32 +0,0 @@
|
||||
local addNode(self, node, nextNode, ed)
|
||||
if not self._pathDB[node] then self._pathDB[node] = {} end
|
||||
self._pathDB[node][ed] = (nextNode == ed and node or nextNode)
|
||||
end
|
||||
|
||||
-- Path lookupTable
|
||||
local lookupTable = {}
|
||||
lookupTable.__index = lookupTable
|
||||
|
||||
function lookupTable:new()
|
||||
local lut = {_pathDB = {}}
|
||||
return setmetatable(lut, lookupTable)
|
||||
end
|
||||
|
||||
function lookupTable:addPath(path)
|
||||
local st, ed = path._nodes[1], path._nodes[#path._nodes]
|
||||
for node, count in path:nodes() do
|
||||
local nextNode = path._nodes[count+1]
|
||||
if nextNode then addNode(self, node, nextNode, ed) end
|
||||
end
|
||||
end
|
||||
|
||||
function lookupTable:hasPath(nodeA, nodeB)
|
||||
local found
|
||||
found = self._pathDB[nodeA] and self._path[nodeA][nodeB]
|
||||
if found then return true, true end
|
||||
found = self._pathDB[nodeB] and self._path[nodeB][nodeA]
|
||||
if found then return true, false end
|
||||
return false
|
||||
end
|
||||
|
||||
return lookupTable
|
@ -4,14 +4,12 @@
|
||||
-- and then cached within the `grid`.
|
||||
--
|
||||
-- In the following implementation, nodes can be compared using the `<` operator. The comparison is
|
||||
-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
|
||||
-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
|
||||
-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.
|
||||
--
|
||||
--
|
||||
|
||||
if (...) then
|
||||
|
||||
local assert = assert
|
||||
|
||||
--- The `Node` class.<br/>
|
||||
-- This class is callable.
|
||||
-- Therefore,_ <code>Node(...)</code> _acts as a shortcut to_ <code>Node:new(...)</code>.
|
||||
@ -26,7 +24,7 @@ if (...) then
|
||||
-- @treturn node a new `node`
|
||||
-- @usage local node = Node(3,4)
|
||||
function Node:new(x,y,z)
|
||||
return setmetatable({_x = x, _y = y, _z = z, _clearance = {}}, Node)
|
||||
return setmetatable({_x = x, _y = y, _z = z }, Node)
|
||||
end
|
||||
|
||||
-- Enables the use of operator '<' to compare nodes.
|
||||
@ -36,48 +34,24 @@ if (...) then
|
||||
--- Returns x-coordinate of a `node`
|
||||
-- @class function
|
||||
-- @treturn number the x-coordinate of the `node`
|
||||
-- @usage local x = node:getX()
|
||||
-- @usage local x = node:getX()
|
||||
function Node:getX() return self._x end
|
||||
|
||||
|
||||
--- Returns y-coordinate of a `node`
|
||||
-- @class function
|
||||
-- @treturn number the y-coordinate of the `node`
|
||||
-- @usage local y = node:getY()
|
||||
-- @treturn number the y-coordinate of the `node`
|
||||
-- @usage local y = node:getY()
|
||||
function Node:getY() return self._y end
|
||||
|
||||
function Node:getZ() return self._z end
|
||||
|
||||
|
||||
--- Returns x and y coordinates of a `node`
|
||||
-- @class function
|
||||
-- @treturn number the x-coordinate of the `node`
|
||||
-- @treturn number the y-coordinate of the `node`
|
||||
-- @usage local x, y = node:getPos()
|
||||
-- @usage local x, y = node:getPos()
|
||||
function Node:getPos() return self._x, self._y, self._z end
|
||||
|
||||
--- Returns the amount of true [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
|
||||
-- for a given `node`
|
||||
-- @class function
|
||||
-- @tparam string|int|func walkable the value for walkable locations in the collision map array.
|
||||
-- @treturn int the clearance of the `node`
|
||||
-- @usage
|
||||
-- -- Assuming walkable was 0
|
||||
-- local clearance = node:getClearance(0)
|
||||
function Node:getClearance(walkable)
|
||||
return self._clearance[walkable]
|
||||
end
|
||||
|
||||
--- Removes the clearance value for a given walkable.
|
||||
-- @class function
|
||||
-- @tparam string|int|func walkable the value for walkable locations in the collision map array.
|
||||
-- @treturn node self (the calling `node` itself, can be chained)
|
||||
-- @usage
|
||||
-- -- Assuming walkable is defined
|
||||
-- node:removeClearance(walkable)
|
||||
function Node:removeClearance(walkable)
|
||||
self._clearance[walkable] = nil
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Clears temporary cached attributes of a `node`.
|
||||
-- Deletes the attributes cached within a given node after a pathfinding call.
|
||||
-- This function is internally used by the search algorithms, so you should not use it explicitely.
|
||||
@ -91,10 +65,10 @@ if (...) then
|
||||
self._opened, self._closed, self._parent = nil, nil, nil
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return setmetatable(Node,
|
||||
{__call = function(self,...)
|
||||
return Node:new(...)
|
||||
{__call = function(_,...)
|
||||
return Node:new(...)
|
||||
end}
|
||||
)
|
||||
end
|
@ -6,17 +6,7 @@
|
||||
-- It should normally not be used explicitely, yet it remains fully accessible.
|
||||
--
|
||||
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependencies
|
||||
local _PATH = (...):match('(.+)%.path$')
|
||||
local Heuristic = require (_PATH .. '.heuristics')
|
||||
|
||||
-- Local references
|
||||
local abs, max = math.abs, math.max
|
||||
local t_insert, t_remove = table.insert, table.remove
|
||||
|
||||
--- The `Path` class.<br/>
|
||||
-- This class is callable.
|
||||
-- Therefore, <em><code>Path(...)</code></em> acts as a shortcut to <em><code>Path:new(...)</code></em>.
|
||||
@ -42,8 +32,8 @@ if (...) then
|
||||
-- for node, count in p:iter() do
|
||||
-- ...
|
||||
-- end
|
||||
function Path:iter()
|
||||
local i,pathLen = 1,#self._nodes
|
||||
function Path:nodes()
|
||||
local i = 1
|
||||
return function()
|
||||
if self._nodes[i] then
|
||||
i = i+1
|
||||
@ -51,150 +41,9 @@ if (...) then
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Iterates on each single `node` along a `path`. At each step of iteration,
|
||||
-- returns a `node` plus a count value. Alias for @{Path:iter}
|
||||
-- @class function
|
||||
-- @name Path:nodes
|
||||
-- @treturn node a `node`
|
||||
-- @treturn int the count for the number of nodes
|
||||
-- @see Path:iter
|
||||
-- @usage
|
||||
-- for node, count in p:nodes() do
|
||||
-- ...
|
||||
-- end
|
||||
Path.nodes = Path.iter
|
||||
|
||||
--- Evaluates the `path` length
|
||||
-- @class function
|
||||
-- @treturn number the `path` length
|
||||
-- @usage local len = p:getLength()
|
||||
function Path:getLength()
|
||||
local len = 0
|
||||
for i = 2,#self._nodes do
|
||||
len = len + Heuristic.EUCLIDIAN(self._nodes[i], self._nodes[i-1])
|
||||
end
|
||||
return len
|
||||
end
|
||||
|
||||
--- Counts the number of steps.
|
||||
-- Returns the number of waypoints (nodes) in the current path.
|
||||
-- @class function
|
||||
-- @tparam node node a node to be added to the path
|
||||
-- @tparam[opt] int index the index at which the node will be inserted. If omitted, the node will be appended after the last node in the path.
|
||||
-- @treturn path self (the calling `path` itself, can be chained)
|
||||
-- @usage local nSteps = p:countSteps()
|
||||
function Path:addNode(node, index)
|
||||
index = index or #self._nodes+1
|
||||
t_insert(self._nodes, index, node)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- `Path` filling modifier. Interpolates between non contiguous nodes along a `path`
|
||||
-- to build a fully continuous `path`. This maybe useful when using search algorithms such as Jump Point Search.
|
||||
-- Does the opposite of @{Path:filter}
|
||||
-- @class function
|
||||
-- @treturn path self (the calling `path` itself, can be chained)
|
||||
-- @see Path:filter
|
||||
-- @usage p:fill()
|
||||
function Path:fill()
|
||||
local i = 2
|
||||
local xi,yi,dx,dy
|
||||
local N = #self._nodes
|
||||
local incrX, incrY
|
||||
while true do
|
||||
xi,yi = self._nodes[i]._x,self._nodes[i]._y
|
||||
dx,dy = xi-self._nodes[i-1]._x,yi-self._nodes[i-1]._y
|
||||
if (abs(dx) > 1 or abs(dy) > 1) then
|
||||
incrX = dx/max(abs(dx),1)
|
||||
incrY = dy/max(abs(dy),1)
|
||||
t_insert(self._nodes, i, self._grid:getNodeAt(self._nodes[i-1]._x + incrX, self._nodes[i-1]._y +incrY))
|
||||
N = N+1
|
||||
else i=i+1
|
||||
end
|
||||
if i>N then break end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`
|
||||
-- consisting of straight moves. Does the opposite of @{Path:fill}
|
||||
-- @class function
|
||||
-- @treturn path self (the calling `path` itself, can be chained)
|
||||
-- @see Path:fill
|
||||
-- @usage p:filter()
|
||||
function Path:filter()
|
||||
local i = 2
|
||||
local xi,yi,dx,dy, olddx, olddy
|
||||
xi,yi = self._nodes[i]._x, self._nodes[i]._y
|
||||
dx, dy = xi - self._nodes[i-1]._x, yi-self._nodes[i-1]._y
|
||||
while true do
|
||||
olddx, olddy = dx, dy
|
||||
if self._nodes[i+1] then
|
||||
i = i+1
|
||||
xi, yi = self._nodes[i]._x, self._nodes[i]._y
|
||||
dx, dy = xi - self._nodes[i-1]._x, yi - self._nodes[i-1]._y
|
||||
if olddx == dx and olddy == dy then
|
||||
t_remove(self._nodes, i-1)
|
||||
i = i - 1
|
||||
end
|
||||
else break end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clones a `path`.
|
||||
-- @class function
|
||||
-- @treturn path a `path`
|
||||
-- @usage local p = path:clone()
|
||||
function Path:clone()
|
||||
local p = Path:new()
|
||||
for node in self:nodes() do p:addNode(node) end
|
||||
return p
|
||||
end
|
||||
|
||||
--- Checks if a `path` is equal to another. It also supports *filtered paths* (see @{Path:filter}).
|
||||
-- @class function
|
||||
-- @tparam path p2 a path
|
||||
-- @treturn boolean a boolean
|
||||
-- @usage print(myPath:isEqualTo(anotherPath))
|
||||
function Path:isEqualTo(p2)
|
||||
local p1 = self:clone():filter()
|
||||
local p2 = p2:clone():filter()
|
||||
for node, count in p1:nodes() do
|
||||
if not p2._nodes[count] then return false end
|
||||
local n = p2._nodes[count]
|
||||
if n._x~=node._x or n._y~=node._y then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Reverses a `path`.
|
||||
-- @class function
|
||||
-- @treturn path self (the calling `path` itself, can be chained)
|
||||
-- @usage myPath:reverse()
|
||||
function Path:reverse()
|
||||
local _nodes = {}
|
||||
for i = #self._nodes,1,-1 do
|
||||
_nodes[#_nodes+1] = self._nodes[i]
|
||||
end
|
||||
self._nodes = _nodes
|
||||
return self
|
||||
end
|
||||
|
||||
--- Appends a given `path` to self.
|
||||
-- @class function
|
||||
-- @tparam path p a path
|
||||
-- @treturn path self (the calling `path` itself, can be chained)
|
||||
-- @usage myPath:append(anotherPath)
|
||||
function Path:append(p)
|
||||
for node in p:nodes() do self:addNode(node) end
|
||||
return self
|
||||
end
|
||||
|
||||
return setmetatable(Path,
|
||||
{__call = function(self,...)
|
||||
{__call = function(_,...)
|
||||
return Path:new(...)
|
||||
end
|
||||
})
|
||||
|
@ -5,125 +5,20 @@ if (...) then
|
||||
-- Dependencies
|
||||
local _PATH = (...):gsub('%.utils$','')
|
||||
local Path = require (_PATH .. '.path')
|
||||
local Node = require (_PATH .. '.node')
|
||||
|
||||
-- Local references
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local t_insert = table.insert
|
||||
local assert = assert
|
||||
local coroutine = coroutine
|
||||
|
||||
-- Raw array items count
|
||||
local function arraySize(t)
|
||||
local count = 0
|
||||
for k,v in pairs(t) do
|
||||
for _ in pairs(t) do
|
||||
count = count+1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- Parses a string map and builds an array map
|
||||
local function stringMapToArray(str)
|
||||
local map = {}
|
||||
local w, h
|
||||
for line in str:gmatch('[^\n\r]+') do
|
||||
if line then
|
||||
w = not w and #line or w
|
||||
assert(#line == w, 'Error parsing map, rows must have the same size!')
|
||||
h = (h or 0) + 1
|
||||
map[h] = {}
|
||||
for char in line:gmatch('.') do
|
||||
map[h][#map[h]+1] = char
|
||||
end
|
||||
end
|
||||
end
|
||||
return map
|
||||
end
|
||||
|
||||
-- Collects and returns the keys of a given array
|
||||
local function getKeys(t)
|
||||
local keys = {}
|
||||
for k,v in pairs(t) do keys[#keys+1] = k end
|
||||
return keys
|
||||
end
|
||||
|
||||
-- Calculates the bounds of a 2d array
|
||||
local function getArrayBounds(map)
|
||||
local min_x, max_x
|
||||
local min_y, max_y
|
||||
for y in pairs(map) do
|
||||
min_y = not min_y and y or (y<min_y and y or min_y)
|
||||
max_y = not max_y and y or (y>max_y and y or max_y)
|
||||
for x in pairs(map[y]) do
|
||||
min_x = not min_x and x or (x<min_x and x or min_x)
|
||||
max_x = not max_x and x or (x>max_x and x or max_x)
|
||||
end
|
||||
end
|
||||
return min_x,max_x,min_y,max_y
|
||||
end
|
||||
|
||||
-- Converts an array to a set of nodes
|
||||
local function arrayToNodes(map)
|
||||
local min_x, max_x
|
||||
local min_y, max_y
|
||||
local min_z, max_z
|
||||
local nodes = {}
|
||||
for y in pairs(map) do
|
||||
min_y = not min_y and y or (y<min_y and y or min_y)
|
||||
max_y = not max_y and y or (y>max_y and y or max_y)
|
||||
nodes[y] = {}
|
||||
for x in pairs(map[y]) do
|
||||
min_x = not min_x and x or (x<min_x and x or min_x)
|
||||
max_x = not max_x and x or (x>max_x and x or max_x)
|
||||
nodes[y][x] = {}
|
||||
for z in pairs(map[y][x]) do
|
||||
min_z = not min_z and z or (z<min_z and z or min_z)
|
||||
max_z = not max_z and z or (z>max_z and z or max_z)
|
||||
nodes[y][x][z] = Node:new(x,y,z)
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes,
|
||||
(min_x or 0), (max_x or 0),
|
||||
(min_y or 0), (max_y or 0),
|
||||
(min_z or 0), (max_z or 0)
|
||||
end
|
||||
|
||||
-- Iterator, wrapped within a coroutine
|
||||
-- Iterates around a given position following the outline of a square
|
||||
local function around()
|
||||
local iterf = function(x0, y0, z0, s)
|
||||
local x, y, z = x0-s, y0-s, z0-s
|
||||
coroutine.yield(x, y, z)
|
||||
repeat
|
||||
x = x + 1
|
||||
coroutine.yield(x,y,z)
|
||||
until x == x0+s
|
||||
repeat
|
||||
y = y + 1
|
||||
coroutine.yield(x,y,z)
|
||||
until y == y0 + s
|
||||
repeat
|
||||
z = z + 1
|
||||
coroutine.yield(x,y,z)
|
||||
until z == z0 + s
|
||||
repeat
|
||||
x = x - 1
|
||||
coroutine.yield(x, y,z)
|
||||
until x == x0-s
|
||||
repeat
|
||||
y = y - 1
|
||||
coroutine.yield(x,y,z)
|
||||
until y == y0-s+1
|
||||
repeat
|
||||
z = z - 1
|
||||
coroutine.yield(x,y,z)
|
||||
until z == z0-s+1
|
||||
end
|
||||
return coroutine.create(iterf)
|
||||
end
|
||||
|
||||
-- Extract a path from a given start/end position
|
||||
local function traceBackPath(finder, node, startNode)
|
||||
local path = Path:new()
|
||||
@ -151,17 +46,11 @@ if (...) then
|
||||
local function outOfRange(i,low,up)
|
||||
return (i< low or i > up)
|
||||
end
|
||||
|
||||
|
||||
return {
|
||||
arraySize = arraySize,
|
||||
getKeys = getKeys,
|
||||
indexOf = indexOf,
|
||||
outOfRange = outOfRange,
|
||||
getArrayBounds = getArrayBounds,
|
||||
arrayToNodes = arrayToNodes,
|
||||
strToMap = stringMapToArray,
|
||||
around = around,
|
||||
drAround = drAround,
|
||||
traceBackPath = traceBackPath
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
-- Implementation of the `grid` class.
|
||||
-- The `grid` is a implicit graph which represents the 2D
|
||||
-- world map layout on which the `pathfinder` object will run.
|
||||
-- During a search, the `pathfinder` object needs to save some critical values. These values are cached within each `node`
|
||||
-- During a search, the `pathfinder` object needs to save some critical values.
|
||||
-- These values are cached within each `node`
|
||||
-- object, and the whole set of nodes are tight inside the `grid` object itself.
|
||||
|
||||
if (...) then
|
||||
@ -12,16 +13,10 @@ if (...) then
|
||||
|
||||
-- Local references
|
||||
local Utils = require (_PATH .. '.core.utils')
|
||||
local Assert = require (_PATH .. '.core.assert')
|
||||
local Node = require (_PATH .. '.core.node')
|
||||
|
||||
-- Local references
|
||||
local pairs = pairs
|
||||
local assert = assert
|
||||
local next = next
|
||||
local setmetatable = setmetatable
|
||||
local floor = math.floor
|
||||
local coroutine = coroutine
|
||||
|
||||
-- Offsets for straights moves
|
||||
local straightOffsets = {
|
||||
@ -30,12 +25,6 @@ if (...) then
|
||||
{x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
|
||||
}
|
||||
|
||||
-- Offsets for diagonal moves
|
||||
local diagonalOffsets = {
|
||||
{x = -1, y = -1} --[[NW]], {x = 1, y = -1}, --[[NE]]
|
||||
{x = -1, y = 1} --[[SW]], {x = 1, y = 1}, --[[SE]]
|
||||
}
|
||||
|
||||
--- The `Grid` class.<br/>
|
||||
-- This class is callable.
|
||||
-- Therefore,_ <code>Grid(...)</code> _acts as a shortcut to_ <code>Grid:new(...)</code>.
|
||||
@ -43,46 +32,21 @@ if (...) then
|
||||
local Grid = {}
|
||||
Grid.__index = Grid
|
||||
|
||||
-- Specialized grids
|
||||
local PreProcessGrid = setmetatable({},Grid)
|
||||
local PostProcessGrid = setmetatable({},Grid)
|
||||
PreProcessGrid.__index = PreProcessGrid
|
||||
PostProcessGrid.__index = PostProcessGrid
|
||||
PreProcessGrid.__call = function (self,x,y,z)
|
||||
return self:getNodeAt(x,y,z)
|
||||
end
|
||||
PostProcessGrid.__call = function (self,x,y,z,create)
|
||||
if create then return self:getNodeAt(x,y,z) end
|
||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z]
|
||||
end
|
||||
|
||||
--- Inits a new `grid`
|
||||
-- @class function
|
||||
-- @tparam table|string map A collision map - (2D array) with consecutive indices (starting at 0 or 1)
|
||||
-- @tparam table Map dimensions
|
||||
-- or a `string` with line-break chars (<code>\n</code> or <code>\r</code>) as row delimiters.
|
||||
-- @tparam[opt] bool cacheNodeAtRuntime When __true__, returns an empty `grid` instance, so that
|
||||
-- later on, indexing a non-cached `node` will cause it to be created and cache within the `grid` on purpose (i.e, when needed).
|
||||
-- This is a __memory-safe__ option, in case your dealing with some tight memory constraints.
|
||||
-- Defaults to __false__ when omitted.
|
||||
-- @treturn grid a new `grid` instance
|
||||
-- @usage
|
||||
-- -- A simple 3x3 grid
|
||||
-- local myGrid = Grid:new({{0,0,0},{0,0,0},{0,0,0}})
|
||||
--
|
||||
-- -- A memory-safe 3x3 grid
|
||||
-- myGrid = Grid('000\n000\n000', true)
|
||||
function Grid:new(map, cacheNodeAtRuntime)
|
||||
if type(map) == 'string' then
|
||||
assert(Assert.isStrMap(map), 'Wrong argument #1. Not a valid string map')
|
||||
map = Utils.strToMap(map)
|
||||
end
|
||||
--assert(Assert.isMap(map),('Bad argument #1. Not a valid map'))
|
||||
assert(Assert.isBool(cacheNodeAtRuntime) or Assert.isNil(cacheNodeAtRuntime),
|
||||
('Bad argument #2. Expected \'boolean\', got %s.'):format(type(cacheNodeAtRuntime)))
|
||||
if cacheNodeAtRuntime then
|
||||
return PostProcessGrid:new(map,walkable)
|
||||
end
|
||||
return PreProcessGrid:new(map,walkable)
|
||||
function Grid:new(dim)
|
||||
local newGrid = { }
|
||||
newGrid._min_x, newGrid._max_x = dim.x, dim.ex
|
||||
newGrid._min_y, newGrid._max_y = dim.y, dim.ey
|
||||
newGrid._min_z, newGrid._max_z = dim.z, dim.ez
|
||||
newGrid._nodes = { }
|
||||
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
||||
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
||||
newGrid._length = (newGrid._max_z-newGrid._min_z)+1
|
||||
return setmetatable(newGrid,Grid)
|
||||
end
|
||||
|
||||
--- Checks if `node` at [x,y] is __walkable__.
|
||||
@ -90,41 +54,11 @@ if (...) then
|
||||
-- @class function
|
||||
-- @tparam int x the x-location of the node
|
||||
-- @tparam int y the y-location of the node
|
||||
-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
|
||||
-- Defaults to __false__ when omitted.
|
||||
-- If this parameter is a function, it should be prototyped as __f(value)__ and return a `boolean`:
|
||||
-- __true__ when value matches a __walkable__ `node`, __false__ otherwise. If this parameter is not given
|
||||
-- while location [x,y] __is valid__, this actual function returns __true__.
|
||||
-- @tparam[optchain] int clearance the amount of clearance needed. Defaults to 1 (normal clearance) when not given.
|
||||
-- @treturn bool __true__ if `node` exists and is __walkable__, __false__ otherwise
|
||||
-- @usage
|
||||
-- -- Always true
|
||||
-- print(myGrid:isWalkableAt(2,3))
|
||||
-- @tparam int z the z-location of the node
|
||||
--
|
||||
-- -- True if node at [2,3] collision map value is 0
|
||||
-- print(myGrid:isWalkableAt(2,3,0))
|
||||
--
|
||||
-- -- True if node at [2,3] collision map value is 0 and has a clearance higher or equal to 2
|
||||
-- print(myGrid:isWalkableAt(2,3,0,2))
|
||||
--
|
||||
function Grid:isWalkableAt(x, y, z, walkable, clearance)
|
||||
local nodeValue = self._map[y] and self._map[y][x] and self._map[y][x][z]
|
||||
if nodeValue then
|
||||
if not walkable then return true end
|
||||
else
|
||||
return false
|
||||
end
|
||||
local hasEnoughClearance = not clearance and true or false
|
||||
if not hasEnoughClearance then
|
||||
if not self._isAnnotated[walkable] then return false end
|
||||
local node = self:getNodeAt(x,y,z)
|
||||
local nodeClearance = node:getClearance(walkable)
|
||||
hasEnoughClearance = (nodeClearance >= clearance)
|
||||
end
|
||||
if self._eval then
|
||||
return walkable(nodeValue) and hasEnoughClearance
|
||||
end
|
||||
return ((nodeValue == walkable) and hasEnoughClearance)
|
||||
function Grid:isWalkableAt(x, y, z)
|
||||
local node = self:getNodeAt(x,y,z)
|
||||
return node and node.walkable ~= 1
|
||||
end
|
||||
|
||||
--- Returns the `grid` width.
|
||||
@ -143,14 +77,6 @@ if (...) then
|
||||
return self._height
|
||||
end
|
||||
|
||||
--- Returns the collision map.
|
||||
-- @class function
|
||||
-- @treturn map the collision map (see @{Grid:new})
|
||||
-- @usage local map = myGrid:getMap()
|
||||
function Grid:getMap()
|
||||
return self._map
|
||||
end
|
||||
|
||||
--- Returns the set of nodes.
|
||||
-- @class function
|
||||
-- @treturn {{node,...},...} an array of nodes
|
||||
@ -174,18 +100,14 @@ if (...) then
|
||||
--- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.
|
||||
-- @class function
|
||||
-- @tparam node node a given `node`
|
||||
-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
|
||||
-- Defaults to __false__ when omitted.
|
||||
-- @tparam[optchain] bool allowDiagonal when __true__, allows adjacent nodes are included (8-neighbours).
|
||||
-- Defaults to __false__ when omitted.
|
||||
-- @tparam[optchain] bool tunnel When __true__, allows the `pathfinder` to tunnel through walls when heading diagonally.
|
||||
-- @tparam[optchain] int clearance When given, will prune for the neighbours set all nodes having a clearance value lower than the passed-in value
|
||||
-- @tparam[opt] string|int|func walkable the value for walkable locations
|
||||
-- in the collision map array (see @{Grid:new}).
|
||||
-- Defaults to __false__ when omitted.
|
||||
-- @treturn {node,...} an array of nodes neighbouring a given node
|
||||
-- @usage
|
||||
-- local aNode = myGrid:getNodeAt(5,6)
|
||||
-- local neighbours = myGrid:getNeighbours(aNode, 0, true)
|
||||
function Grid:getNeighbours(node, walkable, allowDiagonal, tunnel, clearance)
|
||||
function Grid:getNeighbours(node)
|
||||
local neighbours = {}
|
||||
for i = 1,#straightOffsets do
|
||||
local n = self:getNodeAt(
|
||||
@ -193,227 +115,31 @@ if (...) then
|
||||
node._y + straightOffsets[i].y,
|
||||
node._z + straightOffsets[i].z
|
||||
)
|
||||
if n and self:isWalkableAt(n._x, n._y, n._z, walkable, clearance) then
|
||||
if n and self:isWalkableAt(n._x, n._y, n._z) then
|
||||
neighbours[#neighbours+1] = n
|
||||
end
|
||||
end
|
||||
|
||||
if not allowDiagonal then return neighbours end
|
||||
|
||||
tunnel = not not tunnel
|
||||
for i = 1,#diagonalOffsets do
|
||||
local n = self:getNodeAt(
|
||||
node._x + diagonalOffsets[i].x,
|
||||
node._y + diagonalOffsets[i].y
|
||||
)
|
||||
if n and self:isWalkableAt(n._x, n._y, walkable, clearance) then
|
||||
if tunnel then
|
||||
neighbours[#neighbours+1] = n
|
||||
else
|
||||
local skipThisNode = false
|
||||
local n1 = self:getNodeAt(node._x+diagonalOffsets[i].x, node._y)
|
||||
local n2 = self:getNodeAt(node._x, node._y+diagonalOffsets[i].y)
|
||||
if ((n1 and n2) and not self:isWalkableAt(n1._x, n1._y, walkable, clearance) and not self:isWalkableAt(n2._x, n2._y, walkable, clearance)) then
|
||||
skipThisNode = true
|
||||
end
|
||||
if not skipThisNode then neighbours[#neighbours+1] = n end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return neighbours
|
||||
end
|
||||
|
||||
--- Grid iterator. Iterates on every single node
|
||||
-- in the `grid`. Passing __lx, ly, ex, ey__ arguments will iterate
|
||||
-- only on nodes inside the bounding-rectangle delimited by those given coordinates.
|
||||
-- @class function
|
||||
-- @tparam[opt] int lx the leftmost x-coordinate of the rectangle. Default to the `grid` leftmost x-coordinate (see @{Grid:getBounds}).
|
||||
-- @tparam[optchain] int ly the topmost y-coordinate of the rectangle. Default to the `grid` topmost y-coordinate (see @{Grid:getBounds}).
|
||||
-- @tparam[optchain] int ex the rightmost x-coordinate of the rectangle. Default to the `grid` rightmost x-coordinate (see @{Grid:getBounds}).
|
||||
-- @tparam[optchain] int ey the bottom-most y-coordinate of the rectangle. Default to the `grid` bottom-most y-coordinate (see @{Grid:getBounds}).
|
||||
-- @treturn node a `node` on the collision map, upon each iteration step
|
||||
-- @treturn int the iteration count
|
||||
-- @usage
|
||||
-- for node, count in myGrid:iter() do
|
||||
-- print(node:getX(), node:getY(), count)
|
||||
-- end
|
||||
function Grid:iter(lx,ly,lz,ex,ey,ez)
|
||||
local min_x = lx or self._min_x
|
||||
local min_y = ly or self._min_y
|
||||
local min_z = lz or self._min_z
|
||||
local max_x = ex or self._max_x
|
||||
local max_y = ey or self._max_y
|
||||
local max_z = ez or self._max_z
|
||||
|
||||
local x, y, z
|
||||
z = min_z
|
||||
return function()
|
||||
x = not x and min_x or x+1
|
||||
if x > max_x then
|
||||
x = min_x
|
||||
y = y+1
|
||||
end
|
||||
y = not y and min_y or y+1
|
||||
if y > max_y then
|
||||
y = min_y
|
||||
z = z+1
|
||||
end
|
||||
if z > max_z then
|
||||
z = nil
|
||||
end
|
||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or self:getNodeAt(x,y,z)
|
||||
end
|
||||
end
|
||||
|
||||
--- Grid iterator. Iterates on each node along the outline (border) of a squared area
|
||||
-- centered on the given node.
|
||||
-- @tparam node node a given `node`
|
||||
-- @tparam[opt] int radius the area radius (half-length). Defaults to __1__ when not given.
|
||||
-- @treturn node a `node` at each iteration step
|
||||
-- @usage
|
||||
-- for node in myGrid:around(node, 2) do
|
||||
-- ...
|
||||
-- end
|
||||
function Grid:around(node, radius)
|
||||
local x, y, z = node._x, node._y, node._z
|
||||
radius = radius or 1
|
||||
local _around = Utils.around()
|
||||
local _nodes = {}
|
||||
repeat
|
||||
local state, x, y, z = coroutine.resume(_around,x,y,z,radius)
|
||||
local nodeAt = state and self:getNodeAt(x, y, z)
|
||||
if nodeAt then _nodes[#_nodes+1] = nodeAt end
|
||||
until (not state)
|
||||
local _i = 0
|
||||
return function()
|
||||
_i = _i+1
|
||||
return _nodes[_i]
|
||||
end
|
||||
end
|
||||
|
||||
--- Each transformation. Calls the given function on each `node` in the `grid`,
|
||||
-- passing the `node` as the first argument to function __f__.
|
||||
-- @class function
|
||||
-- @tparam func f a function prototyped as __f(node,...)__
|
||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
||||
-- @usage
|
||||
-- local function printNode(node)
|
||||
-- print(node:getX(), node:getY())
|
||||
-- end
|
||||
-- myGrid:each(printNode)
|
||||
function Grid:each(f,...)
|
||||
for node in self:iter() do f(node,...) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Each (in range) transformation. Calls a function on each `node` in the range of a rectangle of cells,
|
||||
-- passing the `node` as the first argument to function __f__.
|
||||
-- @class function
|
||||
-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
|
||||
-- @tparam int ly the topmost y-coordinate of the rectangle
|
||||
-- @tparam int ex the rightmost x-coordinate of the rectangle
|
||||
-- @tparam int ey the bottom-most y-coordinate of the rectangle
|
||||
-- @tparam func f a function prototyped as __f(node,...)__
|
||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
||||
-- @usage
|
||||
-- local function printNode(node)
|
||||
-- print(node:getX(), node:getY())
|
||||
-- end
|
||||
-- myGrid:eachRange(1,1,8,8,printNode)
|
||||
function Grid:eachRange(lx,ly,ex,ey,f,...)
|
||||
for node in self:iter(lx,ly,ex,ey) do f(node,...) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Map transformation.
|
||||
-- Calls function __f(node,...)__ on each `node` in a given range, passing the `node` as the first arg to function __f__ and replaces
|
||||
-- it with the returned value. Therefore, the function should return a `node`.
|
||||
-- @class function
|
||||
-- @tparam func f a function prototyped as __f(node,...)__
|
||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
||||
-- @usage
|
||||
-- local function nothing(node)
|
||||
-- return node
|
||||
-- end
|
||||
-- myGrid:imap(nothing)
|
||||
function Grid:imap(f,...)
|
||||
for node in self:iter() do
|
||||
node = f(node,...)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Map in range transformation.
|
||||
-- Calls function __f(node,...)__ on each `node` in a rectangle range, passing the `node` as the first argument to the function and replaces
|
||||
-- it with the returned value. Therefore, the function should return a `node`.
|
||||
-- @class function
|
||||
-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
|
||||
-- @tparam int ly the topmost y-coordinate of the rectangle
|
||||
-- @tparam int ex the rightmost x-coordinate of the rectangle
|
||||
-- @tparam int ey the bottom-most y-coordinate of the rectangle
|
||||
-- @tparam func f a function prototyped as __f(node,...)__
|
||||
-- @tparam[opt] vararg ... args to be passed to function __f__
|
||||
-- @treturn grid self (the calling `grid` itself, can be chained)
|
||||
-- @usage
|
||||
-- local function nothing(node)
|
||||
-- return node
|
||||
-- end
|
||||
-- myGrid:imap(1,1,6,6,nothing)
|
||||
function Grid:imapRange(lx,ly,ex,ey,f,...)
|
||||
for node in self:iter(lx,ly,ex,ey) do
|
||||
node = f(node,...)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- Specialized grids
|
||||
-- Inits a preprocessed grid
|
||||
function PreProcessGrid:new(map)
|
||||
local newGrid = {}
|
||||
newGrid._map = map
|
||||
newGrid._nodes, newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y, newGrid._min_z, newGrid._max_z = Utils.arrayToNodes(newGrid._map)
|
||||
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
||||
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
||||
newGrid._length = (newGrid._max_z-newGrid._min_z)+1
|
||||
newGrid._isAnnotated = {}
|
||||
return setmetatable(newGrid,PreProcessGrid)
|
||||
end
|
||||
|
||||
-- Inits a postprocessed grid
|
||||
function PostProcessGrid:new(map)
|
||||
local newGrid = {}
|
||||
newGrid._map = map
|
||||
newGrid._nodes = {}
|
||||
newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y = Utils.getArrayBounds(newGrid._map)
|
||||
newGrid._width = (newGrid._max_x-newGrid._min_x)+1
|
||||
newGrid._height = (newGrid._max_y-newGrid._min_y)+1
|
||||
newGrid._isAnnotated = {}
|
||||
return setmetatable(newGrid,PostProcessGrid)
|
||||
end
|
||||
|
||||
--- Returns the `node` at location [x,y].
|
||||
--- Returns the `node` at location [x,y,z].
|
||||
-- @class function
|
||||
-- @name Grid:getNodeAt
|
||||
-- @tparam int x the x-coordinate coordinate
|
||||
-- @tparam int y the y-coordinate coordinate
|
||||
-- @tparam int z the z-coordinate coordinate
|
||||
-- @treturn node a `node`
|
||||
-- @usage local aNode = myGrid:getNodeAt(2,2)
|
||||
|
||||
-- Gets the node at location <x,y> on a preprocessed grid
|
||||
function PreProcessGrid:getNodeAt(x,y,z)
|
||||
return self._nodes[y] and self._nodes[y][x] and self._nodes[y][x][z] or nil
|
||||
end
|
||||
|
||||
-- Gets the node at location <x,y> on a postprocessed grid
|
||||
function PostProcessGrid:getNodeAt(x,y,z)
|
||||
function Grid:getNodeAt(x,y,z)
|
||||
if not x or not y or not z then return end
|
||||
if Utils.outOfRange(x,self._min_x,self._max_x) then return end
|
||||
if Utils.outOfRange(y,self._min_y,self._max_y) then return end
|
||||
if Utils.outOfRange(z,self._min_z,self._max_z) then return end
|
||||
|
||||
-- inefficient
|
||||
if not self._nodes[y] then self._nodes[y] = {} end
|
||||
if not self._nodes[y][x] then self._nodes[y][x] = {} end
|
||||
if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end
|
||||
|
@ -41,53 +41,24 @@ if (...) then
|
||||
-- Dependencies
|
||||
local _PATH = (...):gsub('%.pathfinder$','')
|
||||
local Utils = require (_PATH .. '.core.utils')
|
||||
local Assert = require (_PATH .. '.core.assert')
|
||||
local Heap = require (_PATH .. '.core.bheap')
|
||||
local Heuristic = require (_PATH .. '.core.heuristics')
|
||||
local Grid = require (_PATH .. '.grid')
|
||||
local Path = require (_PATH .. '.core.path')
|
||||
|
||||
-- Internalization
|
||||
local t_insert, t_remove = table.insert, table.remove
|
||||
local floor = math.floor
|
||||
local pairs = pairs
|
||||
local assert = assert
|
||||
local type = type
|
||||
local setmetatable, getmetatable = setmetatable, getmetatable
|
||||
local setmetatable = setmetatable
|
||||
|
||||
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
|
||||
--
|
||||
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
|
||||
-- <li>[Dijkstra](http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)</li>
|
||||
-- <li>[Theta Astar](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)</li>
|
||||
-- <li>[BFS](http://en.wikipedia.org/wiki/Breadth-first_search)</li>
|
||||
-- <li>[DFS](http://en.wikipedia.org/wiki/Depth-first_search)</li>
|
||||
-- <li>[JPS](http://harablog.wordpress.com/2011/09/07/jump-point-search/)</li>
|
||||
-- @finder Finders
|
||||
-- @see Pathfinder:getFinders
|
||||
local Finders = {
|
||||
['ASTAR'] = require (_PATH .. '.search.astar'),
|
||||
-- ['DIJKSTRA'] = require (_PATH .. '.search.dijkstra'),
|
||||
-- ['THETASTAR'] = require (_PATH .. '.search.thetastar'),
|
||||
['BFS'] = require (_PATH .. '.search.bfs'),
|
||||
-- ['DFS'] = require (_PATH .. '.search.dfs'),
|
||||
-- ['JPS'] = require (_PATH .. '.search.jps')
|
||||
}
|
||||
|
||||
-- Will keep track of all nodes expanded during the search
|
||||
-- to easily reset their properties for the next pathfinding call
|
||||
local toClear = {}
|
||||
|
||||
--- Search modes. Refers to the search modes. In ORTHOGONAL mode, 4-directions are only possible when moving,
|
||||
-- including North, East, West, South. In DIAGONAL mode, 8-directions are possible when moving,
|
||||
-- including North, East, West, South and adjacent directions.
|
||||
--
|
||||
-- <li>ORTHOGONAL</li>
|
||||
-- <li>DIAGONAL</li>
|
||||
-- @mode Modes
|
||||
-- @see Pathfinder:getModes
|
||||
local searchModes = {['DIAGONAL'] = true, ['ORTHOGONAL'] = true}
|
||||
|
||||
-- Performs a traceback from the goal node to the start node
|
||||
-- Only happens when the path was found
|
||||
|
||||
@ -103,259 +74,27 @@ if (...) then
|
||||
-- @tparam grid grid a `grid`
|
||||
-- @tparam[opt] string finderName the name of the `Finder` (search algorithm) to be used for search.
|
||||
-- Defaults to `ASTAR` when not given (see @{Pathfinder:getFinders}).
|
||||
-- @tparam[optchain] string|int|func walkable the value for __walkable__ nodes.
|
||||
-- If this parameter is a function, it should be prototyped as __f(value)__, returning a boolean:
|
||||
-- __true__ when value matches a __walkable__ `node`, __false__ otherwise.
|
||||
-- @treturn pathfinder a new `pathfinder` instance
|
||||
-- @usage
|
||||
-- -- Example one
|
||||
-- local finder = Pathfinder:new(myGrid, 'ASTAR', 0)
|
||||
--
|
||||
-- -- Example two
|
||||
-- local function walkable(value)
|
||||
-- return value > 0
|
||||
-- end
|
||||
-- local finder = Pathfinder(myGrid, 'JPS', walkable)
|
||||
function Pathfinder:new(grid, finderName, walkable)
|
||||
-- local finder = Pathfinder:new(myGrid, 'ASTAR')
|
||||
function Pathfinder:new(heuristic)
|
||||
local newPathfinder = {}
|
||||
setmetatable(newPathfinder, Pathfinder)
|
||||
--newPathfinder:setGrid(grid)
|
||||
newPathfinder:setFinder(finderName)
|
||||
--newPathfinder:setWalkable(walkable)
|
||||
newPathfinder:setMode('DIAGONAL')
|
||||
newPathfinder:setHeuristic('MANHATTAN')
|
||||
newPathfinder:setTunnelling(false)
|
||||
self._finder = Finders.ASTAR
|
||||
self._heuristic = heuristic
|
||||
return newPathfinder
|
||||
end
|
||||
|
||||
--- Evaluates [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
|
||||
-- for the whole `grid`. It should be called only once, unless the collision map or the
|
||||
-- __walkable__ attribute changes. The clearance values are calculated and cached within the grid nodes.
|
||||
-- @class function
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage myFinder:annotateGrid()
|
||||
function Pathfinder:annotateGrid()
|
||||
assert(self._walkable, 'Finder must implement a walkable value')
|
||||
for x=self._grid._max_x,self._grid._min_x,-1 do
|
||||
for y=self._grid._max_y,self._grid._min_y,-1 do
|
||||
local node = self._grid:getNodeAt(x,y)
|
||||
if self._grid:isWalkableAt(x,y,self._walkable) then
|
||||
local nr = self._grid:getNodeAt(node._x+1, node._y)
|
||||
local nrd = self._grid:getNodeAt(node._x+1, node._y+1)
|
||||
local nd = self._grid:getNodeAt(node._x, node._y+1)
|
||||
if nr and nrd and nd then
|
||||
local m = nrd._clearance[self._walkable] or 0
|
||||
m = (nd._clearance[self._walkable] or 0)<m and (nd._clearance[self._walkable] or 0) or m
|
||||
m = (nr._clearance[self._walkable] or 0)<m and (nr._clearance[self._walkable] or 0) or m
|
||||
node._clearance[self._walkable] = m+1
|
||||
else
|
||||
node._clearance[self._walkable] = 1
|
||||
end
|
||||
else node._clearance[self._walkable] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
self._grid._isAnnotated[self._walkable] = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)values.
|
||||
-- Clears cached clearance values for the current __walkable__.
|
||||
-- @class function
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage myFinder:clearAnnotations()
|
||||
function Pathfinder:clearAnnotations()
|
||||
assert(self._walkable, 'Finder must implement a walkable value')
|
||||
for node in self._grid:iter() do
|
||||
node:removeClearance(self._walkable)
|
||||
end
|
||||
self._grid._isAnnotated[self._walkable] = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the `grid`. Defines the given `grid` as the one on which the `pathfinder` will perform the search.
|
||||
-- @class function
|
||||
-- @tparam grid grid a `grid`
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage myFinder:setGrid(myGrid)
|
||||
function Pathfinder:setGrid(grid)
|
||||
assert(Assert.inherits(grid, Grid), 'Wrong argument #1. Expected a \'grid\' object')
|
||||
self._grid = grid
|
||||
self._grid._eval = self._walkable and type(self._walkable) == 'function'
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the `grid`. This is a reference to the actual `grid` used by the `pathfinder`.
|
||||
-- @class function
|
||||
-- @treturn grid the `grid`
|
||||
-- @usage local myGrid = myFinder:getGrid()
|
||||
function Pathfinder:getGrid()
|
||||
return self._grid
|
||||
end
|
||||
|
||||
--- Sets the __walkable__ value or function.
|
||||
-- @class function
|
||||
-- @tparam string|int|func walkable the value for walkable nodes.
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage
|
||||
-- -- Value '0' is walkable
|
||||
-- myFinder:setWalkable(0)
|
||||
--
|
||||
-- -- Any value greater than 0 is walkable
|
||||
-- myFinder:setWalkable(function(n)
|
||||
-- return n>0
|
||||
-- end
|
||||
function Pathfinder:setWalkable(walkable)
|
||||
assert(Assert.matchType(walkable,'stringintfunctionnil'),
|
||||
('Wrong argument #1. Expected \'string\', \'number\' or \'function\', got %s.'):format(type(walkable)))
|
||||
self._walkable = walkable
|
||||
self._grid._eval = type(self._walkable) == 'function'
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the __walkable__ value or function.
|
||||
-- @class function
|
||||
-- @treturn string|int|func the `walkable` value or function
|
||||
-- @usage local walkable = myFinder:getWalkable()
|
||||
function Pathfinder:getWalkable()
|
||||
return self._walkable
|
||||
end
|
||||
|
||||
--- Defines the `finder`. It refers to the search algorithm used by the `pathfinder`.
|
||||
-- Default finder is `ASTAR`. Use @{Pathfinder:getFinders} to get the list of available finders.
|
||||
-- @class function
|
||||
-- @tparam string finderName the name of the `finder` to be used for further searches.
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage
|
||||
-- --To use Breadth-First-Search
|
||||
-- myFinder:setFinder('BFS')
|
||||
-- @see Pathfinder:getFinders
|
||||
function Pathfinder:setFinder(finderName)
|
||||
if not finderName then
|
||||
if not self._finder then
|
||||
finderName = 'ASTAR'
|
||||
else return
|
||||
end
|
||||
end
|
||||
assert(Finders[finderName],'Not a valid finder name!')
|
||||
self._finder = finderName
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the name of the `finder` being used.
|
||||
-- @class function
|
||||
-- @treturn string the name of the `finder` to be used for further searches.
|
||||
-- @usage local finderName = myFinder:getFinder()
|
||||
function Pathfinder:getFinder()
|
||||
return self._finder
|
||||
end
|
||||
|
||||
--- Returns the list of all available finders names.
|
||||
-- @class function
|
||||
-- @treturn {string,...} array of built-in finders names.
|
||||
-- @usage
|
||||
-- local finders = myFinder:getFinders()
|
||||
-- for i, finderName in ipairs(finders) do
|
||||
-- print(i, finderName)
|
||||
-- end
|
||||
function Pathfinder:getFinders()
|
||||
return Utils.getKeys(Finders)
|
||||
end
|
||||
|
||||
--- Sets a heuristic. This is a function internally used by the `pathfinder` to find the optimal path during a search.
|
||||
-- Use @{Pathfinder:getHeuristics} to get the list of all available `heuristics`. One can also define
|
||||
-- his own `heuristic` function.
|
||||
-- @class function
|
||||
-- @tparam func|string heuristic `heuristic` function, prototyped as __f(dx,dy)__ or as a `string`.
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @see Pathfinder:getHeuristics
|
||||
-- @see core.heuristics
|
||||
-- @usage myFinder:setHeuristic('MANHATTAN')
|
||||
function Pathfinder:setHeuristic(heuristic)
|
||||
assert(Heuristic[heuristic] or (type(heuristic) == 'function'),'Not a valid heuristic!')
|
||||
self._heuristic = Heuristic[heuristic] or heuristic
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the `heuristic` used. Returns the function itself.
|
||||
-- @class function
|
||||
-- @treturn func the `heuristic` function being used by the `pathfinder`
|
||||
-- @see core.heuristics
|
||||
-- @usage local h = myFinder:getHeuristic()
|
||||
function Pathfinder:getHeuristic()
|
||||
return self._heuristic
|
||||
end
|
||||
|
||||
--- Gets the list of all available `heuristics`.
|
||||
-- @class function
|
||||
-- @treturn {string,...} array of heuristic names.
|
||||
-- @see core.heuristics
|
||||
-- @usage
|
||||
-- local heur = myFinder:getHeuristic()
|
||||
-- for i, heuristicName in ipairs(heur) do
|
||||
-- ...
|
||||
-- end
|
||||
function Pathfinder:getHeuristics()
|
||||
return Utils.getKeys(Heuristic)
|
||||
end
|
||||
|
||||
--- Defines the search `mode`.
|
||||
-- The default search mode is the `DIAGONAL` mode, which implies 8-possible directions when moving (north, south, east, west and diagonals).
|
||||
-- In `ORTHOGONAL` mode, only 4-directions are allowed (north, south, east and west).
|
||||
-- Use @{Pathfinder:getModes} to get the list of all available search modes.
|
||||
-- @class function
|
||||
-- @tparam string mode the new search `mode`.
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @see Pathfinder:getModes
|
||||
-- @see Modes
|
||||
-- @usage myFinder:setMode('ORTHOGONAL')
|
||||
function Pathfinder:setMode(mode)
|
||||
assert(searchModes[mode],'Invalid mode')
|
||||
self._allowDiagonal = (mode == 'DIAGONAL')
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the search mode.
|
||||
-- @class function
|
||||
-- @treturn string the current search mode
|
||||
-- @see Modes
|
||||
-- @usage local mode = myFinder:getMode()
|
||||
function Pathfinder:getMode()
|
||||
return (self._allowDiagonal and 'DIAGONAL' or 'ORTHOGONAL')
|
||||
end
|
||||
|
||||
--- Gets the list of all available search modes.
|
||||
-- @class function
|
||||
-- @treturn {string,...} array of search modes.
|
||||
-- @see Modes
|
||||
-- @usage local modes = myFinder:getModes()
|
||||
-- for modeName in ipairs(modes) do
|
||||
-- ...
|
||||
-- end
|
||||
function Pathfinder:getModes()
|
||||
return Utils.getKeys(searchModes)
|
||||
end
|
||||
|
||||
--- Enables tunnelling. Defines the ability for the `pathfinder` to tunnel through walls when heading diagonally.
|
||||
-- This feature __is not compatible__ with Jump Point Search algorithm (i.e. enabling it will not affect Jump Point Search)
|
||||
-- @class function
|
||||
-- @tparam bool bool a boolean
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
-- @usage myFinder:setTunnelling(true)
|
||||
function Pathfinder:setTunnelling(bool)
|
||||
assert(Assert.isBool(bool), ('Wrong argument #1. Expected boolean, got %s'):format(type(bool)))
|
||||
self._tunnel = bool
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns tunnelling feature state.
|
||||
-- @class function
|
||||
-- @treturn bool tunnelling feature actual state
|
||||
-- @usage local isTunnellingEnabled = myFinder:getTunnelling()
|
||||
function Pathfinder:getTunnelling()
|
||||
return self._tunnel
|
||||
end
|
||||
|
||||
--- Calculates a `path`. Returns the `path` from location __[startX, startY]__ to location __[endX, endY]__.
|
||||
-- Both locations must exist on the collision map. The starting location can be unwalkable.
|
||||
-- @class function
|
||||
@ -363,11 +102,9 @@ if (...) then
|
||||
-- @tparam int startY the y-coordinate for the starting location
|
||||
-- @tparam int endX the x-coordinate for the goal location
|
||||
-- @tparam int endY the y-coordinate for the goal location
|
||||
-- @tparam int clearance the amount of clearance (i.e the pathing agent size) to consider
|
||||
-- @treturn path a path (array of nodes) when found, otherwise nil
|
||||
-- @usage local path = myFinder:getPath(1,1,5,5)
|
||||
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh, clearance)
|
||||
|
||||
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
|
||||
self:reset()
|
||||
local startNode = self._grid:getNodeAt(startX, startY, startZ)
|
||||
local endNode = self._grid:getNodeAt(endX, endY, endZ)
|
||||
@ -381,14 +118,15 @@ if (...) then
|
||||
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
|
||||
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
|
||||
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
|
||||
local _endNode = Finders[self._finder](self, startNode, endNode, clearance, toClear)
|
||||
local _endNode = self._finder(self, startNode, endNode, toClear)
|
||||
if _endNode then
|
||||
return Utils.traceBackPath(self, _endNode, startNode)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Resets the `pathfinder`. This function is called internally between successive pathfinding calls, so you should not
|
||||
--- Resets the `pathfinder`. This function is called internally between
|
||||
-- successive pathfinding calls, so you should not
|
||||
-- use it explicitely, unless under specific circumstances.
|
||||
-- @class function
|
||||
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
|
||||
@ -399,7 +137,6 @@ if (...) then
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- Returns Pathfinder class
|
||||
Pathfinder._VERSION = _VERSION
|
||||
Pathfinder._RELEASEDATE = _RELEASEDATE
|
||||
@ -408,5 +145,4 @@ if (...) then
|
||||
return self:new(...)
|
||||
end
|
||||
})
|
||||
|
||||
end
|
||||
|
@ -5,16 +5,14 @@
|
||||
if (...) then
|
||||
|
||||
-- Internalization
|
||||
local ipairs = ipairs
|
||||
local huge = math.huge
|
||||
|
||||
-- Dependancies
|
||||
local _PATH = (...):match('(.+)%.search.astar$')
|
||||
local Heuristics = require (_PATH .. '.core.heuristics')
|
||||
local Heap = require (_PATH.. '.core.bheap')
|
||||
|
||||
-- Updates G-cost
|
||||
local function computeCost(node, neighbour, finder, clearance, heuristic)
|
||||
local function computeCost(node, neighbour, heuristic)
|
||||
local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
|
||||
|
||||
if node._g + mCost < neighbour._g then
|
||||
@ -25,31 +23,24 @@ if (...) then
|
||||
end
|
||||
|
||||
-- Updates vertex node-neighbour
|
||||
local function updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
|
||||
local function updateVertex(openList, node, neighbour, endNode, heuristic)
|
||||
local oldG = neighbour._g
|
||||
local cmpCost = overrideCostEval or computeCost
|
||||
cmpCost(node, neighbour, finder, clearance, heuristic)
|
||||
computeCost(node, neighbour, heuristic)
|
||||
if neighbour._g < oldG then
|
||||
local nClearance = neighbour._clearance[finder._walkable]
|
||||
local pushThisNode = clearance and nClearance and (nClearance >= clearance)
|
||||
if (clearance and pushThisNode) or (not clearance) then
|
||||
if neighbour._opened then neighbour._opened = false end
|
||||
neighbour._h = heuristic(endNode, neighbour)
|
||||
neighbour._f = neighbour._g + neighbour._h
|
||||
openList:push(neighbour)
|
||||
neighbour._opened = true
|
||||
end
|
||||
if neighbour._opened then neighbour._opened = false end
|
||||
neighbour._h = heuristic(endNode, neighbour)
|
||||
neighbour._f = neighbour._g + neighbour._h
|
||||
openList:push(neighbour)
|
||||
neighbour._opened = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculates a path.
|
||||
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
|
||||
return function (finder, startNode, endNode, clearance, toClear, overrideHeuristic, overrideCostEval)
|
||||
|
||||
local heuristic = overrideHeuristic or finder._heuristic
|
||||
return function (finder, startNode, endNode, toClear)
|
||||
local openList = Heap()
|
||||
startNode._g = 0
|
||||
startNode._h = heuristic(endNode, startNode)
|
||||
startNode._h = finder._heuristic(endNode, startNode)
|
||||
startNode._f = startNode._g + startNode._h
|
||||
openList:push(startNode)
|
||||
toClear[startNode] = true
|
||||
@ -59,17 +50,17 @@ if (...) then
|
||||
local node = openList:pop()
|
||||
node._closed = true
|
||||
if node == endNode then return node end
|
||||
local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
|
||||
local neighbours = finder._grid:getNeighbours(node)
|
||||
for i = 1,#neighbours do
|
||||
local neighbour = neighbours[i]
|
||||
if not neighbour._closed then
|
||||
toClear[neighbour] = true
|
||||
if not neighbour._opened then
|
||||
neighbour._g = huge
|
||||
neighbour._parent = nil
|
||||
neighbour._parent = nil
|
||||
end
|
||||
updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
|
||||
end
|
||||
updateVertex(openList, node, neighbour, endNode, finder._heuristic)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
@ -81,8 +72,6 @@ if (...) then
|
||||
--]]
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,46 +0,0 @@
|
||||
-- Breadth-First search algorithm
|
||||
|
||||
if (...) then
|
||||
-- Internalization
|
||||
local t_remove = table.remove
|
||||
|
||||
local function breadth_first_search(finder, openList, node, endNode, clearance, toClear)
|
||||
local neighbours = finder._grid:getNeighbours(node, finder._walkable, finder._allowDiagonal, finder._tunnel)
|
||||
for i = 1,#neighbours do
|
||||
local neighbour = neighbours[i]
|
||||
if not neighbour._closed and not neighbour._opened then
|
||||
local nClearance = neighbour._clearance[finder._walkable]
|
||||
local pushThisNode = clearance and nClearance and (nClearance >= clearance)
|
||||
if (clearance and pushThisNode) or (not clearance) then
|
||||
openList[#openList+1] = neighbour
|
||||
neighbour._opened = true
|
||||
neighbour._parent = node
|
||||
toClear[neighbour] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Calculates a path.
|
||||
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
|
||||
return function (finder, startNode, endNode, clearance, toClear)
|
||||
|
||||
local openList = {} -- We'll use a FIFO queue (simple array)
|
||||
openList[1] = startNode
|
||||
startNode._opened = true
|
||||
toClear[startNode] = true
|
||||
|
||||
local node
|
||||
while (#openList > 0) do
|
||||
node = openList[1]
|
||||
t_remove(openList,1)
|
||||
node._closed = true
|
||||
if node == endNode then return node end
|
||||
breadth_first_search(finder, openList, node, endNode, clearance, toClear)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
@ -7,59 +7,46 @@ local Util = require('util')
|
||||
|
||||
local turtle = _G.turtle
|
||||
|
||||
local WALKABLE = 0
|
||||
|
||||
local function createMap(dim)
|
||||
local map = { }
|
||||
for _ = 1, dim.ez do
|
||||
local row = {}
|
||||
for _ = 1, dim.ex do
|
||||
local col = { }
|
||||
for _ = 1, dim.ey do
|
||||
table.insert(col, WALKABLE)
|
||||
end
|
||||
table.insert(row, col)
|
||||
local function addBlock(grid, b, dim)
|
||||
if Point.inBox(b, dim) then
|
||||
local node = grid:getNodeAt(b.x, b.y, b.z)
|
||||
if node then
|
||||
node.walkable = 1
|
||||
end
|
||||
table.insert(map, row)
|
||||
end
|
||||
|
||||
return map
|
||||
end
|
||||
|
||||
local function addBlock(map, dim, b)
|
||||
map[b.z + dim.oz][b.x + dim.ox][b.y + dim.oy] = 1
|
||||
end
|
||||
|
||||
-- map shrinks/grows depending upon blocks encountered
|
||||
-- the map will encompass any blocks encountered, the turtle position, and the destination
|
||||
local function mapDimensions(dest, blocks, boundingBox)
|
||||
local function mapDimensions(dest, blocks, boundingBox, dests)
|
||||
local sx, sz, sy = turtle.point.x, turtle.point.z, turtle.point.y
|
||||
local ex, ez, ey = turtle.point.x, turtle.point.z, turtle.point.y
|
||||
|
||||
local function adjust(pt)
|
||||
if pt.x < sx then
|
||||
sx = pt.x
|
||||
end
|
||||
if pt.z < sz then
|
||||
sz = pt.z
|
||||
elseif pt.x > ex then
|
||||
ex = pt.x
|
||||
end
|
||||
if pt.y < sy then
|
||||
sy = pt.y
|
||||
end
|
||||
if pt.x > ex then
|
||||
ex = pt.x
|
||||
end
|
||||
if pt.z > ez then
|
||||
ez = pt.z
|
||||
end
|
||||
if pt.y > ey then
|
||||
elseif pt.y > ey then
|
||||
ey = pt.y
|
||||
end
|
||||
if pt.z < sz then
|
||||
sz = pt.z
|
||||
elseif pt.z > ez then
|
||||
ez = pt.z
|
||||
end
|
||||
end
|
||||
|
||||
adjust(dest)
|
||||
|
||||
for _,b in ipairs(blocks) do
|
||||
for _,d in pairs(dests) do
|
||||
adjust(d)
|
||||
end
|
||||
|
||||
for _,b in pairs(blocks) do
|
||||
adjust(b)
|
||||
end
|
||||
|
||||
@ -81,29 +68,23 @@ local function mapDimensions(dest, blocks, boundingBox)
|
||||
end
|
||||
|
||||
return {
|
||||
ex = ex - sx + 1,
|
||||
ez = ez - sz + 1,
|
||||
ey = ey - sy + 1,
|
||||
ox = -sx + 1,
|
||||
oz = -sz + 1,
|
||||
oy = -sy + 1
|
||||
ex = ex,
|
||||
ez = ez,
|
||||
ey = ey,
|
||||
x = sx,
|
||||
z = sz,
|
||||
y = sy
|
||||
}
|
||||
end
|
||||
|
||||
-- shifting and coordinate flipping
|
||||
local function pointToMap(dim, pt)
|
||||
return { x = pt.x + dim.ox, z = pt.y + dim.oy, y = pt.z + dim.oz }
|
||||
end
|
||||
|
||||
local function nodeToPoint(dim, node)
|
||||
return { x = node:getX() - dim.ox, z = node:getY() - dim.oz, y = node:getZ() - dim.oy }
|
||||
local function nodeToPoint(node)
|
||||
return { x = node:getX(), z = node:getZ(), y = node:getY() }
|
||||
end
|
||||
|
||||
local heuristic = function(n, node)
|
||||
|
||||
local m, h = Point.calculateMoves(
|
||||
{ x = node._x, z = node._y, y = node._z, heading = node._heading },
|
||||
{ x = n._x, z = n._y, y = n._z, heading = n._heading })
|
||||
{ x = node._x, y = node._y, z = node._z, heading = node._heading },
|
||||
{ x = n._x, y = n._y, z = n._z, heading = n._heading })
|
||||
|
||||
return m, h
|
||||
end
|
||||
@ -112,9 +93,9 @@ local function dimsAreEqual(d1, d2)
|
||||
return d1.ex == d2.ex and
|
||||
d1.ey == d2.ey and
|
||||
d1.ez == d2.ez and
|
||||
d1.ox == d2.ox and
|
||||
d1.oy == d2.oy and
|
||||
d1.oz == d2.oz
|
||||
d1.x == d2.x and
|
||||
d1.y == d2.y and
|
||||
d1.z == d2.z
|
||||
end
|
||||
|
||||
-- turtle sensor returns blocks in relation to the world - not turtle orientation
|
||||
@ -122,7 +103,6 @@ end
|
||||
-- really kinda dumb since it returns the coordinates as offsets of our location
|
||||
-- instead of true coordinates
|
||||
local function addSensorBlocks(blocks, sblocks)
|
||||
|
||||
for _,b in pairs(sblocks) do
|
||||
if b.type ~= 'AIR' then
|
||||
local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
|
||||
@ -142,28 +122,29 @@ local function addSensorBlocks(blocks, sblocks)
|
||||
end
|
||||
end
|
||||
|
||||
local function selectDestination(pts, box, map, dim)
|
||||
|
||||
local function selectDestination(pts, box, grid)
|
||||
if #pts == 1 then
|
||||
return pts[1]
|
||||
end
|
||||
while #pts > 0 do
|
||||
local pt = Point.closest(turtle.point, pts)
|
||||
|
||||
if (box and not Point.inBox(pt, box)) or
|
||||
map[pt.z + dim.oz][pt.x + dim.ox][pt.y + dim.oy] == 1 then
|
||||
if box and not Point.inBox(pt, box) then
|
||||
Util.removeByValue(pts, pt)
|
||||
else
|
||||
if grid:isWalkableAt(pt.x, pt.y, pt.z) then
|
||||
return pt
|
||||
end
|
||||
Util.removeByValue(pts, pt)
|
||||
else
|
||||
return pt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pathTo(dest, options)
|
||||
|
||||
local blocks = options.blocks or turtle.getState().blocks or { }
|
||||
local dests = options.dest or { dest } -- support alternative destinations
|
||||
local box = options.box or turtle.getState().box
|
||||
|
||||
local lastDim = nil
|
||||
local map = nil
|
||||
local grid = nil
|
||||
|
||||
if box then
|
||||
@ -171,32 +152,26 @@ local function pathTo(dest, options)
|
||||
end
|
||||
|
||||
-- Creates a pathfinder object
|
||||
local myFinder = Pathfinder(grid, 'ASTAR', WALKABLE)
|
||||
|
||||
myFinder:setMode('ORTHOGONAL')
|
||||
myFinder:setHeuristic(heuristic)
|
||||
local myFinder = Pathfinder(heuristic)
|
||||
|
||||
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
|
||||
|
||||
-- map expands as we encounter obstacles
|
||||
local dim = mapDimensions(dest, blocks, box)
|
||||
local dim = mapDimensions(dest, blocks, box, dests)
|
||||
|
||||
-- reuse map if possible
|
||||
if not lastDim or not dimsAreEqual(dim, lastDim) then
|
||||
map = createMap(dim)
|
||||
-- Creates a grid object
|
||||
grid = Grid(map)
|
||||
grid = Grid(dim)
|
||||
myFinder:setGrid(grid)
|
||||
myFinder:setWalkable(WALKABLE)
|
||||
|
||||
lastDim = dim
|
||||
end
|
||||
|
||||
for _,b in ipairs(blocks) do
|
||||
addBlock(map, dim, b)
|
||||
for _,b in pairs(blocks) do
|
||||
addBlock(grid, b, dim)
|
||||
end
|
||||
|
||||
dest = selectDestination(dests, box, map, dim)
|
||||
dest = selectDestination(dests, box, grid)
|
||||
if not dest then
|
||||
-- error('failed to reach destination')
|
||||
return false, 'failed to reach destination'
|
||||
@ -206,8 +181,8 @@ local function pathTo(dest, options)
|
||||
end
|
||||
|
||||
-- Define start and goal locations coordinates
|
||||
local startPt = pointToMap(dim, turtle.point)
|
||||
local endPt = pointToMap(dim, dest)
|
||||
local startPt = turtle.point
|
||||
local endPt = dest
|
||||
|
||||
-- Calculates the path, and its length
|
||||
local path = myFinder:getPath(
|
||||
@ -218,7 +193,7 @@ local function pathTo(dest, options)
|
||||
Util.removeByValue(dests, dest)
|
||||
else
|
||||
for node in path:nodes() do
|
||||
local pt = nodeToPoint(dim, node)
|
||||
local pt = nodeToPoint(node)
|
||||
|
||||
if turtle.abort then
|
||||
return false, 'aborted'
|
||||
@ -262,6 +237,12 @@ return {
|
||||
turtle.getState().blocks = blocks
|
||||
end,
|
||||
|
||||
addBlock = function(block)
|
||||
if turtle.getState().blocks then
|
||||
table.insert(turtle.getState().blocks, block)
|
||||
end
|
||||
end,
|
||||
|
||||
reset = function()
|
||||
turtle.getState().box = nil
|
||||
turtle.getState().blocks = nil
|
||||
|
@ -969,8 +969,23 @@ function turtle.abortAction()
|
||||
end
|
||||
|
||||
-- [[ Pathing ]] --
|
||||
function turtle.faceAgainst(pt, options) -- 4 sided
|
||||
function turtle.setPersistent(isPersistent)
|
||||
if isPersistent then
|
||||
Pathing.setBlocks({ })
|
||||
else
|
||||
Pathing.setBlocks()
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.setPathingBox(box)
|
||||
Pathing.setBox(box)
|
||||
end
|
||||
|
||||
function turtle.addWorldBlock(pt)
|
||||
Pathing.addBlock(pt)
|
||||
end
|
||||
|
||||
function turtle.faceAgainst(pt, options) -- 4 sided
|
||||
options = options or { }
|
||||
options.dest = { }
|
||||
|
||||
@ -989,7 +1004,6 @@ function turtle.faceAgainst(pt, options) -- 4 sided
|
||||
end
|
||||
|
||||
function turtle.moveAgainst(pt, options) -- 6 sided
|
||||
|
||||
options = options or { }
|
||||
options.dest = { }
|
||||
|
||||
@ -1090,6 +1104,32 @@ local function _actionUpAt(action, pt, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local function _actionXXXAt(action, pt, dir, ...)
|
||||
local reversed =
|
||||
{ [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, }
|
||||
dir = reversed[dir]
|
||||
local apt = { x = pt.x + headings[dir].xd,
|
||||
y = pt.y + headings[dir].yd,
|
||||
z = pt.z + headings[dir].zd, }
|
||||
local direction
|
||||
|
||||
-- ex: place a block at this point, from above, facing east
|
||||
if dir < 4 then
|
||||
apt.heading = (dir + 2) % 4
|
||||
direction = 'forward'
|
||||
elseif dir == 4 then
|
||||
apt.heading = pt.heading
|
||||
direction = 'down'
|
||||
elseif dir == 5 then
|
||||
apt.heading = pt.heading
|
||||
direction = 'up'
|
||||
end
|
||||
|
||||
if turtle.pathfind(apt) then
|
||||
return action[direction](...)
|
||||
end
|
||||
end
|
||||
|
||||
function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end
|
||||
function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end
|
||||
function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end
|
||||
@ -1109,6 +1149,7 @@ function turtle.placeAt(pt, arg) return _actionAt(actionsAt.place, pt, ar
|
||||
function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end
|
||||
function turtle.placeXXXAt(pt, dir, arg) return _actionXXXAt(actionsAt.place, pt, dir, arg) end
|
||||
|
||||
function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
|
||||
function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
|
||||
|
@ -24,9 +24,10 @@ Event.addRoutine(function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
print('proxy: lost connection from ' .. socket.dhost)
|
||||
break
|
||||
end
|
||||
socket:write({ proxy[data.fn](unpack(data.args)) })
|
||||
socket:write({ proxy[data.fn](table.unpack(data.args)) })
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
@ -17,7 +17,7 @@ local function netUp()
|
||||
_G.requireInjector()
|
||||
|
||||
local Event = require('event')
|
||||
_G._e2 = _ENV
|
||||
|
||||
for _,file in pairs(fs.list('sys/network')) do
|
||||
local fn, msg = Util.run(_ENV, 'sys/network/' .. file)
|
||||
if not fn then
|
||||
@ -48,8 +48,6 @@ print('Net daemon started')
|
||||
local function startNetwork()
|
||||
print('Starting network services')
|
||||
|
||||
_G._e1 = _ENV
|
||||
|
||||
local success, msg = Util.runFunction(
|
||||
Util.shallowCopy(_ENV), netUp)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user