local util = {} local AXES = { x = vector.new(1, 0, 0), y = vector.new(0, 1, 0), z = vector.new(0, 0, 1), } util.AXES = AXES; -- Gets an array of vectors relative to `pos` of neighbors that have the specified `match`. local function find_neighbors_with_name(pos, match) local result = {} for _, v in pairs(AXES) do if minetest.get_node(pos - v).name == match then table.insert(result, -v) end end for _, v in pairs(AXES) do if minetest.get_node(pos + v).name == match then table.insert(result, v) end end return result end util.find_neighbors_with_name = find_neighbors_with_name; -- Moves `pos` towards `dir` while node is still `match`. -- returns (vector, array) or nil local function move_until_corner(pos, match, dir) repeat pos = pos + dir until minetest.get_node(pos + dir).name ~= match local neighbors = find_neighbors_with_name(pos, match) if #neighbors == 2 then return pos, neighbors else return nil end end -- Moves `pos` towards `dir`, checking that each node has exactly two neighbors -- that of type `match`, until a corner turning towards `expected_corner_dir` is hit. -- returns vector or nil local function move_until_corner_with_checks(pos, match, dir, expected_corner_dir) while true do pos = pos + dir local neighbors = find_neighbors_with_name(pos, match) if #neighbors ~= 2 then return nil end -- Get the "next direction" the piece is going. local next_dir = neighbors[1] if next_dir == -dir then next_dir = neighbors[2] end if next_dir ~= dir then if next_dir == expected_corner_dir then return pos else return nil end end end end -- Finds a rectangle structure made from the specified node `match`. -- returns { min = vector, max = vector } or nil local function find_rectangle(pos, match) local neighbors = find_neighbors_with_name(pos, match) if #neighbors ~= 2 then return nil end -- If we found a line piece (neighbors are opposite) then move in the -- direction of the smallest xyz coordinate. if neighbors[1] == -neighbors[2] then -- NOTE: Due to how `get_neighbors_with_name` is written, the first element -- is guaranteed to contain the vector pointing towards negative. pos, neighbors = move_until_corner(pos, match, neighbors[1]) if not pos then return nil end end -- We're now guaranteed to be at a corner piece. -- Move to the corner piece with the smallest xyz coordinate. while neighbors[1].x + neighbors[1].y + neighbors[1].z < 1 do pos, neighbors = move_until_corner(pos, match, neighbors[1]) if not pos then return nil end end -- Find the neighboring corners of the starting corner. local first_corner = move_until_corner_with_checks(pos, match, neighbors[1], neighbors[2]) if not first_corner then return nil end local second_corner = move_until_corner_with_checks(pos, match, neighbors[2], neighbors[1]) if not second_corner then return nil end -- Continue towards the final corner from the new corners. first_corner = move_until_corner_with_checks(first_corner, match, neighbors[2], -neighbors[1]) if not first_corner then return nil end second_corner = move_until_corner_with_checks(second_corner, match, neighbors[1], -neighbors[2]) if not second_corner then return nil end -- Ensure that they have met up at the same location. if first_corner ~= second_corner then return nil end return { min = pos, max = second_corner } end util.find_rectangle = find_rectangle; return util