forked from VoxeLibre/VoxeLibre
Implement vl_map_index.find_node_near()
This commit is contained in:
parent
68a9dfb6de
commit
a0af27ba1d
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue