floodfill.lua 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. if not minetest.global_exists("hb4") then hb4 = {} end
  2. hb4.floodfill = hb4.floodfill or {}
  3. local function get_node_name(pos)
  4. local node = minetest.get_node_or_nil(pos)
  5. if node then
  6. return node.name
  7. end
  8. local meta = minetest.get_meta(pos)
  9. return meta:get_string("nodename")
  10. end
  11. -- Recursive algorithm.
  12. local function floodfill(startpos, nodelist, maxdepth)
  13. local traversal = {}
  14. local queue = {}
  15. local output = {}
  16. local curpos, hash, exists, name, found, norm, cb, count, depth
  17. local maxlength = 1
  18. local first = true
  19. local get_node_hash = minetest.hash_node_position
  20. startpos.d = 1
  21. queue[#queue+1] = startpos
  22. count = 1
  23. ::continue::
  24. curpos = queue[#queue]
  25. queue[#queue] = nil
  26. depth = curpos.d
  27. curpos.d = nil
  28. hash = get_node_hash(curpos)
  29. exists = false
  30. if traversal[hash] then
  31. exists = true
  32. if depth >= traversal[hash] then
  33. goto next
  34. end
  35. end
  36. if depth >= maxdepth then
  37. goto next
  38. end
  39. count = count + 1
  40. name = get_node_name(curpos)
  41. found = false
  42. norm = true
  43. cb = nil
  44. for n, m in pairs(nodelist) do
  45. if n == name then
  46. found = true
  47. if type(m) == "function" then
  48. cb = m
  49. elseif type(m) == "string" then
  50. if m == "leaf" then
  51. -- The first node scanned musn't be treated as a leaf.
  52. if not first then
  53. norm = false
  54. end
  55. end
  56. end
  57. break
  58. end
  59. end
  60. if not found then
  61. goto next
  62. end
  63. traversal[hash] = depth
  64. if not exists then
  65. output[#output+1] = {pos=curpos, name=name}
  66. end
  67. if cb then
  68. -- The node callback can add to the adjacency list.
  69. cb(curpos, queue, depth+1)
  70. elseif norm then
  71. queue[#queue+1] = {x=curpos.x+1, y=curpos.y, z=curpos.z, d=depth+1}
  72. queue[#queue+1] = {x=curpos.x-1, y=curpos.y, z=curpos.z, d=depth+1}
  73. queue[#queue+1] = {x=curpos.x, y=curpos.y+1, z=curpos.z, d=depth+1}
  74. queue[#queue+1] = {x=curpos.x, y=curpos.y-1, z=curpos.z, d=depth+1}
  75. queue[#queue+1] = {x=curpos.x, y=curpos.y, z=curpos.z+1, d=depth+1}
  76. queue[#queue+1] = {x=curpos.x, y=curpos.y, z=curpos.z-1, d=depth+1}
  77. end
  78. if #queue > maxlength then
  79. maxlength = #queue
  80. end
  81. ::next::
  82. first = false
  83. if #queue > 0 then
  84. goto continue
  85. end
  86. --minetest.chat_send_all("# Server: Array size: " .. maxlength)
  87. return count, output
  88. end
  89. local function floodfill2(startpos, nodelist, depth, maxdepth)
  90. local queue = {}
  91. local traversal = {}
  92. local output = {}
  93. local pos = startpos
  94. local hash
  95. local adjacent = {}
  96. local node
  97. local name
  98. local found
  99. local normaladjacency
  100. local nodecallback
  101. ::continue::
  102. if #queue > 0 then
  103. -- This should be the normal case.
  104. pos = queue[#queue]
  105. queue[#queue] = nil
  106. end
  107. hash = minetest.hash_node_position(pos)
  108. if traversal[hash] then
  109. -- This method makes the algorithm very slow, but is needed
  110. -- in order to correctly bind the flood distance to a given range.
  111. if traversal[hash] < depth then
  112. goto next
  113. end
  114. end
  115. traversal[hash] = depth
  116. if depth > maxdepth then
  117. goto next
  118. end
  119. node = minetest.get_node(pos)
  120. name = node.name
  121. found = false
  122. normaladjacency = true
  123. nodecallback = nil
  124. for n, m in pairs(nodelist) do
  125. if n == name then
  126. found = true
  127. if type(m) == "function" then
  128. nodecallback = m
  129. elseif type(m) == "string" then
  130. if m == "leaf" then
  131. normaladjacency = false
  132. end
  133. end
  134. break
  135. end
  136. end
  137. if not found then
  138. goto next
  139. end
  140. output[#output+1] = {pos=table.copy(pos), name=name}
  141. adjacent = {}
  142. if nodecallback then
  143. -- The node callback can set the adjacency list.
  144. adjacent = nodecallback(pos, node)
  145. elseif normaladjacency then
  146. adjacent = {
  147. {x=pos.x+1, y=pos.y, z=pos.z},
  148. {x=pos.x-1, y=pos.y, z=pos.z},
  149. {x=pos.x, y=pos.y+1, z=pos.z},
  150. {x=pos.x, y=pos.y-1, z=pos.z},
  151. {x=pos.x, y=pos.y, z=pos.z+1},
  152. {x=pos.x, y=pos.y, z=pos.z-1},
  153. }
  154. end
  155. -- Add adjacent nodes to the queue for checking.
  156. for k, v in ipairs(adjacent) do
  157. queue[#queue+1] = v
  158. end
  159. ::next::
  160. if #queue > 0 then
  161. goto continue
  162. end
  163. return output
  164. end
  165. -- Find all nodes attached to a given node, following only nodes in
  166. -- the given list of nodes. Uses a 'floodfill' recursive style algorithm.
  167. function hb4.floodfill.execute(pos, nodes, max)
  168. local count, out = floodfill(pos, nodes, max)
  169. return out, count
  170. --return floodfill2(pos, nodes, 1, max)
  171. end
  172. --[===[
  173. local function floodfill(startpos, nodelist, traversal, depth, maxdepth, output, calls)
  174. local hash = minetest.hash_node_position(startpos)
  175. local exists = false
  176. if traversal[hash] then
  177. exists = true
  178. if depth >= traversal[hash] then
  179. return calls
  180. end
  181. end
  182. if depth > maxdepth then
  183. return calls
  184. end
  185. local name = get_node_name(startpos)
  186. local found = false
  187. local normaladjacency = true
  188. local nodecallback
  189. for n, m in pairs(nodelist) do
  190. if n == name then
  191. found = true
  192. if type(m) == "function" then
  193. nodecallback = m
  194. elseif type(m) == "string" then
  195. if m == "leaf" then
  196. normaladjacency = false
  197. end
  198. end
  199. break
  200. end
  201. end
  202. if not found then
  203. return calls
  204. end
  205. traversal[hash] = depth
  206. if not exists then
  207. output[#output+1] = {pos=table.copy(startpos), name=name}
  208. end
  209. local adjacent = {}
  210. if nodecallback then
  211. -- The node callback can set the adjacency list.
  212. adjacent = nodecallback(startpos)
  213. elseif normaladjacency then
  214. adjacent = {
  215. {x=startpos.x+1, y=startpos.y, z=startpos.z},
  216. {x=startpos.x-1, y=startpos.y, z=startpos.z},
  217. {x=startpos.x, y=startpos.y+1, z=startpos.z},
  218. {x=startpos.x, y=startpos.y-1, z=startpos.z},
  219. {x=startpos.x, y=startpos.y, z=startpos.z+1},
  220. {x=startpos.x, y=startpos.y, z=startpos.z-1},
  221. }
  222. end
  223. for k, v in ipairs(adjacent) do
  224. calls = floodfill(v, nodelist, traversal, depth+1, maxdepth, output, calls+1)
  225. end
  226. return calls
  227. end
  228. --]===]