diff --git a/LICENSE.txt b/LICENSE.txt index bc0b1db..151369c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1 +1 @@ -WTFPL +CC0, except for code copied from e.g. minetest's builtin diff --git a/adammil_flood_fill.lua b/adammil_flood_fill.lua new file mode 100644 index 0000000..b6ceed9 --- /dev/null +++ b/adammil_flood_fill.lua @@ -0,0 +1,116 @@ +-- http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html + +local can_go +local marked_places +local function calc_2d_index(x, y) + return (y + 32768) * 65536 + x + 32768 +end +local function mark(x, y) + marked_places[calc_2d_index(x, y)] = true +end + +local _fill +local function fill(x, y) + if can_go(x, y) then + _fill(x, y) + end +end + +local corefill +function _fill(x, y) + while true do + local ox = x + local oy = y + while can_go(x, y-1) do + y = y-1 + end + while can_go(x-1, y) do + x = x-1 + end + if x == ox + and y == oy then + break + end + end + corefill(x, y) +end + +function corefill(x, y) + local lastcnt = 0 + repeat + local cnt = 0 + local sx = x + if lastcnt ~= 0 + and not can_go(y, x) then + -- go right to find the x start + repeat + lastcnt = lastcnt-1 + if lastcnt == 0 then + return + end + x = x+1 + until can_go(x, y) + sx = x + else + -- go left if possible, and mark and _fill above + while can_go(x-1, y) do + x = x-1 + mark(x, y) + if can_go(x, y-1) then + _fill(x, y-1) + end + cnt = cnt+1 + lastcnt = lastcnt+1 + end + end + + -- go right if possible, and mark + while can_go(sx, y) do + mark(sx, y) + cnt = cnt+1 + sx = sx+1 + end + + if cnt < lastcnt then + local e = x + lastcnt + sx = sx+1 + while sx < e do + if can_go(sx, y) then + corefill(sx, y) + end + sx = sx+1 + end + elseif cnt > lastcnt then + local ux = x + lastcnt + 1 + while ux < sx do + if can_go(ux, y-1) then + _fill(ux, y-1) + end + ux = ux+1 + end + end + lastcnt = cnt + y = y+1 + until lastcnt == 0 +end + +local function apply_fill(go_test, x0, y0, allow_revisit) + if allow_revisit then + can_go = go_test + else + local visited = {} + can_go = function(x, y) + local vi = calc_2d_index(x, y) + if visited[vi] then + return false + end + visited[vi] = true + return go_test(x, y) + end + end + marked_places = {} + fill(x0, y0) + return marked_places +end + +return apply_fill diff --git a/fill_3d.lua b/fill_3d.lua new file mode 100644 index 0000000..eaa7142 --- /dev/null +++ b/fill_3d.lua @@ -0,0 +1,53 @@ +-- Algorithm created by sofar and changed by others: +-- https://github.com/minetest/minetest/commit/d7908ee49480caaab63d05c8a53d93103579d7a9 + +local function search(go, p, apply_move, moves) + local num_moves = #moves + + -- We make a stack, and manually maintain size for performance. + -- Stored in the stack, we will maintain tables with pos, and + -- last neighbor visited. This way, when we get back to each + -- node, we know which directions we have already walked, and + -- which direction is the next to walk. + local s = {} + local n = 0 + -- The neighbor order we will visit from our table. + local v = 1 + + while true do + -- Push current pos onto the stack. + n = n + 1 + s[n] = {p = p, v = v} + -- Select next node from neighbor list. + p = apply_move(p, moves[v]) + -- Now we check out the node. If it is in need of an update, + -- it will let us know in the return value (true = updated). + if not go(p) then + -- If we don't need to "recurse" (walk) to it then pop + -- our previous pos off the stack and continue from there, + -- with the v value we were at when we last were at that + -- node + repeat + local pop = s[n] + p = pop.p + v = pop.v + s[n] = nil + n = n - 1 + -- If there's nothing left on the stack, and no + -- more sides to walk to, we're done and can exit + if n == 0 and v == num_moves then + return + end + until v < num_moves + -- The next round walk the next neighbor in list. + v = v + 1 + else + -- If we did need to walk the neighbor, then + -- start walking it from the walk order start (1), + -- and not the order we just pushed up the stack. + v = 1 + end + end +end + +return search diff --git a/init.lua b/init.lua index 96ded27..5d6a3bd 100644 --- a/init.lua +++ b/init.lua @@ -1,4 +1,4 @@ -local load_time_start = minetest.get_us_time() +local path = minetest.get_modpath"vector_extras" local funcs = {} @@ -399,6 +399,66 @@ function funcs.from_number(i) return {x=i, y=i, z=i} end +local adammil_fill = dofile(path .. "/adammil_flood_fill.lua") +function funcs.search_2d(go_test, x0, y0, allow_revisit, give_map) + marked_places = adammil_fill(go_test, x0, y0, allow_revisit) + if give_map then + return marked_places + end + local l = {} + for vi in pairs(marked_places) do + local x = (vi % 65536) - 32768 + local y = (math.floor(x / 65536) % 65536) - 32768 + l[#l+1] = {x, y} + end + return l +end + +local fallings_search = dofile(path .. "/fill_3d.lua") +local moves_touch = { + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = 0}, + {x = 1, y = 0, z = 0}, + {x = 0, y = -1, z = 0}, + {x = 0, y = 1, z = 0}, + {x = 0, y = 0, z = -1}, + {x = 0, y = 0, z = 1}, +} +local moves_near = {} +for z = -1,1 do + for y = -1,1 do + for x = -1,1 do + moves_near[#moves_near+1] = {x = x, y = y, z = z} + end + end +end + +function funcs.search_3d(can_go, startpos, apply_move, moves) + local visited = {} + local found = {} + local function on_visit(pos) + local vi = minetest.hash_node_position(pos) + if visited[vi] then + return false + end + visited[vi] = true + local valid_pos = can_go(pos) + if valid_pos then + found[#found+1] = pos + end + return valid_pos + end + if apply_move == "touch" then + apply_move = vector.add + moves = moves_touch + elseif apply_move == "near" then + apply_move = vector.add + moves = moves_near + end + fallings_search(on_visit, startpos, apply_move, moves) +end + + local explosion_tables = {} function funcs.explosion_table(r) local table = explosion_tables[r] @@ -961,7 +1021,6 @@ end vector_extras_functions = funcs -local path = minetest.get_modpath"vector_extras" dofile(path .. "/legacy.lua") --dofile(minetest.get_modpath("vector_extras").."/vector_meta.lua") @@ -977,7 +1036,6 @@ for name,func in pairs(funcs) do end end - local time = (minetest.get_us_time() - load_time_start) / 1000000 local msg = "[vector_extras] loaded after ca. " .. time .. " seconds." if time > 0.01 then