forked from VoxeLibre/VoxeLibre
Start writing new pathfinding algorithm
This commit is contained in:
parent
e50ad7d6de
commit
9f5c948deb
|
@ -0,0 +1,275 @@
|
||||||
|
local storage = minetest.get_mod_storage()
|
||||||
|
local EXPAND_SIZE = 1
|
||||||
|
local FALL_DAMAGE_DISTANCE = 3
|
||||||
|
local MOB_HEIGHT_EXPAND_SIZE = 2
|
||||||
|
|
||||||
|
local function get_node_at(state, pos, idx)
|
||||||
|
if not idx then idx = state.area:index(pos.x, pos.y, pos.z) end
|
||||||
|
|
||||||
|
local cid = state.data[idx]
|
||||||
|
if not cid then return "unloaded" end
|
||||||
|
if state.content_id_cache[cid] then return state.content_id_cache[cid] end
|
||||||
|
|
||||||
|
local name = minetest.get_name_from_content_id(cid)
|
||||||
|
state.content_id_cache[cid] = name
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
local air_height_topside_cache = {}
|
||||||
|
local function get_node_air_height_topside(state, pos, name)
|
||||||
|
if not name then name = get_node_at(state, pos) end
|
||||||
|
|
||||||
|
if name == "air" then return 1 end
|
||||||
|
|
||||||
|
local nodedef = minetest.registered_nodes[name]
|
||||||
|
if not nodedef then
|
||||||
|
print("Missing node definition for "..tostring(name)..", treating as solid")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if nodedef.drawtype == "nodebox" then
|
||||||
|
local max = -0.5
|
||||||
|
if nodedef.node_box.type == "fixed" then
|
||||||
|
for _,part in ipairs(nodedef.node_box.fixed) do
|
||||||
|
if part[5] > max then
|
||||||
|
max = part[5]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local air_height_topside = 0.5 - max
|
||||||
|
air_height_topside_cache[name] = air_height_topside
|
||||||
|
return air_height_topside
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- TODO: finish implement partial block heights
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local air_height_underside_cache = {}
|
||||||
|
local function get_node_air_height_underside(state, pos, name)
|
||||||
|
if not name then name = get_node_at(state, pos) end
|
||||||
|
|
||||||
|
if name == "air" then return 1 end
|
||||||
|
|
||||||
|
local nodedef = minetest.registered_nodes[name]
|
||||||
|
if not nodedef then
|
||||||
|
print("Missing node definition for "..tostring(name)..", treating as solid")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local node_air_height_underside = nodedef._mcl2_pathfinding_air_height_underside or air_height_underside_cache[name]
|
||||||
|
if node_air_height_underside then return node_air_height_underside end
|
||||||
|
|
||||||
|
if nodedef.drawtype == "nodebox" then
|
||||||
|
local min = 0.5
|
||||||
|
if nodedef.node_box.type == "fixed" then
|
||||||
|
for _,part in ipairs(nodedef.node_box.fixed) do
|
||||||
|
if part[5] < min then
|
||||||
|
min = part[5]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cache for future use
|
||||||
|
local air_height_underside = 0.5 + min
|
||||||
|
air_height_underside_cache[name] = air_height_underside
|
||||||
|
return air_height_underside
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: implement partial block heights
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local RULE_NO_NAV = { no_nav = true }
|
||||||
|
local NODE_NEIGHBORS = {
|
||||||
|
vector.new( 1,0, 0),
|
||||||
|
vector.new(-1,0, 0),
|
||||||
|
vector.new( 0,0, 1),
|
||||||
|
vector.new( 0,0,-1),
|
||||||
|
}
|
||||||
|
local NODE_NEIGHBOR_JUMPS = {
|
||||||
|
vector.new( 1,1, 0),
|
||||||
|
vector.new(-1,1, 0),
|
||||||
|
vector.new( 0,1, 1),
|
||||||
|
vector.new( 0,1,-1),
|
||||||
|
}
|
||||||
|
local NODE_NEIGHBOR_DROP_ONE = {
|
||||||
|
vector.new( 1,-1, 0),
|
||||||
|
vector.new(-1,-1, 0),
|
||||||
|
vector.new( 0,-1, 1),
|
||||||
|
vector.new( 0,-1,-1),
|
||||||
|
}
|
||||||
|
local NODE_NEIGHBOR_DROP_TWO = {
|
||||||
|
vector.new( 1,-1, 0),
|
||||||
|
vector.new(-1,-1, 0),
|
||||||
|
vector.new( 0,-1, 1),
|
||||||
|
vector.new( 0,-1,-1),
|
||||||
|
}
|
||||||
|
local function get_navigation_rules_for_node(state, node_pos)
|
||||||
|
local area = state.area
|
||||||
|
local idx = area:index(node_pos.x,node_pos.y,node_pos.z)
|
||||||
|
if not idx then return RULE_NO_NAV end
|
||||||
|
|
||||||
|
-- Check if we already have generated rules for this node
|
||||||
|
local navigation_rules = state.navigation_rules
|
||||||
|
if navigation_rules[idx] then return navigation_rules[idx] end
|
||||||
|
|
||||||
|
-- Bounds check
|
||||||
|
if node_pos.x < state.bound.min.x then return RULE_NO_NAV end
|
||||||
|
if node_pos.y < state.bound.min.y then return RULE_NO_NAV end
|
||||||
|
if node_pos.z < state.bound.min.z then return RULE_NO_NAV end
|
||||||
|
if node_pos.x > state.bound.max.x then return RULE_NO_NAV end
|
||||||
|
if node_pos.y > state.bound.max.y then return RULE_NO_NAV end
|
||||||
|
if node_pos.z > state.bound.max.z then return RULE_NO_NAV end
|
||||||
|
|
||||||
|
-- Lookup node information
|
||||||
|
local name = get_node_at(state, node_pos, idx)
|
||||||
|
local nodedef = minetest.registered_nodes[name]
|
||||||
|
|
||||||
|
-- Check if navigable: Need to have at least 2 height for horizontal travel thru this node
|
||||||
|
local height = 0
|
||||||
|
if name == "air" then
|
||||||
|
-- Make sure we have a solid block below us
|
||||||
|
if get_node_air_height_topside(state, vector.offset(node_pos,0,-1,0)) == 1 then
|
||||||
|
-- Would fall thru and navigate thru node below here
|
||||||
|
navigation_rules[idx] = RULE_NO_NAV
|
||||||
|
return RULE_NO_NAV
|
||||||
|
end
|
||||||
|
|
||||||
|
height = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle solid and semi-solid block
|
||||||
|
height = get_node_air_height_topside(state, node_pos, name)
|
||||||
|
if height == 1 then
|
||||||
|
-- Solid
|
||||||
|
navigation_rules[idx] = RULE_NO_NAV
|
||||||
|
return RULE_NO_NAV
|
||||||
|
end
|
||||||
|
|
||||||
|
local node_underside_height = 0
|
||||||
|
local above_block = node_pos
|
||||||
|
repeat
|
||||||
|
above_block = vector.offset(above_block,0,1,0)
|
||||||
|
node_underside_height = get_node_air_height_underside(state, above_block)
|
||||||
|
height = height + node_underside_height
|
||||||
|
until node_underside_height ~= 1 or height >= 3
|
||||||
|
|
||||||
|
-- Handle too small spaces
|
||||||
|
if height < 2 then
|
||||||
|
navigation_rules[idx] = RULE_NO_NAV
|
||||||
|
return RULE_NO_NAV
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We can navigate thru this node
|
||||||
|
local rule = {
|
||||||
|
height = height,
|
||||||
|
walk = {},
|
||||||
|
drop = {},
|
||||||
|
jump = {},
|
||||||
|
}
|
||||||
|
navigation_rules[idx] = rule
|
||||||
|
|
||||||
|
-- Now check neighbors to see if we can move there
|
||||||
|
for i,dir in ipairs(NODE_NEIGHBORS) do
|
||||||
|
-- Check direct neighbor
|
||||||
|
local neighbor_pos = vector.add(node_pos, dir)
|
||||||
|
local neighbor_rule = get_navigation_rules_for_node(state, neighbor_pos)
|
||||||
|
if not neighbor_rule.no_nav then
|
||||||
|
rule.walk[#rule.walk + 1] = dir
|
||||||
|
else
|
||||||
|
-- Check jumps
|
||||||
|
neighbor_rule = get_navigation_rules_for_node(state, vector.offset(neighbor_pos,0,1,0))
|
||||||
|
if not neighbor_rule.no_nav then
|
||||||
|
rule.jump[#rule.jump + 1] = NODE_NEIGHBOR_JUMPS[i]
|
||||||
|
else
|
||||||
|
-- Check drop one block
|
||||||
|
neighbor_rule = get_navigation_rules_for_node(state, vector.offset(neighbor_pos,0,-1,0))
|
||||||
|
if not neighbor_rule.no_nav then
|
||||||
|
rule.drop[#rule.drop + 1] = NODE_NEIGHBOR_DROP_ONE[i]
|
||||||
|
else
|
||||||
|
-- Check drop two blocks
|
||||||
|
neighbor_rule = get_navigation_rules_for_node(state, vector.offset(neighbor_pos,0,-1,0))
|
||||||
|
if not neighbor_rule.no_nav then
|
||||||
|
rule.drop[#rule.drop + 1] = NODE_NEIGHBOR_DROP_TWO[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("node_pos="..tostring(node_pos)..",name="..tostring(name)..",rule="..dump(rule))
|
||||||
|
|
||||||
|
return rule
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gen_navigation_rules(state)
|
||||||
|
local node_base = state.node_base
|
||||||
|
local area = state.area
|
||||||
|
local navigation_rules = state.navigation_rules
|
||||||
|
local miny = state.bound.min.y
|
||||||
|
local maxy = state.bound.max.y
|
||||||
|
local minx = state.bound.min.x
|
||||||
|
local maxx = state.bound.max.x
|
||||||
|
|
||||||
|
for z = (state.bound.min.z),(state.bound.max.z) do
|
||||||
|
for y = miny,maxy do
|
||||||
|
for x = minx,maxx do
|
||||||
|
get_navigation_rules_for_node(state, vector.new(x,y,z))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Precomputes a lookup table for each node to get the next direction to take to get to any other node
|
||||||
|
-- in the map block. This will take some time, but shouldn't have to be done very often (only when a change
|
||||||
|
-- in the map data makes it impossible to get to the target node)
|
||||||
|
local function gen_navigation_lut(state)
|
||||||
|
-- TODO: implement
|
||||||
|
end
|
||||||
|
|
||||||
|
mcl2_pathfinding.recalculate_pathfinding_data = function(pos, async)
|
||||||
|
local state = {
|
||||||
|
content_id_cache = {},
|
||||||
|
navigation_rules = {},
|
||||||
|
node_base = vector.new(
|
||||||
|
math.floor(pos.x / 16) * 16,
|
||||||
|
math.floor(pos.y / 16) * 16,
|
||||||
|
math.floor(pos.z / 16) * 16
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
local node_base = state.node_base
|
||||||
|
local vm = minetest.get_voxel_manip(); state.vm = vm
|
||||||
|
state.bound = {
|
||||||
|
min = vector.offset(node_base, 0 - EXPAND_SIZE, 0 - FALL_DAMAGE_DISTANCE, 0 - EXPAND_SIZE),
|
||||||
|
max = vector.offset(node_base, 16 + EXPAND_SIZE, 16 + MOB_HEIGHT_EXPAND_SIZE, 16 + EXPAND_SIZE)
|
||||||
|
}
|
||||||
|
local emin,emax = vm:read_from_map( state.bound.min, state.bound.max )
|
||||||
|
state.min = emin
|
||||||
|
state.max = emax
|
||||||
|
local area = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }; state.area = area
|
||||||
|
local data = vm:get_data(); state.data = data
|
||||||
|
|
||||||
|
local function process(state)
|
||||||
|
gen_navigation_rules(state)
|
||||||
|
gen_navigation_lut(state)
|
||||||
|
|
||||||
|
return state.node_base,state.navigation_lut
|
||||||
|
end
|
||||||
|
local function finish(node_pos,navigation_lut)
|
||||||
|
local data = minetest.serialize(navigation_lut)
|
||||||
|
local hash = minetest.hash_node_position(node_pos)
|
||||||
|
storage:set_string(tostring(hash),data)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- use async environemt if request and supported
|
||||||
|
if async and minetest.handle_async then
|
||||||
|
minetest.handle_async(process,finish,state)
|
||||||
|
else
|
||||||
|
finish(process(state))
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
local modname = minetest.get_current_modname()
|
||||||
|
local modpath = minetest.get_modpath(modname)
|
||||||
|
local S = minetest.get_translator(modname)
|
||||||
|
|
||||||
|
mcl2_pathfinding = {}
|
||||||
|
dofile(modpath.."/api.lua")
|
||||||
|
|
||||||
|
minetest.register_chatcommand("test_pathfinding",{
|
||||||
|
description = S("Test pathfinding"),
|
||||||
|
params = "",
|
||||||
|
privs = {},
|
||||||
|
func = function(name)
|
||||||
|
local player = minetest.get_player_by_name(name)
|
||||||
|
|
||||||
|
mcl2_pathfinding.recalculate_pathfinding_data(player:get_pos(),false)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
name = mcl2_pathfinding
|
||||||
|
author = teknomunk
|
||||||
|
description = Adds a pathfinding API
|
Loading…
Reference in New Issue