Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst
This commit is contained in:
parent
95945c0306
commit
a0c893ca0b
1124 changed files with 64294 additions and 763 deletions
268
mods/futil/data_structures/point_search_tree.lua
Normal file
268
mods/futil/data_structures/point_search_tree.lua
Normal file
|
@ -0,0 +1,268 @@
|
|||
--[[
|
||||
a data structure which can efficiently retrieve values within specific rectangular regions of 3d space.
|
||||
|
||||
the closest relevant descriptions of this problem and solution are in the following:
|
||||
https://en.wikipedia.org/wiki/Min/max_kd-tree
|
||||
https://medium.com/omarelgabrys-blog/geometric-applications-of-bsts-e58f0a5019f3
|
||||
|
||||
creation is O(n log n)
|
||||
finding objects in a region is O(m + log n) if there's m objects in the region.
|
||||
|
||||
the hope here is that this will provide a faster alternative to `minetest.get_objects_in_area()`. that currently
|
||||
iterates over *all* active objects in the world, which can be slow when there's ten thousand or more of objects in the
|
||||
world, and you are only interested in a few of them. in particular, the your-land server usually has 5-8 thousand
|
||||
objects loaded at once, and can have hundreds of mobs calling `get_objects_in_area` every couple server steps. perftop
|
||||
definitively implicated this routine as being a major source of lag. the question, now, is what to do about it.
|
||||
|
||||
the current implementation doesn't allow for insertion, deletion, or changes in location. if your points move around,
|
||||
you're gonna have to rebuild the whole tree from scratch, which currently limits the usefulness.
|
||||
|
||||
TODO: read this and incorporate if applicable: https://arxiv.org/abs/1410.5420
|
||||
|
||||
== footnotes about the algorithms ==
|
||||
currently, we're hard-coding the usage of the [median of medians](https://en.wikipedia.org/wiki/Median_of_medians)
|
||||
algorithm as the pivot strategy, as this resulted in unexpectedly dramatic improvements over random selection in
|
||||
some informal performance tests i did.
|
||||
|
||||
== footnotes about results ==
|
||||
currently, performance can range from taking 1/20th of the time of the engine call, to 100 times as long. this
|
||||
makes me realize that this is worth pursuing, but probably this will need to be ported to c++ to consistently provide
|
||||
a benefit. but, i'll absolutely need to figure out a self-balancing strategy before that'll be appropriate.
|
||||
]]
|
||||
local sort = table.sort
|
||||
|
||||
local in_area = vector.in_area
|
||||
or function(pos, pmin, pmax)
|
||||
local x, y, z = pos.x, pos.y, pos.z
|
||||
return pmin.x <= x and x <= pmax.x and pmin.y <= y and y <= pmax.y and pmin.z <= z and z <= pmax.z
|
||||
end
|
||||
|
||||
local axes = { "x", "y", "z" }
|
||||
local POS = 1
|
||||
local VALUE = 2
|
||||
|
||||
local Leaf = futil.class1()
|
||||
|
||||
function Leaf:_init(pos_and_value)
|
||||
self[POS] = pos_and_value[POS]
|
||||
self[VALUE] = pos_and_value[VALUE]
|
||||
end
|
||||
|
||||
local Node = futil.class1()
|
||||
|
||||
function Node:_init(min, max, left, right)
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.left = left
|
||||
self.right = right
|
||||
end
|
||||
|
||||
local PointSearchTree = futil.class1()
|
||||
|
||||
futil.min_median_max = {}
|
||||
|
||||
function futil.min_median_max.sort(t, indexer)
|
||||
if indexer then
|
||||
sort(t, function(a, b)
|
||||
return indexer(a) < indexer(b)
|
||||
end)
|
||||
else
|
||||
sort(t)
|
||||
end
|
||||
return t[1], math.floor(#t / 2), t[#t]
|
||||
end
|
||||
|
||||
function futil.min_median_max.gen_select(pivot_alg)
|
||||
return function(t, indexer)
|
||||
local median = futil.selection.select(t, pivot_alg, function(a, b)
|
||||
return indexer(a) < indexer(b)
|
||||
end)
|
||||
local min = indexer(t[1])
|
||||
local max = min
|
||||
for i = 2, #t do
|
||||
local v = indexer(t[i])
|
||||
if v < min then
|
||||
min = v
|
||||
elseif v > max then
|
||||
max = v
|
||||
end
|
||||
end
|
||||
return min, median, max
|
||||
end
|
||||
end
|
||||
|
||||
local function bisect(pos_and_values, axis_i, min_median_max)
|
||||
if #pos_and_values == 1 then
|
||||
return Leaf(pos_and_values[1])
|
||||
end
|
||||
|
||||
local axis = axes[axis_i]
|
||||
|
||||
local min, median, max = min_median_max(pos_and_values, function(i)
|
||||
return i[POS][axis]
|
||||
end)
|
||||
|
||||
local next_axis_i = (axis_i % #axes) + 1
|
||||
return Node(
|
||||
min,
|
||||
max,
|
||||
bisect({ unpack(pos_and_values, 1, median) }, next_axis_i, min_median_max),
|
||||
bisect({ unpack(pos_and_values, median + 1) }, next_axis_i, min_median_max)
|
||||
)
|
||||
end
|
||||
|
||||
function PointSearchTree:_init(pos_and_values, min_median_max)
|
||||
pos_and_values = pos_and_values or {}
|
||||
min_median_max = min_median_max or futil.min_median_max.gen_select(futil.selection.pivot.median_of_medians)
|
||||
self._len = #pos_and_values
|
||||
if #pos_and_values > 0 then
|
||||
self._root = bisect(pos_and_values, 1, min_median_max)
|
||||
end
|
||||
end
|
||||
|
||||
-- -DLUAJIT_ENABLE_LUA52COMPAT
|
||||
function PointSearchTree:__len()
|
||||
return self._len
|
||||
end
|
||||
|
||||
function PointSearchTree:dump()
|
||||
local function getlines(node, axis_i)
|
||||
local axis = axes[axis_i]
|
||||
if not node then
|
||||
return {}
|
||||
elseif node:is_a(Leaf) then
|
||||
return { minetest.pos_to_string(node[POS], 1) }
|
||||
else
|
||||
local lines = {}
|
||||
for _, line in ipairs(getlines(node.left, (axis_i % #axes) + 1)) do
|
||||
lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
|
||||
end
|
||||
for _, line in ipairs(getlines(node.right, (axis_i % #axes) + 1)) do
|
||||
lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(getlines(self._root, 1), "\n")
|
||||
end
|
||||
|
||||
local function make_iterator(pmin, pmax, predicate, accumulate)
|
||||
local function iterate(node, axis_i)
|
||||
local next_axis_i = (axis_i % 3) + 1
|
||||
local next_axis = axes[next_axis_i]
|
||||
|
||||
local left = node.left
|
||||
if left then
|
||||
if left:is_a(Leaf) then
|
||||
if predicate(left) then
|
||||
accumulate(left)
|
||||
end
|
||||
elseif pmin[next_axis] <= left.max then
|
||||
iterate(left, next_axis_i)
|
||||
end
|
||||
end
|
||||
|
||||
local right = node.right
|
||||
if right then
|
||||
if right:is_a(Leaf) then
|
||||
if predicate(right) then
|
||||
accumulate(right)
|
||||
end
|
||||
elseif right.min <= pmax[next_axis] then
|
||||
iterate(right, next_axis_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
return iterate
|
||||
end
|
||||
|
||||
function PointSearchTree:iterate_values_in_area(pmin, pmax)
|
||||
if not self._root then
|
||||
return function() end
|
||||
end
|
||||
|
||||
pmin, pmax = vector.sort(pmin, pmax)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return function() end
|
||||
end
|
||||
|
||||
return coroutine.wrap(function()
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return in_area(leaf[POS], pmin, pmax)
|
||||
end, function(leaf)
|
||||
coroutine.yield(leaf[POS], leaf[VALUE])
|
||||
end)(self._root, 1)
|
||||
end)
|
||||
end
|
||||
|
||||
function PointSearchTree:get_values_in_area(pmin, pmax)
|
||||
local via = {}
|
||||
if not self._root then
|
||||
return via
|
||||
end
|
||||
|
||||
pmin, pmax = vector.sort(pmin, pmax)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return via
|
||||
end
|
||||
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return in_area(leaf[POS], pmin, pmax)
|
||||
end, function(leaf)
|
||||
via[#via + 1] = leaf[VALUE]
|
||||
end)(self._root, 1)
|
||||
|
||||
return via
|
||||
end
|
||||
|
||||
function PointSearchTree:iterate_values_inside_radius(center, radius)
|
||||
if not self._root then
|
||||
return function() end
|
||||
end
|
||||
|
||||
local pmin = vector.subtract(center, radius)
|
||||
local pmax = vector.add(center, radius)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return function() end
|
||||
end
|
||||
|
||||
local v_distance = vector.distance
|
||||
|
||||
return coroutine.wrap(function()
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return v_distance(center, leaf[POS]) <= radius
|
||||
end, function(leaf)
|
||||
coroutine.yield(leaf[POS], leaf[VALUE])
|
||||
end)(self._root, 1)
|
||||
end)
|
||||
end
|
||||
|
||||
function PointSearchTree:get_values_inside_radius(center, radius)
|
||||
local vir = {}
|
||||
if not self._root then
|
||||
return vir
|
||||
end
|
||||
|
||||
local pmin = vector.subtract(center, radius)
|
||||
local pmax = vector.add(center, radius)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return vir
|
||||
end
|
||||
|
||||
local v_distance = vector.distance
|
||||
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return v_distance(center, leaf[POS]) <= radius
|
||||
end, function(leaf)
|
||||
vir[#vir + 1] = leaf[VALUE]
|
||||
end)(self._root, 1)
|
||||
|
||||
return vir
|
||||
end
|
||||
|
||||
futil.PointSearchTree = PointSearchTree
|
Loading…
Add table
Add a link
Reference in a new issue