Implement vl_map_index.find_node_near()

This commit is contained in:
teknomunk 2024-05-03 16:08:14 +00:00
parent 68a9dfb6de
commit a0af27ba1d
3 changed files with 257 additions and 39 deletions

View File

@ -7,6 +7,25 @@ local storage = minetest.get_mod_storage()
local mod = vl_map_index
mod.main_thread = true
local cache = {}
function mod.get_index(key)
local cached = cache[key]
if cached then return cached end
local value = storage:get_string(key)
if value ~= "" then
local data = mod.unpack_index(value)
cache[key] = data
return data
end
return nil
end
function mod.clear_cache(key)
cache[key] = nil
end
local index_cache = {}
local index_cache_count = 0
function mod.cache_map_index(map_block_hash, indexer, result)

View File

@ -87,7 +87,9 @@ local function process_dirty_block()
if post_processor then
result = post_processor(map_block_hash, result)
end
storage:set_string(block_data.index_string, tostring(result))
local key = block_data.index_string
storage:set_string(key, tostring(result))
mod.clear_cache(key)
index_in_process = false
decompress_queue[#decompress_queue + 1] = {map_block_hash, indexer, result}

View File

@ -9,15 +9,21 @@ end
local MIN_SEARCH_SIZE = 1000
local function block_pos_hash_vector(min, pos)
return (pos.x - min.x) * 10000+
(pos.y - min.y) * 100 +
return (pos.x - min.x) * 10000 +
(pos.y - min.y) * 100 +
(pos.z - min.z)
end
local function block_pos_hash(min, x, y, z)
return (x - min.x) * 10000+
(y - min.y) * 100 +
return (x - min.x) * 10000 +
(y - min.y) * 100 +
(z - min.z)
end
local function get_block_pos_from_hash(min, hash)
local dz = hash % 100; hash = (hash - dz) / 100
local dy = hash % 100; hash = (hash - dy) / 100
local dx = hash % 100
return vector.offset(min, dx, dy, dz)
end
local l2_tasks = {}
local l3_tasks = {}
@ -252,12 +258,18 @@ mod.register_indexer("node_counter",{
local pos = minetest.get_position_from_hash(map_block_hash)
local l2_block = mod.map_block(pos)
storage:set_string("node_counter.l2"..vector.to_string(l2_block), unpacked_result.l2_index)
local key = "node_counter.l2"..vector.to_string(l2_block)
storage:set_string(key, unpacked_result.l2_index)
mod.clear_cache(key)
local l3_block = mod.map_block(l2_block)
storage:set_string("node_counter.l3"..vector.to_string(l3_block), unpacked_result.l3_index)
local key = "node_counter.l3"..vector.to_string(l3_block)
storage:set_string(key, unpacked_result.l3_index)
mod.clear_cache(key)
storage:set_string("node_counter.l4", unpacked_result.l4_index)
key = "node_counter.l4"
storage:set_string(key, unpacked_result.l4_index)
mod.clear_cache(key)
return unpacked_result.index
end,
@ -265,48 +277,233 @@ mod.register_indexer("node_counter",{
if not mod.main_thread then return end
local function closest_block_in_region(pos, min, max)
local closest = vector.new(pos)
if closest.x < min.x then
closest.x = min.x
elseif closest.x > max.x then
closest.x = max.x
end
if closest.y < min.y then
closest.y = min.y
elseif closest.y > max.y then
closest.y = max.y
end
if closest.z < min.z then
closest.z = min.z
elseif closest.z > max.z then
closest.z = max.z
end
return closest
end
local function distance_to_block(pos, min, max)
return vector.distance(pos, closest_block_in_region(pos, min, max))
end
local function expand_nodenames(nodenames, l4_index)
local nodes = {}
local groups = {}
for i = 1,#nodenames do
local name = nodenames[i]
if name:sub(1,6) == "group:" then
local group_name = name:sub(7)
groups[#groups + 1] = group_name
else
nodes[name] = true
end
end
local result = {}
local cid_from_nodename = {}
for cid,nodename in pairs(l4_index.cid_map) do
local nodedef = minetest.registered_nodes[nodename]
if nodes[nodename] then
result[#result + 1] = nodename
cid_from_nodename[nodename] = cid
elseif nodedef then
local node_groups = nodedef.groups
local hit = false
for j = 1,#groups do
if not hit and (node_groups[groups[j]] or 0) ~= 0 then
result[#result + 1] = nodename
cid_from_nodename[nodename] = cid
hit = true
end
end
end
end
return result,cid_from_nodename
end
function expand_subindex(nodenames, cid_from_nodename, subindex_name, index, size, cost_filter)
if not cid_from_nodename then
cid_from_nodename = {}
for cid,nodename in pairs(index.cid_map) do
cid_from_nodename[nodename] = cid
end
end
-- Get union of subnode bitmap index where these nodes are located
local union_index = {}
for i = 1,#nodenames do
local nodename = nodenames[i]
local cid = cid_from_nodename[nodename]
if cid then
local node_index = index.subblocks[cid]
for subnode,part in pairs(node_index) do
local union_part = union_index[subnode] or 0
union_part = bit.bor(union_part, part)
union_index[subnode] = union_part
end
end
end
-- Build a cost-sorted candidate list from the list of subindexes that have the desired blocks
local candidates = {}
for k,v in pairs(union_index) do
for i=0,31 do
local bit = v % 2
v = (v - bit) / 2
if bit == 1 then
local offset = k * 32 + i
local x = offset % 16; offset = (offset - x)/16
local y = offset % 16; offset = (offset - y)/16
local z = offset % 16
local min = vector.multiply(vector.new(x,y,z), size)
local max = vector.offset(min, size-1, size-1, size-1)
local subindex = nil
if subindex_name then
subindex = string.format(subindex_name.."(%d, %d, %d)",x,y,z)
end
local cost = cost_filter(min, max)
if cost then
local idx = #candidates + 1
candidates[idx] = {cost, subindex, min, max}
-- Insersion sort according to cost so the lowest cost is at the end
if idx > 1 then
local a = candidates[idx]
local b = candidates[idx - 1]
while idx > 1 and a[1] > b[1] do
-- Swap
candidates[idx ] = b
candidates[idx-1] = a
idx = idx - 1
b = candidates[idx - 1]
end
end
end
end
end
end
return candidates
end
-- Public-facing API
function mod.find_nodes_in_area(pos1, pos2, nodenames, grouped)
local size = math.abs(pos1.x - pos2.x) * math.abs(pos1.y - pos2.y) * math.abs(pos1.z - pos2.z)
if size >= MIN_SEARCH_SIZE then
local indexes = mod.get_indexes_for_area(pos1, pos2, "node_counter")
if indexes then
for i=1,#indexes do
-- TODO: find nodes in area
end
end
if size < MIN_SEARCH_SIZE then
return minetest.find_nodes_in_area(pos1, pos2, nodenames, grouped)
end
-- Otherwize, fallback onto minetest builtin function
return minetest.find_nodes_in_area(pos1, pos2, nodenames, grouped)
-- Start at the top-level index, then work down
local l4_index = mod.get_index("node_counter.l4")
print("l4_index="..dump(l4_index))
-- Expand nodenames to actual list of nodenames
local actual_nodenames = expand_nodenames(nodenames, l4_index)
if #nodenames == 0 then return {},{} end
-- No results
return {},{}
end
function mod.find_node_near(pos, radius, nodenames, search_center)
local size = radius * radius * radius
if size >= MIN_SEARCH_SIZE then
local indexes = mod.get_indexes_near(pos, radius, "node_counter")
if indexes then
for i=1,#indexes do
-- TODO: find nodes in area
end
end
return nil
if size < MIN_SEARCH_SIZE then
return minetest.find_node_near(pos, radius, nodenames, search_center)
end
-- Otherwize, fallback onto minetest builtin function
return minetest.find_node_near(pos, radius, nodenames, search_center)
end
function mod.find_nodes_in_area_under_air(pos1, pos2, nodenames)
local size = math.abs(pos1.x - pos2.x) * math.abs(pos1.y - pos2.y) * math.abs(pos1.z - pos2.z)
if size >= MIN_SEARCH_SIZE then
local indexes = mod.get_indexes_for_area(pos1, pos2, "node_counter")
if indexes then
for i=1,#indexes do
-- TODO: find nodes under air in area
end
end
local function distance_filter(min, max)
local distance = distance_to_block(pos, min, max)
if distance > radius then return nil end
return distance
end
-- Otherwize, fallback onto minetest builtin function
return minetest.find_nodes_in_area_under_air(pos1, pos2, nodenames)
-- Start at the top-level index, then work down
local l4_index = mod.get_index("node_counter.l4")
-- Expand nodenames to actual list of nodenames, return if we can't match any indexed nodes
local nodenames,cid_from_nodename = expand_nodenames(nodenames, l4_index)
if #nodenames == 0 then return nil end
-- Expand out 3 more levels of subindexes, starting closest to pos
local l3_candidates = expand_subindex(nodenames, cid_from_nodename, "node_counter.l3", l4_index, 16*16*16, distance_filter )
local l3_idx = #l3_candidates
while l3_idx > 0 do
local l3_candidate = l3_candidates[l3_idx]
local l3_index = mod.get_index(l3_candidate[2])
local l2_candidates = expand_subindex(nodenames, nil, "node_counter.l2", l3_index, 16*16, distance_filter )
local l2_idx = #l2_candidates
while l2_idx > 0 do
local l2_candidate = l2_candidates[l2_idx]
local l2_index = mod.get_index(l2_candidate[2])
local l1_candidates = expand_subindex(nodenames, nil, "node_counter", l2_index, 16, distance_filter )
local l1_idx = #l1_candidates
while l1_idx > 0 do
local l1_candidate = l1_candidates[l1_idx]
local l1_index = mod.get_index(l1_candidate[2])
local block_min = l1_candidate[3]
local cid_from_nodename = {}
for cid,nodename in pairs(l1_index.cid_map) do
cid_from_nodename[nodename] = cid
end
-- Find closest node
local best_pos = nil
local best_dist = radius
for i = 1,#nodenames do
local cid = cid_from_nodename[nodenames[i]]
local cid_positions = l1_index.positions[cid] or {}
for j = 1,#cid_positions do
local cid_position = cid_positions[j]
local block_pos = get_block_pos_from_hash(block_min,cid_position)
local dist = vector.distance(block_pos, pos)
if dist < best_dist then
best_dist = dist
best_pos = block_pos
end
end
end
-- Because L3, L2 and L1 candidates are all sorted by closest to pos, if we have
-- position at this point this will always be the closest indexed node inside the
-- provided radius
if best_pos then
return best_pos
end
-- remove the L1 candidate
l1_candidates[l1_idx] = nil
l1_idx = l1_idx - 1
end
-- remove the L2 candidate
l2_candidates[l2_idx] = nil
l2_idx = l2_idx - 1
end
-- Remove the L3 candidate
l3_candidates[l3_idx] = nil
l3_idx = l3_idx - 1
end
-- No result
return nil
end