rnd 8db6acb801 code preprocessor fix
various demo scripts added
2019-03-05 14:15:59 +01:00

297 lines
8.3 KiB
Lua

-- subdivide rectangular area made from various node types into union of disjoint contigious boxes,
-- box count should be small, by rnd 2018
if not init then
init = true -- map saver
local dout = function(msg) minetest.chat_send_player("rnd", msg) end
minidx = 1 -- minimal position index not yet in box collection
blockdata = {}; --[1] = {p1,p2} [2] = list of nodenames, [3] = list [x][y][z] = nodename idx
table_copy = function(tab)
local out = {};
for k,v in pairs(tab) do
out[k] = v
end
return out
end
read_block = function(p1,p2,data)
if p1.x>p2.x or p1.y>p2.y or p1.z>p2.z then return end -- p1 coords must be smaller than p2 coords
data[1] = {{x=p1.x,y=p1.y,z=p1.z},{x=p2.x-p1.x+1,y=p2.y-p1.y+1,z=p2.z-p1.z+1}} -- startpos, dimensions
local nodedb = {} -- [nodename] = node idx
local nodelist = {} -- list of nodes: nodename1, nodename2,...
local nodes = {} -- table containing nodes [x][y][z] = node idx
local ncount = 0;
local idx = 0
for x = p1.x, p2.x do
nodes[x-p1.x+1] = {}
for y = p1.y, p2.y do
nodes[x-p1.x+1][y-p1.y+1] = {}
for z = p1.z, p2.z do
idx=idx+1
local nodename = minetest.get_node({x=x,y=y,z=z}).name
local nidx = nodedb[nodename]
if not nidx then
ncount = ncount + 1
nodedb[nodename] = ncount;
nodelist[ncount] = nodename
nidx = ncount
end
--nodes[idx] = nidx
nodes[x-p1.x+1][y-p1.y+1][z-p1.z+1 ] = nidx
end
end
end
data[2] = nodelist
data[3] = nodes
end
id2pos = function(idx,blockdata) -- idx starts with 0
local dx = blockdata[1][2].x
local dy = blockdata[1][2].y
local dz = blockdata[1][2].z
-- idx = z*dx*dy + y*dx + dx
local x = idx % dx;
idx = (idx - x)/dx
local y = idx % dy; -- y = y % (dy)
local z = (idx - y)/dy
return {x=x, y = y, z=z}
end
pos2id = function(pos,blockdata)
local dx = blockdata[1][2].x
local dy = blockdata[1][2].y
local dz = blockdata[1][2].z
local x = pos.x - blockdata[1][2].x;
local y = pos.y - blockdata[1][2].y;
local z = pos.z - blockdata[1][2].z;
return z*dx*dy + y*dx + dx
end
get_box = function(pos, blockdata, boxdata) -- return p1,p2, nodeidx defining largest contiguos box containing pos (all relative coordinates)
local nodeidx = blockdata[3][pos.x][pos.y][pos.z];
local active_dir = {[1]=true,[2]=true,[3]=true,[4]=true,[5]=true,[6]=true}; -- which dirs to search: x-,x+ y-,y+ z-,z+
local p1 = {x=pos.x,y=pos.y,z=pos.z} -- search limits
local p2 = {x=pos.x,y=pos.y,z=pos.z}
local stop = false;
local steps = 0
while not stop and steps < 10000 do -- steps 'safety'
steps = steps + 1
--dout("step " .. steps .. " p1 " .. serialize(p1) .. " p2 " .. serialize(p2) .. " dirs " .. serialize(active_dir))
stop = true
for idx,_ in pairs(active_dir) do -- try expansion in different directions
stop = false -- still something to do
if idx<=2 then
local x
if idx == 1 then x = p1.x-1 else x=p2.x+1 end
if blockdata[3][x] then
local bdata = blockdata[3][x];
for y = p1.y,p2.y do
for z = p1.z,p2.z do
if bdata[y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then
active_dir[idx] = nil; goto ex; -- not contiguous anymore
end
end
end
::ex::
else
active_dir[idx] = nil -- out of bounds, remove direction
end
if active_dir[idx] then -- expansion succesful
if idx == 1 then p1.x = p1.x -1 else p2.x = p2.x+1 end
end
elseif idx>=5 then
local z
if idx == 5 then z = p1.z-1 else z=p2.z+1 end
local bdata = blockdata[3]
if bdata[1][1][z] then
for x = p1.x,p2.x do
for y = p1.y,p2.y do
if bdata[x][y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then
active_dir[idx] = nil;goto ex; -- lua only breaks out of 1 loop :(
end
end
end
::ex::
else
active_dir[idx] = nil -- out of bounds, remove direction
end
if active_dir[idx] then -- expansion succesful
if idx == 5 then p1.z = p1.z -1 else p2.z = p2.z+1 end
end
else
local y
if idx == 3 then y = p1.y-1 else y=p2.y+1 end
local bdata = blockdata[3]
if bdata[1][y] then
for x = p1.x,p2.x do
for z = p1.z,p2.z do
if bdata[x][y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then
active_dir[idx] = nil; goto ex; -- not contiguous anymore
end
end
end
::ex::
else
active_dir[idx] = nil -- out of bounds, remove direction
end
if active_dir[idx] then -- expansion succesful
if idx == 3 then p1.y = p1.y-1 else p2.y = p2.y+1 end
end
end
end --try to expand: -x +x -y +y -z +z -x +x -y ... when not possible remove direction from list
end
return {p1,p2, nodeidx}
end
get_boxes = function(blockdata,boxdata)
local dx = blockdata[1][2].x
local dy = blockdata[1][2].y
local dz = blockdata[1][2].z
for k,v in pairs(boxdata) do boxdata[k] = nil end
local res = {}; -- list of boxes
for x=1,dx do
boxdata[x] = {}
for y = 1,dy do
boxdata[x][y] = {}
end
end
local xc, yc,zc;
xc = 1 ; yc = 1; zc = 1; -- current 'working' coordinates in block
local steps = 0;
local stop = false;
while not stop and steps < 1000 do
steps = steps + 1
stop = true
-- find 'next' coordinate thats not yet marked: todo - fix bugs here, skipping/unnecessary ...
xc = 1; -- set 1st index ( inner loop) to start
--dout("step " .. steps ..", search start xc yc zc ".. xc .. " " .. yc .. " " .. zc)
local x,y,z;x= xc; y=yc; z = zc;
while (z<=dz) do
while (y<=dy) do
while (x<=dx) do
if not boxdata[x][y][z] then
--dout("step " .. steps .. ", next point: " .. x .. " " .. y .. " " .. z)
stop = false; xc = x; yc=y; zc = z; goto ex
end
x=x+1
end
y=y+1; x=1; -- reset x-loop
end
z=z+1; y=1; -- reset y-loop
end
--dout("no unmarked point left, dim dx dy dz : " .. dx .. " " .. dy .. " " .. dz)
::ex::
if stop then break end
--if box non air add it
local box = get_box({x=xc,y=yc,z=zc},blockdata, boxdata);
if blockdata[2][box[3]]~= "air" then
res[#res+1] = box
end
-- mark box area as done
for x = box[1].x, box[2].x do
for y = box[1].y, box[2].y do
for z = box[1].z, box[2].z do
boxdata[x][y][z] = true
end
end
end
--dout("boxdata " .. serialize(boxdata))
end
return res
end
render_boxes = function(blockdata,boxes)
local dx = blockdata[1][2].x
local dy = blockdata[1][2].y
local dz = blockdata[1][2].z
local x0 = blockdata[1][1].x
local y0 = blockdata[1][1].y
local z0 = blockdata[1][1].z
local nodelist = {
"wool:white","wool:red","wool:green","wool:blue","wool:yellow", "wool:cyan","wool:pink",
"wool:brown","wool:magenta","wool:orange","wool:violet"
}
local nodelen = #nodelist
for x = 1, dx do
for y = 1, dy do
for z = 1, dz do
minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 1), z = z0+z-1},{name = "air"})
end
end
end
for i = 1,#boxes do
for x = boxes[i][1].x, boxes[i][2].x do
for y = boxes[i][1].y, boxes[i][2].y do
for z = boxes[i][1].z, boxes[i][2].z do
--minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 4), z = z0+z-1},{name = blockdata[2][boxes[i][3] ] })
minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 4), z = z0+z-1},{name = nodelist[1+((i-1) % nodelen)]})
end
end
end
end
end
--p1 = {x=-57,y=6,z=14};p2 = {x=-54,y=6,z=17}
p1 = {x=-45,y=502,z=-45};p2 = {x=-30,y=504,z=-30}
read_block(p1,p2,blockdata) -- careful, all p1 coords must be smaller than p2 coords coordinatewise
--self.label(serialize(blockdata))
-- 7 numbers per box: pos1, pos2, nodeidx. There are n^2 numbers for nxn grid, so for boxes to be efficient
-- 7*boxcount<n^3 or boxcount< n^3/7
--box = get_box({x=3,y=1,z=2},blockdata)
--say(serialize(box))
local boxdata = {};
local boxes = get_boxes(blockdata,boxdata);
self.label("dimensions " .. blockdata[1][2].x .. " " .. blockdata[1][2].y .. " " .. blockdata[1][2].z .. ", n^3/7 = " .. blockdata[1][2].x*blockdata[1][2].y*blockdata[1][2].z/7 .. ", boxes " .. #boxes )
--dout(serialize(boxes))
render_boxes(blockdata,boxes)
end