forked from VoxeLibre/VoxeLibre
Start implementing lookup table calculation
This commit is contained in:
parent
9f5c948deb
commit
9fd09dce98
|
@ -105,6 +105,10 @@ local NODE_NEIGHBOR_DROP_TWO = {
|
|||
vector.new( 0,-1, 1),
|
||||
vector.new( 0,-1,-1),
|
||||
}
|
||||
local ACTION_WALK = nil
|
||||
local ACTION_JUMP = {}
|
||||
local ACTION_OPEN = {}
|
||||
local ACTION_CLIMB = {}
|
||||
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)
|
||||
|
@ -164,38 +168,58 @@ local function get_navigation_rules_for_node(state, node_pos)
|
|||
-- We can navigate thru this node
|
||||
local rule = {
|
||||
height = height,
|
||||
walk = {},
|
||||
drop = {},
|
||||
jump = {},
|
||||
size = 1, -- TODO: calculate a clearance value for mobs larger than 1 block wide
|
||||
pos = node_pos,
|
||||
neighbors = {},
|
||||
actions = {},
|
||||
}
|
||||
navigation_rules[idx] = rule
|
||||
|
||||
-- Update compressed rules map. This is used to store lookup tables in a smaller amount of memory
|
||||
-- for better performance
|
||||
local compression_mapping = state.navigation_lut.compression_mapping
|
||||
compression_mapping.count = compression_mapping.count + 1
|
||||
compression_mapping[minetest.hash_node_position(node_pos)] = {
|
||||
idx = compression_mapping.count,
|
||||
pos = node_pos
|
||||
}
|
||||
|
||||
-- Now check neighbors to see if we can move there
|
||||
for i,dir in ipairs(NODE_NEIGHBORS) do
|
||||
local neighbors = rule.neighbors
|
||||
local actions = rule.actions
|
||||
local function check_neighbors(state, node_pos, dir, neighbors, actions)
|
||||
-- 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
|
||||
neighbors[#neighbors + 1] = dir
|
||||
return
|
||||
end
|
||||
|
||||
-- Check jumps
|
||||
neighbor_rule = get_navigation_rules_for_node(state, vector.offset(neighbor_pos,0,1,0))
|
||||
if not neighbor_rule.no_nav then
|
||||
local idx = #neighbors + 1
|
||||
neighbors[idx] = NODE_NEIGHBOR_JUMPS[i]
|
||||
actions[idx] = ACTION_JUMP
|
||||
return
|
||||
end
|
||||
|
||||
-- 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
|
||||
neighbors[#neighbors + 1] = NODE_NEIGHBOR_DROP_ONE[i]
|
||||
return
|
||||
end
|
||||
|
||||
-- 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
|
||||
neighbors[#neighbors + 1] = NODE_NEIGHBOR_DROP_TWO[i]
|
||||
end
|
||||
end
|
||||
for i,dir in ipairs(NODE_NEIGHBORS) do
|
||||
check_neighbors(state, node_pos, dir, neighbors, actions)
|
||||
end
|
||||
|
||||
print("node_pos="..tostring(node_pos)..",name="..tostring(name)..",rule="..dump(rule))
|
||||
|
@ -204,14 +228,12 @@ local function get_navigation_rules_for_node(state, node_pos)
|
|||
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
|
||||
|
||||
-- Generate raw navigation rules for every node in the mapblock
|
||||
for z = (state.bound.min.z),(state.bound.max.z) do
|
||||
for y = miny,maxy do
|
||||
for x = minx,maxx do
|
||||
|
@ -220,19 +242,124 @@ local function gen_navigation_rules(state)
|
|||
end
|
||||
end
|
||||
|
||||
-- Invert navigation rules. Currently the data is all connections that you can go
|
||||
-- to from a given node. We need all the connections that will get you to a given
|
||||
-- node so that we can generate a gradient lookup table to get to any node from
|
||||
-- any other node in this mapblock
|
||||
local area = state.area
|
||||
local inverted_navigation_rules = state.inverted_navigation_rules
|
||||
for vm_idx,rule in pairs(state.navigation_rules) do
|
||||
if rule.neighbors then
|
||||
for _,neighbor_dir in pairs(rule.neighbors) do
|
||||
local neighbor_pos = vector.add(neighbor_dir, rule.pos)
|
||||
local neighbor_idx = area:index(neighbor_pos.x, neighbor_pos.y, neighbor_pos.z)
|
||||
if neighbor_idx then
|
||||
local neighbor_inverted_rules = inverted_navigation_rules[neighbor_idx]
|
||||
if not neighbor_inverted_rules then
|
||||
neighbor_inverted_rules = { count=0 }
|
||||
inverted_navigation_rules[neighbor_idx] = neighbor_inverted_rules
|
||||
end
|
||||
local new_count = neighbor_inverted_rules.count + 1
|
||||
neighbor_inverted_rules.count = new_count
|
||||
neighbor_inverted_rules[new_count] = vector.multiply(neighbor_dir, -1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("Compression mapping = "..dump(state.navigation_lut.compression_mapping))
|
||||
end
|
||||
|
||||
local function gen_navigation_lut(state, target_node_pos)
|
||||
-- Based on Dijkstra's Algorithm for shortest path calculation
|
||||
-- We want the minimum distance from every navigable node to
|
||||
-- `node_pos'
|
||||
local comp_map = state.navigation_lut.compression_mapping
|
||||
local hash_pos = minetest.hash_node_position
|
||||
local area = state.area
|
||||
local distances = {}
|
||||
local inv_nav_rules = state.inverted_navigation_rules
|
||||
|
||||
-- Start with `node_pops' as the only open node with a distance of 0
|
||||
local open = { target_node_pos }
|
||||
distances[comp_map[hash_pos(target_node_pos)].idx] = 0
|
||||
|
||||
-- While we have nodes left to compute a distance for
|
||||
while #open > 0 do
|
||||
-- Select next node to process. This will be the node
|
||||
-- with the shortest dinstance of all open nodes
|
||||
local next_node_idx = 1
|
||||
local min_distance = nil
|
||||
for i,node in ipairs(open) do
|
||||
local distance = distances[comp_map[hash_pos(node)].idx]
|
||||
if not min_distance or distance < min_distance then
|
||||
next_node_idx = i
|
||||
min_distance = distance
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove the next node from the open list
|
||||
local node_pos = open[next_node_idx]
|
||||
open[next_node_idx] = open[#open]
|
||||
open[#open] = nil
|
||||
|
||||
-- Process the node
|
||||
local node_hash = hash_pos(node_pos)
|
||||
local node_idx = area:index(node_pos.x, node_pos.y, node_pos.z)
|
||||
local inv_nav_rule = inv_nav_rules[node_idx]
|
||||
local distance = min_distance
|
||||
|
||||
-- Update neighbor distances
|
||||
for i=1,inv_nav_rule.count do
|
||||
local neighbor_pos = vector.add( inv_nav_rule[i], node_pos )
|
||||
local neighbor_idx = comp_map[hash_pos(neighbor_pos)].idx
|
||||
local neighbor_dist = distances[neighbor_idx]
|
||||
if not neighbor_dist then
|
||||
-- Neighbor hasn't been seen before, add to open list
|
||||
open[#open + 1] = neighbor_pos
|
||||
end
|
||||
if not neighbor_dist or distance + 1 < neighbor_dist then
|
||||
distances[neighbor_idx] = distance + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Now that we have the shortest distance from every navigable node to the target
|
||||
-- node, we can calculate a lookup table that gives us the next neighbor connection
|
||||
-- to take that is along the shortest path to the target node
|
||||
-- TODO: implement
|
||||
-- for each node, pick the forward neighbor rule that has the lowest distance from
|
||||
-- the target node, then encoded this into a string with 4-bit indexes, with an index
|
||||
-- of zero meaning no path exists inside this mapblock to the target node
|
||||
|
||||
print("distances="..dump(distances))
|
||||
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
|
||||
local function gen_all_navigation_luts(state)
|
||||
local navigation_lut = state.navigation_lut
|
||||
local count = navigation_lut.compression_mapping.count
|
||||
navigation_lut.compression_mapping.count = nil
|
||||
|
||||
-- Compute the navigation data for each node that is navigable
|
||||
for k,v in pairs(navigation_lut.compression_mapping) do
|
||||
local pos = v.pos
|
||||
gen_navigation_lut(state, pos)
|
||||
end
|
||||
end
|
||||
|
||||
mcl2_pathfinding.recalculate_pathfinding_data = function(pos, async)
|
||||
local state = {
|
||||
content_id_cache = {},
|
||||
navigation_rules = {},
|
||||
inverted_navigation_rules = {},
|
||||
navigation_lut = {
|
||||
compression_mapping = {
|
||||
count = 0,
|
||||
},
|
||||
},
|
||||
node_base = vector.new(
|
||||
math.floor(pos.x / 16) * 16,
|
||||
math.floor(pos.y / 16) * 16,
|
||||
|
@ -254,7 +381,7 @@ mcl2_pathfinding.recalculate_pathfinding_data = function(pos, async)
|
|||
|
||||
local function process(state)
|
||||
gen_navigation_rules(state)
|
||||
gen_navigation_lut(state)
|
||||
gen_all_navigation_luts(state)
|
||||
|
||||
return state.node_base,state.navigation_lut
|
||||
end
|
||||
|
|
|
@ -16,3 +16,19 @@ minetest.register_chatcommand("test_pathfinding",{
|
|||
end
|
||||
})
|
||||
|
||||
if minetest.get_modpath("worldedit_commands") then
|
||||
minetest.register_chatcommand("test_path",{
|
||||
description = S("Test path planning"),
|
||||
params = "",
|
||||
privs = { worldedit=true },
|
||||
func = function(name)
|
||||
local start = worldedit.pos1[name]
|
||||
local stop = worldedit.pos2[name]
|
||||
|
||||
mcl2_pathfinding.clear_path_debug()
|
||||
local path = mcl2_pathfinding.find_path(start, stop)
|
||||
mcl2_pathfinding.debug_path(path)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
name = mcl2_pathfinding
|
||||
author = teknomunk
|
||||
description = Adds a pathfinding API
|
||||
optional_depends = worldedit_commands
|
||||
|
|
Loading…
Reference in New Issue