ikea/mods/son_of_a_luaentitysao/pathfind.lua

91 lines
3.0 KiB
Lua

local walkable, cost = {}, {}
minetest.register_on_mods_loaded(function()
for k, v in pairs(minetest.registered_nodes) do
if v.walkable then
walkable[minetest.get_content_id(k)] = true
end
if v.groups.pathable and v.groups.pathable > 0 then
cost[minetest.get_content_id(k)] = v.groups.pathable
end
end
end)
local function pathfind(origin, target, radius)
origin, target = vector.round(origin), vector.round(target)
target.y = 0 -- TODO: Jumping/Climbing
local padding = vector.new(radius, 1, radius)
local padded_min, padded_max = vector.sort(origin, target)
padded_min, padded_max = vector.subtract(padded_min, padding), vector.add(padded_max, padding)
local VM = minetest.get_voxel_manip()
local minp, maxp = VM:read_from_map(padded_min, padded_max)
local VA, node_data = VoxelArea:new{MinEdge = minp, MaxEdge = maxp}, VM:get_data()
local x_width, y_width, z_width = maxp.x - minp.x, maxp.y - minp.y, maxp.z - minp.z
local function get_neighbors(i)
-- Bounds check Y - 1
if math.floor(((i - 1 - VA.ystride) % VA.zstride) / VA.ystride) < 0 then
return {}
end
local north = math.floor((i - 1 + VA.zstride) / VA.zstride) <= z_width
local south = math.floor((i - 1 - VA.zstride) / VA.zstride) >= 0
local east = math.floor((i - 1 + 1) % VA.zstride % VA.ystride) <= x_width
local west = math.floor((i - 1 - 1) % VA.zstride % VA.ystride) >= 0
local neighbors = {}
if north then
neighbors[#neighbors + 1] = i + VA.zstride
if east then neighbors[#neighbors + 1] = i + VA.zstride + 1 end
if west then neighbors[#neighbors + 1] = i + VA.zstride - 1 end
end
if south then
neighbors[#neighbors + 1] = i - VA.zstride
if east then neighbors[#neighbors + 1] = i - VA.zstride + 1 end
if west then neighbors[#neighbors + 1] = i - VA.zstride - 1 end
end
if east then neighbors[#neighbors + 1] = i + 1 end
if west then neighbors[#neighbors + 1] = i - 1 end
return neighbors
end
local function is_good_node(i)
return (not walkable[node_data[i]]) and walkable[node_data[i - VA.ystride]]
end
local function get_cost(a, b)
return cost[node_data[b]] or 1
end
local function get_heuristic(a, b)
local a_pos, b_pos = VA:position(a), VA:position(b)
return vector.distance(a_pos, b_pos)
end
local raw_path = util.astar(VA:indexp(origin), VA:indexp(target), get_neighbors, is_good_node, get_cost, get_heuristic)
if not raw_path then return false end
table.remove(raw_path, 1) -- No need to walk to where we already are
local path = {}
for _, node_index in ipairs(raw_path) do
table.insert(path, VA:position(node_index))
end
local trimmed_path, old_direction, old_pos = {}, false, table.remove(path, 1)
for _, pos in ipairs(path) do
local direction = vector.direction(pos, old_pos)
if not old_direction or not vector.equals(direction, old_direction) then
table.insert(trimmed_path, old_pos)
old_pos, old_direction = pos, direction
end
end
table.insert(trimmed_path, table.remove(path))
return trimmed_path
end
return pathfind