mirror of
https://github.com/minetest-mods/MoreMesecons.git
synced 2024-07-15 12:09:28 +02:00
In comparison to using Minetest's hash_node_position, this saves additional information about the z-only and zy-only components, which is what the vector_extras code did. In comparison to the old vector_extras code, this does not create lots of lua tables, which is slow, but instead uses one table as hashmap.
337 lines
8.2 KiB
Lua
337 lines
8.2 KiB
Lua
moremesecons = {}
|
|
|
|
function moremesecons.setting(modname, settingname, default, min)
|
|
local setting = "moremesecons_" .. modname .. "." .. settingname
|
|
|
|
if type(default) == "boolean" then
|
|
local ret = minetest.settings:get_bool(setting)
|
|
if ret == nil then
|
|
ret = default
|
|
end
|
|
return ret
|
|
elseif type(default) == "string" then
|
|
return minetest.settings:get(setting) or default
|
|
elseif type(default) == "number" then
|
|
local ret = tonumber(minetest.settings:get(setting)) or default
|
|
if not ret then
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' must be a number. Set to default value ("..tostring(default)..").")
|
|
ret = default
|
|
elseif ret ~= ret then -- NaN
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' is NaN. Set to default value ("..tostring(default)..").")
|
|
ret = default
|
|
end
|
|
if min and ret < min then
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' is under minimum value "..tostring(min)..". Set to minimum value ("..tostring(min)..").")
|
|
ret = min
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
|
|
-- Storage helpers
|
|
|
|
function moremesecons.get_storage_data(storage, name)
|
|
return {
|
|
tab = minetest.deserialize(storage:get_string(name)) or {},
|
|
name = name,
|
|
storage = storage
|
|
}
|
|
end
|
|
|
|
function moremesecons.set_data_to_pos(sto, pos, data)
|
|
sto.tab[minetest.hash_node_position(pos)] = data
|
|
sto.storage:set_string(sto.name, minetest.serialize(sto.tab))
|
|
end
|
|
|
|
function moremesecons.get_data_from_pos(sto, pos)
|
|
return sto.tab[minetest.hash_node_position(pos)]
|
|
end
|
|
|
|
function moremesecons.remove_data_from_pos(sto, pos)
|
|
sto.tab[minetest.hash_node_position(pos)] = nil
|
|
sto.storage:set_string(sto.name, minetest.serialize(sto.tab))
|
|
end
|
|
|
|
-- Some additional vector helpers
|
|
|
|
-- The same as minetest.hash_node_position; I copied it to ensure backwards
|
|
-- compatibility and used hexadecimal number notation
|
|
local function node_position_key(pos)
|
|
return (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
+ (pos.y + 0x8000) * 0x10000
|
|
+ pos.x + 0x8000
|
|
end
|
|
|
|
local MapDataStorage = {}
|
|
setmetatable(MapDataStorage, {__call = function()
|
|
local obj = {}
|
|
setmetatable(obj, MapDataStorage)
|
|
return obj
|
|
end})
|
|
MapDataStorage.__index = {
|
|
getAt = function(self, pos)
|
|
return self[node_position_key(pos)]
|
|
end,
|
|
setAt = function(self, pos, data)
|
|
-- If x, y or z is omitted, the key corresponds to a position outside
|
|
-- of the map (hopefully), so it can be used to skip lines and planes
|
|
local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000
|
|
local vi = vi_zy + pos.x + 0x8000
|
|
local is_new = self[vi] == nil
|
|
self[vi] = data
|
|
if is_new then
|
|
self[vi_z] = (self[vi_z] or 0) + 1
|
|
self[vi_zy] = (self[vi_zy] or 0) + 1
|
|
end
|
|
end,
|
|
removeAt = function(self, pos)
|
|
local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000
|
|
local vi = vi_zy + pos.x + 0x8000
|
|
if self[vi] == nil then
|
|
-- Nothing to remove
|
|
return
|
|
end
|
|
self[vi] = nil
|
|
-- Update existence information for the xy plane and x line
|
|
self[vi_z] = self[vi_z] - 1
|
|
if self[vi_z] == 0 then
|
|
self[vi_z] = nil
|
|
self[vi_zy] = nil
|
|
return
|
|
end
|
|
self[vi_zy] = self[vi_zy] - 1
|
|
if self[vi_zy] == 0 then
|
|
self[vi_zy] = nil
|
|
end
|
|
end,
|
|
iter = function(self, pos1, pos2)
|
|
local ystride = 0x10000
|
|
local zstride = 0x10000 * 0x10000
|
|
|
|
-- Skip z values where no data can be found
|
|
pos1 = vector.new(pos1)
|
|
local vi_z = (pos1.z + 0x8000) * 0x10000 * 0x10000
|
|
while not self[vi_z] do
|
|
pos1.z = pos1.z + 1
|
|
vi_z = vi_z + zstride
|
|
if pos1.z > pos2.z then
|
|
-- There are no values to iterate through
|
|
return function() return end
|
|
end
|
|
end
|
|
-- Skipping y values is not yet implemented and may require much code
|
|
|
|
local xrange = pos2.x - pos1.x + 1
|
|
local yrange = pos2.y - pos1.y + 1
|
|
local zrange = pos2.z - pos1.z + 1
|
|
|
|
-- x-only and y-only parts of the vector index of pos1
|
|
local vi_y = (pos1.y + 0x8000) * 0x10000
|
|
local vi_x = pos1.x + 0x8000
|
|
|
|
local y = 0
|
|
local z = 0
|
|
|
|
local vi = node_position_key(pos1)
|
|
local pos = vector.new(pos1)
|
|
local nextaction = vi + xrange
|
|
pos.x = pos.x - 1
|
|
vi = vi - 1
|
|
local function iterfunc()
|
|
-- continue along x until it needs to jump
|
|
vi = vi + 1
|
|
pos.x = pos.x + 1
|
|
if vi ~= nextaction then
|
|
local v = self[vi]
|
|
if v == nil then
|
|
-- No data here
|
|
return iterfunc()
|
|
end
|
|
-- The returned position must not be changed
|
|
return pos, v
|
|
end
|
|
|
|
-- Reset x position
|
|
vi = vi - xrange
|
|
-- Go along y until pos2.y is exceeded
|
|
while true do
|
|
y = y + 1
|
|
pos.y = pos.y + 1
|
|
-- Set vi to index(pos1.x, pos1.y + y, pos1.z + z)
|
|
vi = vi + ystride
|
|
if y == yrange then
|
|
break
|
|
end
|
|
if self[vi - vi_x] then
|
|
nextaction = vi + xrange
|
|
|
|
vi = vi - 1
|
|
pos.x = pos1.x - 1
|
|
return iterfunc()
|
|
end
|
|
-- Nothing along this x line, so increase y again
|
|
end
|
|
|
|
-- Go back along y
|
|
vi = vi - yrange * ystride
|
|
y = 0
|
|
pos.y = pos1.y
|
|
-- Go along z until pos2.z is exceeded
|
|
while true do
|
|
z = z + 1
|
|
pos.z = pos.z + 1
|
|
vi = vi + zstride
|
|
if z == zrange then
|
|
-- Cuboid finished, return nil
|
|
return
|
|
end
|
|
if self[vi - vi_x - vi_y] then
|
|
y = 0
|
|
nextaction = vi + xrange
|
|
|
|
vi = vi - 1
|
|
pos.x = pos1.x - 1
|
|
return iterfunc()
|
|
end
|
|
-- Nothing in this xy plane, so increase z again
|
|
end
|
|
end
|
|
return iterfunc
|
|
end,
|
|
iterAll = function(self)
|
|
local pairsfunc = pairs(self)
|
|
local function iterfunc()
|
|
local vi, v = pairsfunc()
|
|
if not vi then
|
|
return
|
|
end
|
|
local z = math.floor(vi / (0x10000 * 0x10000))
|
|
vi = vi - z * 0x10000 * 0x10000
|
|
local y = math.floor(vi / 0x10000)
|
|
if y == 0 or z == 0 then
|
|
-- The index does not refer to a position inside the map
|
|
return iterfunc()
|
|
end
|
|
local x = vi - y * 0x10000 - 0x8000
|
|
y = y - 0x8000
|
|
z = z - 0x8000
|
|
return {x=x, y=y, z=z}, v
|
|
end
|
|
end,
|
|
}
|
|
moremesecons.MapDataStorage = MapDataStorage
|
|
|
|
|
|
-- This testing code shows an example usage of the MapDataStorage code
|
|
local function do_test()
|
|
print("Test if iter returns correct positions when a lot is set")
|
|
local data = MapDataStorage()
|
|
local k = 0
|
|
for x = -5, 3 do
|
|
for y = -5, 3 do
|
|
for z = -5, 3 do
|
|
k = k + 1
|
|
data:setAt({x=x, y=y, z=z}, k)
|
|
end
|
|
end
|
|
end
|
|
local expected_positions = {}
|
|
for z = -4, 2 do
|
|
for y = -4, 2 do
|
|
for x = -4, 2 do
|
|
expected_positions[#expected_positions+1] = {x=x, y=y, z=z}
|
|
end
|
|
end
|
|
end
|
|
local i = 0
|
|
for pos, v in data:iter({x=-4, y=-4, z=-4}, {x=2, y=2, z=2}) do
|
|
i = i + 1
|
|
assert(vector.equals(pos, expected_positions[i]))
|
|
end
|
|
|
|
print("Test if iter works correctly on a corner")
|
|
local found = false
|
|
for pos, v in data:iter({x=-8, y=-7, z=-80}, {x=-5, y=-5, z=-5}) do
|
|
assert(not found)
|
|
found = true
|
|
assert(vector.equals(pos, {x=-5, y=-5, z=-5}))
|
|
end
|
|
assert(found)
|
|
|
|
print("Test if iter finds all corners")
|
|
local expected_positions = {}
|
|
local k = 1
|
|
for _, z in ipairs({-9, -6}) do
|
|
for _, y in ipairs({-9, -6}) do
|
|
for _, x in ipairs({-8, -6}) do
|
|
local pos = {x=x, y=y, z=z}
|
|
expected_positions[#expected_positions+1] = pos
|
|
data:setAt(pos, k)
|
|
k = k + 1
|
|
end
|
|
end
|
|
end
|
|
local i = 1
|
|
for pos, v in data:iter({x=-8, y=-9, z=-9}, {x=-6, y=-6, z=-6}) do
|
|
assert(v == i)
|
|
assert(vector.equals(pos, expected_positions[i]))
|
|
i = i + 1
|
|
--~ print("found " .. minetest.pos_to_string(pos))
|
|
end
|
|
assert(i == 8 + 1, "Not enough or too many corners found")
|
|
|
|
--~ data:iterAll()
|
|
end
|
|
|
|
do_test()
|
|
|
|
|
|
if not vector.get_data_from_pos then
|
|
function vector.get_data_from_pos(tab, z,y,x)
|
|
local data = tab[z]
|
|
if data then
|
|
data = data[y]
|
|
if data then
|
|
return data[x]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not vector.set_data_to_pos then
|
|
function vector.set_data_to_pos(tab, z,y,x, data)
|
|
if tab[z] then
|
|
if tab[z][y] then
|
|
tab[z][y][x] = data
|
|
return
|
|
end
|
|
tab[z][y] = {[x] = data}
|
|
return
|
|
end
|
|
tab[z] = {[y] = {[x] = data}}
|
|
end
|
|
end
|
|
|
|
if not vector.remove_data_from_pos then
|
|
function vector.remove_data_from_pos(tab, z,y,x)
|
|
if vector.get_data_from_pos(tab, z,y,x) == nil then
|
|
return
|
|
end
|
|
tab[z][y][x] = nil
|
|
if not next(tab[z][y]) then
|
|
tab[z][y] = nil
|
|
end
|
|
if not next(tab[z]) then
|
|
tab[z] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
if not vector.unpack then
|
|
function vector.unpack(pos)
|
|
return pos.z, pos.y, pos.x
|
|
end
|
|
end
|