|
|
|
@ -0,0 +1,465 @@
|
|
|
|
|
mcl2_pathfinding = mcl2_pathfinding or {}
|
|
|
|
|
local mod = mcl2_pathfinding
|
|
|
|
|
local modname = "mcl2_pathfinding"
|
|
|
|
|
|
|
|
|
|
local EXPAND_SIZE = 1
|
|
|
|
|
mod.EXPAND_SIZE = EXPAND_SIZE
|
|
|
|
|
|
|
|
|
|
local FALL_DAMAGE_DISTANCE = 3
|
|
|
|
|
mod.FALL_DAMAGE_DISTANCE = FALL_DAMAGE_DISTANCE
|
|
|
|
|
|
|
|
|
|
local MOB_HEIGHT_EXPAND_SIZE = 2
|
|
|
|
|
mod.MOB_HEIGHT_EXPAND_SIZE = MOB_HEIGHT_EXPAND_SIZE
|
|
|
|
|
|
|
|
|
|
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["_"..modname.."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 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)
|
|
|
|
|
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,
|
|
|
|
|
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
|
|
|
|
|
local neighbors = rule.neighbors
|
|
|
|
|
local actions = rule.actions
|
|
|
|
|
local function check_neighbors(state, node_pos, dir, neighbors, actions, i)
|
|
|
|
|
-- 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
|
|
|
|
|
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, i)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--print("node_pos="..tostring(node_pos)..",name="..tostring(name)..",rule="..dump(rule))
|
|
|
|
|
|
|
|
|
|
return rule
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function gen_navigation_rules(state)
|
|
|
|
|
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
|
|
|
|
|
get_navigation_rules_for_node(state, vector.new(x,y,z))
|
|
|
|
|
end
|
|
|
|
|
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.
|
|
|
|
|
--
|
|
|
|
|
-- While we are doing this, we will also create the rule index for the lookup table
|
|
|
|
|
local area = state.area
|
|
|
|
|
local inverted_navigation_rules = state.inverted_navigation_rules
|
|
|
|
|
local rule_table = { index={} }
|
|
|
|
|
state.rule_table = rule_table
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
local dir_hash = minetest.hash_node_position(neighbor_dir)
|
|
|
|
|
if not rule_table.index[dir_hash] then
|
|
|
|
|
local idx = #rule_table + 1
|
|
|
|
|
rule_table[idx] = neighbor_dir
|
|
|
|
|
rule_table.index[dir_hash] = idx
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
rule_table.present = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local HEX = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" }
|
|
|
|
|
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
|
|
|
|
|
if inv_nav_rule then
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
local best_directions = {}
|
|
|
|
|
local navigation_rules = state.navigation_rules
|
|
|
|
|
local rule_table_index = state.rule_table.index
|
|
|
|
|
for _,v in pairs(comp_map) do
|
|
|
|
|
local pos = v.pos
|
|
|
|
|
local vm_idx = area:index(pos.x, pos.y, pos.z)
|
|
|
|
|
local rule = navigation_rules[vm_idx]
|
|
|
|
|
|
|
|
|
|
-- Pick the neighbor rule that has the lowest distance to the target node
|
|
|
|
|
-- This neighbor is the next step along the shortest past to the target
|
|
|
|
|
-- node from this position
|
|
|
|
|
local best_rule = nil
|
|
|
|
|
local best_distance = nil
|
|
|
|
|
for i,dir in ipairs(rule.neighbors) do
|
|
|
|
|
local neighbor_pos = vector.add(dir, rule.pos)
|
|
|
|
|
local neighbor_idx = comp_map[hash_pos(neighbor_pos)].idx
|
|
|
|
|
local neighbor_distance = distances[neighbor_idx]
|
|
|
|
|
if not best_distance or ( neighbor_distance and neighbor_distance < best_distance ) then
|
|
|
|
|
best_distance = distances[neighbor_idx]
|
|
|
|
|
best_rule = dir
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Store the best direction as an index into the rule table for this block
|
|
|
|
|
local value = 0
|
|
|
|
|
if best_rule then
|
|
|
|
|
local best_dir_hash = hash_pos(best_rule)
|
|
|
|
|
value = rule_table_index[best_dir_hash]
|
|
|
|
|
end
|
|
|
|
|
best_directions[comp_map[hash_pos(rule.pos)].idx] = value
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Create the lookup table
|
|
|
|
|
local join_list = {}
|
|
|
|
|
for i,best_dir_idx in ipairs(best_directions) do
|
|
|
|
|
join_list[i] = string.char(best_dir_idx) --HEX[best_dir_idx+1]
|
|
|
|
|
end
|
|
|
|
|
local lut = table.concat(join_list,"")
|
|
|
|
|
local target_node_hash = hash_pos(target_node_pos)
|
|
|
|
|
local target_node_idx = comp_map[target_node_hash].idx
|
|
|
|
|
state.navigation_lut[target_node_idx] = lut
|
|
|
|
|
print(tostring(target_node_idx).." => "..lut)
|
|
|
|
|
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_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 _,v in pairs(navigation_lut.compression_mapping) do
|
|
|
|
|
local pos = v.pos
|
|
|
|
|
gen_navigation_lut(state, pos)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Calculate a hash value given voxel data
|
|
|
|
|
local function hash_voxel_data(data, min, max)
|
|
|
|
|
local area = VoxelArea:new{ MinEdge = min, MaxEdge = max }
|
|
|
|
|
local content_id_cache = {}
|
|
|
|
|
|
|
|
|
|
local node_list = {}
|
|
|
|
|
local minx = min.x
|
|
|
|
|
local maxx = max.x
|
|
|
|
|
local miny = min.x
|
|
|
|
|
local maxy = max.x
|
|
|
|
|
for z = min.z,max.z do
|
|
|
|
|
for y = miny,maxy do
|
|
|
|
|
for x = minx,maxx do
|
|
|
|
|
local idx = area:index(x,y,z)
|
|
|
|
|
local cid = data[idx]
|
|
|
|
|
local node_name = "unloaded"
|
|
|
|
|
if cid then
|
|
|
|
|
node_name = content_id_cache[cid]
|
|
|
|
|
if not node_name then
|
|
|
|
|
node_name = minetest.get_name_from_content_id(cid)
|
|
|
|
|
content_id_cache[cid] = node_name
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
node_list[#node_list + 1] = node_name
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return minetest.sha1(table.concat(node_list, ""))
|
|
|
|
|
end
|
|
|
|
|
mod.hash_voxel_data = hash_voxel_data
|
|
|
|
|
|
|
|
|
|
-- This packages the lookup table data into a more compact representation for disk storage
|
|
|
|
|
local function pack_navigation_lut(navigation_lut)
|
|
|
|
|
return minetest.encode_base64(minetest.compress(minetest.serialize(navigation_lut), "zstd" ))
|
|
|
|
|
end
|
|
|
|
|
mod.pack_navigation_lut = pack_navigation_lut
|
|
|
|
|
|
|
|
|
|
-- The inverse of pack_navigation_lut
|
|
|
|
|
local function unpack_navigation_lut(packed)
|
|
|
|
|
local binary = minetest.decode_base64(packed)
|
|
|
|
|
local uncompressed = minetest.decompress(binary, "zstd")
|
|
|
|
|
return minetest.deserialize(uncompressed)
|
|
|
|
|
end
|
|
|
|
|
mod.unpack_navigation_lut = unpack_navigation_lut
|
|
|
|
|
|
|
|
|
|
-- Calculate the navigation lookup table for a given region
|
|
|
|
|
local function process_recalculate_pathfinding_data(state)
|
|
|
|
|
state.area = VoxelArea:new{ MinEdge = state.min, MaxEdge = state.max }
|
|
|
|
|
|
|
|
|
|
gen_navigation_rules(state)
|
|
|
|
|
gen_all_navigation_luts(state)
|
|
|
|
|
|
|
|
|
|
return state.node_base,pack_navigation_lut(state.navigation_lut)
|
|
|
|
|
end
|
|
|
|
|
mod.process_recalculate_pathfinding_data = process_recalculate_pathfinding_data
|