Some of these are copies from the respective origins from mtg, to make sure we have headers everywhere listing the proper code. I've relicensed spectator_mode from WT*PL to LGPL-2.1. No other licenses were changed.
179 lines
4.8 KiB
Lua
179 lines
4.8 KiB
Lua
|
|
--[[
|
|
|
|
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
|
|
|