--[[ ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ]]-- --[[ Box allocation. To keep things simple, we will always allocate boxes with same width and depth, and with a sizee that is a multiple of boxes_resolution. Height is assumed to be unlimited above box_alloc_miny. In order to do that, we keep a quadtree of allocatable positions; an allocated box will always be a leaf of that tree. We also keep for each subtree the max size that can be allocated in it. Thus, this is https://en.wikipedia.org/wiki/Buddy_memory_allocation in 2D. ]] local boxes_resolution = 256 local box_alloc_miny = 50 local boxes_tree = { minp = {x = 1024, z = 1024}, edge_size = 8192, max_alloc_size = 8192, } local function split_leaf(node) assert (node.edge_size >= 2 * boxes_resolution) assert (node.children == nil) local new_size = node.edge_size / 2 local x = node.minp.x local z = node.minp.z node.children = { { minp = {x = x, z = z}, edge_size = new_size, max_alloc_size = new_size, parent = node }, { minp = {x = x + new_size, z = z}, edge_size = new_size, max_alloc_size = new_size, parent = node, }, { minp = {x = x, z = z + new_size}, edge_size = new_size, max_alloc_size = new_size, parent = node, }, { minp = {x = x + new_size, z = z + new_size}, edge_size = new_size, max_alloc_size = new_size, parent = node, }, } end -- Update `max_alloc_size` for node and all its parents. -- Also, merge subtrees if possible. local function update_alloc_sizes(node) while node ~= nil do local max_size = 0 local el = node.edge_size / 2 local can_merge = true -- a crash here with the following error msg: -- `bad argument #1 to 'ipairs' (table expected, got nil)` -- means that there is an unknown node on the map -- hence, this assert: assert(node.children) for _, child in ipairs(node.children) do max_size = math.max(max_size, child.max_alloc_size) if child.max_alloc_size < el then can_merge = false end end if can_merge then node.children = nil node.max_alloc_size = node.edge_size else node.max_alloc_size = max_size end node = node.parent end end -- Function to know how close to zero a node is. -- Used to keep allocations close to (0, box_alloc_miny, 0). local function zero_close(node) local x = node.minp.x local z = node.minp.z local size = node.edge_size if x < 0 then x = x + size end if z < 0 then z = z + size end return math.abs(x) + math.abs(z) end -- Allocated a box of size `size` horizontally, and unbounded vertically -- Returns box minimum position. The minimum position must be given to -- boxes.vfree to free the corresponding area. function boxes.valloc(size) assert (size > 0) if boxes_tree.max_alloc_size < size then return nil end -- Find leaf with enough room, splitting it if big enough local node = boxes_tree while node.edge_size / 2 >= size and node.edge_size / 2 >= boxes_resolution do if node.children == nil then split_leaf(node) end local best = nil local best_distance = 1e10 -- infinity for _, child in ipairs(node.children) do if child.max_alloc_size >= size and zero_close(child) < best_distance then best_distance = zero_close(child) best = child end end assert (best ~= nil) node = best end local result = {x = node.minp.x, y = box_alloc_miny, z = node.minp.z} node.max_alloc_size = 0 update_alloc_sizes(node.parent) return result end function boxes.vfree(minp) assert (minp.y == box_alloc_miny) assert (boxes_tree.minp.x <= minp.x) assert (minp.x < boxes_tree.minp.x + boxes_tree.edge_size) assert (boxes_tree.minp.z <= minp.z) assert (minp.z < boxes_tree.minp.z + boxes_tree.edge_size) -- Find leaf containing minp local node = boxes_tree while node.children ~= nil do local cld = nil local el = node.edge_size / 2 for _, child in ipairs(node.children) do if child.minp.x <= minp.x and minp.x < child.minp.x + el and child.minp.z <= minp.z and minp.z < child.minp.z + el then cld = child break end end assert (cld ~= nil) node = cld end assert (node.max_alloc_size == 0) node.max_alloc_size = node.edge_size update_alloc_sizes(node.parent) end