manipulations.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. --- Generic node manipulations.
  2. -- @module worldedit.manipulations
  3. local mh = worldedit.manip_helpers
  4. --- Sets a region to `node_names`.
  5. -- @param pos1
  6. -- @param pos2
  7. -- @param node_names Node name or list of node names.
  8. -- @return The number of nodes set.
  9. function worldedit.set(pos1, pos2, node_names)
  10. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  11. local manip, area = mh.init(pos1, pos2)
  12. local data = mh.get_empty_data(area)
  13. if type(node_names) == "string" then -- Only one type of node
  14. local id = minetest.get_content_id(node_names)
  15. -- Fill area with node
  16. for i in area:iterp(pos1, pos2) do
  17. data[i] = id
  18. end
  19. else -- Several types of nodes specified
  20. local node_ids = {}
  21. for i, v in ipairs(node_names) do
  22. node_ids[i] = minetest.get_content_id(v)
  23. end
  24. -- Fill area randomly with nodes
  25. local id_count, rand = #node_ids, math.random
  26. for i in area:iterp(pos1, pos2) do
  27. data[i] = node_ids[rand(id_count)]
  28. end
  29. end
  30. mh.finish(manip, data)
  31. return worldedit.volume(pos1, pos2)
  32. end
  33. --- Sets param2 of a region.
  34. -- @param pos1
  35. -- @param pos2
  36. -- @param param2 Value of param2 to set
  37. -- @return The number of nodes set.
  38. function worldedit.set_param2(pos1, pos2, param2)
  39. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  40. local manip, area = mh.init(pos1, pos2)
  41. local param2_data = manip:get_param2_data()
  42. -- Set param2 for every node
  43. for i in area:iterp(pos1, pos2) do
  44. param2_data[i] = param2
  45. end
  46. -- Update map
  47. manip:set_param2_data(param2_data)
  48. manip:write_to_map()
  49. manip:update_map()
  50. return worldedit.volume(pos1, pos2)
  51. end
  52. --- Replaces all instances of `search_node` with `replace_node` in a region.
  53. -- When `inverse` is `true`, replaces all instances that are NOT `search_node`.
  54. -- @return The number of nodes replaced.
  55. function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
  56. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  57. local manip, area = mh.init(pos1, pos2)
  58. local data = manip:get_data()
  59. local search_id = minetest.get_content_id(search_node)
  60. local replace_id = minetest.get_content_id(replace_node)
  61. local count = 0
  62. --- TODO: This could be shortened by checking `inverse` in the loop,
  63. -- but that would have a speed penalty. Is the penalty big enough
  64. -- to matter?
  65. if not inverse then
  66. for i in area:iterp(pos1, pos2) do
  67. if data[i] == search_id then
  68. data[i] = replace_id
  69. count = count + 1
  70. end
  71. end
  72. else
  73. for i in area:iterp(pos1, pos2) do
  74. if data[i] ~= search_id then
  75. data[i] = replace_id
  76. count = count + 1
  77. end
  78. end
  79. end
  80. mh.finish(manip, data)
  81. return count
  82. end
  83. --- Duplicates a region `amount` times with offset vector `direction`.
  84. -- Stacking is spread across server steps, one copy per step.
  85. -- @return The number of nodes stacked.
  86. function worldedit.stack2(pos1, pos2, direction, amount, finished)
  87. local i = 0
  88. local translated = {x=0, y=0, z=0}
  89. local function next_one()
  90. if i < amount then
  91. i = i + 1
  92. translated.x = translated.x + direction.x
  93. translated.y = translated.y + direction.y
  94. translated.z = translated.z + direction.z
  95. worldedit.copy2(pos1, pos2, translated)
  96. minetest.after(0, next_one)
  97. else
  98. if finished then
  99. finished()
  100. end
  101. end
  102. end
  103. next_one()
  104. return worldedit.volume(pos1, pos2) * amount
  105. end
  106. --- Copies a region along `axis` by `amount` nodes.
  107. -- @param pos1
  108. -- @param pos2
  109. -- @param axis Axis ("x", "y", or "z")
  110. -- @param amount
  111. -- @return The number of nodes copied.
  112. function worldedit.copy(pos1, pos2, axis, amount)
  113. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  114. worldedit.keep_loaded(pos1, pos2)
  115. local get_node, get_meta, set_node = minetest.get_node,
  116. minetest.get_meta, minetest.set_node
  117. -- Copy things backwards when negative to avoid corruption.
  118. -- FIXME: Lots of code duplication here.
  119. if amount < 0 then
  120. local pos = {}
  121. pos.x = pos1.x
  122. while pos.x <= pos2.x do
  123. pos.y = pos1.y
  124. while pos.y <= pos2.y do
  125. pos.z = pos1.z
  126. while pos.z <= pos2.z do
  127. local node = get_node(pos) -- Obtain current node
  128. local meta = get_meta(pos):to_table() -- Get meta of current node
  129. local value = pos[axis] -- Store current position
  130. pos[axis] = value + amount -- Move along axis
  131. set_node(pos, node) -- Copy node to new position
  132. get_meta(pos):from_table(meta) -- Set metadata of new node
  133. pos[axis] = value -- Restore old position
  134. pos.z = pos.z + 1
  135. end
  136. pos.y = pos.y + 1
  137. end
  138. pos.x = pos.x + 1
  139. end
  140. else
  141. local pos = {}
  142. pos.x = pos2.x
  143. while pos.x >= pos1.x do
  144. pos.y = pos2.y
  145. while pos.y >= pos1.y do
  146. pos.z = pos2.z
  147. while pos.z >= pos1.z do
  148. local node = get_node(pos) -- Obtain current node
  149. local meta = get_meta(pos):to_table() -- Get meta of current node
  150. local value = pos[axis] -- Store current position
  151. pos[axis] = value + amount -- Move along axis
  152. set_node(pos, node) -- Copy node to new position
  153. get_meta(pos):from_table(meta) -- Set metadata of new node
  154. pos[axis] = value -- Restore old position
  155. pos.z = pos.z - 1
  156. end
  157. pos.y = pos.y - 1
  158. end
  159. pos.x = pos.x - 1
  160. end
  161. end
  162. return worldedit.volume(pos1, pos2)
  163. end
  164. --- Copies a region by offset vector `off`.
  165. -- @param pos1
  166. -- @param pos2
  167. -- @param off
  168. -- @return The number of nodes copied.
  169. function worldedit.copy2(pos1, pos2, off)
  170. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  171. worldedit.keep_loaded(pos1, pos2)
  172. local get_node, get_meta, set_node = minetest.get_node,
  173. minetest.get_meta, minetest.set_node
  174. local pos = {}
  175. pos.x = pos2.x
  176. while pos.x >= pos1.x do
  177. pos.y = pos2.y
  178. while pos.y >= pos1.y do
  179. pos.z = pos2.z
  180. while pos.z >= pos1.z do
  181. local node = get_node(pos) -- Obtain current node
  182. local meta = get_meta(pos):to_table() -- Get meta of current node
  183. local newpos = vector.add(pos, off) -- Calculate new position
  184. set_node(newpos, node) -- Copy node to new position
  185. get_meta(newpos):from_table(meta) -- Set metadata of new node
  186. pos.z = pos.z - 1
  187. end
  188. pos.y = pos.y - 1
  189. end
  190. pos.x = pos.x - 1
  191. end
  192. return worldedit.volume(pos1, pos2)
  193. end
  194. --- Moves a region along `axis` by `amount` nodes.
  195. -- @return The number of nodes moved.
  196. function worldedit.move(pos1, pos2, axis, amount)
  197. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  198. worldedit.keep_loaded(pos1, pos2)
  199. --- TODO: Move slice by slice using schematic method in the move axis
  200. -- and transfer metadata in separate loop (and if the amount is
  201. -- greater than the length in the axis, copy whole thing at a time and
  202. -- erase original after, using schematic method).
  203. local get_node, get_meta, set_node, remove_node = minetest.get_node,
  204. minetest.get_meta, minetest.set_node, minetest.remove_node
  205. -- Copy things backwards when negative to avoid corruption.
  206. --- FIXME: Lots of code duplication here.
  207. if amount < 0 then
  208. local pos = {}
  209. pos.x = pos1.x
  210. while pos.x <= pos2.x do
  211. pos.y = pos1.y
  212. while pos.y <= pos2.y do
  213. pos.z = pos1.z
  214. while pos.z <= pos2.z do
  215. local node = get_node(pos) -- Obtain current node
  216. local meta = get_meta(pos):to_table() -- Get metadata of current node
  217. remove_node(pos) -- Remove current node
  218. local value = pos[axis] -- Store current position
  219. pos[axis] = value + amount -- Move along axis
  220. set_node(pos, node) -- Move node to new position
  221. get_meta(pos):from_table(meta) -- Set metadata of new node
  222. pos[axis] = value -- Restore old position
  223. pos.z = pos.z + 1
  224. end
  225. pos.y = pos.y + 1
  226. end
  227. pos.x = pos.x + 1
  228. end
  229. else
  230. local pos = {}
  231. pos.x = pos2.x
  232. while pos.x >= pos1.x do
  233. pos.y = pos2.y
  234. while pos.y >= pos1.y do
  235. pos.z = pos2.z
  236. while pos.z >= pos1.z do
  237. local node = get_node(pos) -- Obtain current node
  238. local meta = get_meta(pos):to_table() -- Get metadata of current node
  239. remove_node(pos) -- Remove current node
  240. local value = pos[axis] -- Store current position
  241. pos[axis] = value + amount -- Move along axis
  242. set_node(pos, node) -- Move node to new position
  243. get_meta(pos):from_table(meta) -- Set metadata of new node
  244. pos[axis] = value -- Restore old position
  245. pos.z = pos.z - 1
  246. end
  247. pos.y = pos.y - 1
  248. end
  249. pos.x = pos.x - 1
  250. end
  251. end
  252. return worldedit.volume(pos1, pos2)
  253. end
  254. --- Duplicates a region along `axis` `amount` times.
  255. -- Stacking is spread across server steps, one copy per step.
  256. -- @param pos1
  257. -- @param pos2
  258. -- @param axis Axis direction, "x", "y", or "z".
  259. -- @param count
  260. -- @return The number of nodes stacked.
  261. function worldedit.stack(pos1, pos2, axis, count)
  262. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  263. local length = pos2[axis] - pos1[axis] + 1
  264. if count < 0 then
  265. count = -count
  266. length = -length
  267. end
  268. local amount = 0
  269. local copy = worldedit.copy
  270. local i = 1
  271. function next_one()
  272. if i <= count then
  273. i = i + 1
  274. amount = amount + length
  275. copy(pos1, pos2, axis, amount)
  276. minetest.after(0, next_one)
  277. end
  278. end
  279. next_one()
  280. return worldedit.volume(pos1, pos2) * count
  281. end
  282. --- Stretches a region by a factor of positive integers along the X, Y, and Z
  283. -- axes, respectively, with `pos1` as the origin.
  284. -- @param pos1
  285. -- @param pos2
  286. -- @param stretch_x Amount to stretch along X axis.
  287. -- @param stretch_y Amount to stretch along Y axis.
  288. -- @param stretch_z Amount to stretch along Z axis.
  289. -- @return The number of nodes scaled.
  290. -- @return The new scaled position 1.
  291. -- @return The new scaled position 2.
  292. function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
  293. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  294. -- Prepare schematic of large node
  295. local get_node, get_meta, place_schematic = minetest.get_node,
  296. minetest.get_meta, minetest.place_schematic
  297. local placeholder_node = {name="", param1=255, param2=0}
  298. local nodes = {}
  299. for i = 1, stretch_x * stretch_y * stretch_z do
  300. nodes[i] = placeholder_node
  301. end
  302. local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}
  303. local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
  304. local new_pos2 = {
  305. x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
  306. y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
  307. z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
  308. }
  309. worldedit.keep_loaded(pos1, new_pos2)
  310. local pos = {x=pos2.x, y=0, z=0}
  311. local big_pos = {x=0, y=0, z=0}
  312. while pos.x >= pos1.x do
  313. pos.y = pos2.y
  314. while pos.y >= pos1.y do
  315. pos.z = pos2.z
  316. while pos.z >= pos1.z do
  317. local node = get_node(pos) -- Get current node
  318. local meta = get_meta(pos):to_table() -- Get meta of current node
  319. -- Calculate far corner of the big node
  320. local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
  321. local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
  322. local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
  323. -- Create large node
  324. placeholder_node.name = node.name
  325. placeholder_node.param2 = node.param2
  326. big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
  327. place_schematic(big_pos, schematic)
  328. -- Fill in large node meta
  329. if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
  330. -- Node has meta fields
  331. for x = 0, size_x do
  332. for y = 0, size_y do
  333. for z = 0, size_z do
  334. big_pos.x = pos_x + x
  335. big_pos.y = pos_y + y
  336. big_pos.z = pos_z + z
  337. -- Set metadata of new node
  338. get_meta(big_pos):from_table(meta)
  339. end
  340. end
  341. end
  342. end
  343. pos.z = pos.z - 1
  344. end
  345. pos.y = pos.y - 1
  346. end
  347. pos.x = pos.x - 1
  348. end
  349. return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
  350. end
  351. --- Transposes a region between two axes.
  352. -- @return The number of nodes transposed.
  353. -- @return The new transposed position 1.
  354. -- @return The new transposed position 2.
  355. function worldedit.transpose(pos1, pos2, axis1, axis2)
  356. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  357. local compare
  358. local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
  359. if extent1 > extent2 then
  360. compare = function(extent1, extent2)
  361. return extent1 > extent2
  362. end
  363. else
  364. compare = function(extent1, extent2)
  365. return extent1 < extent2
  366. end
  367. end
  368. -- Calculate the new position 2 after transposition
  369. local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
  370. new_pos2[axis1] = pos1[axis1] + extent2
  371. new_pos2[axis2] = pos1[axis2] + extent1
  372. local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
  373. if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
  374. if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
  375. worldedit.keep_loaded(pos1, upper_bound)
  376. local pos = {x=pos1.x, y=0, z=0}
  377. local get_node, get_meta, set_node = minetest.get_node,
  378. minetest.get_meta, minetest.set_node
  379. while pos.x <= pos2.x do
  380. pos.y = pos1.y
  381. while pos.y <= pos2.y do
  382. pos.z = pos1.z
  383. while pos.z <= pos2.z do
  384. local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
  385. if compare(extent1, extent2) then -- Transpose only if below the diagonal
  386. local node1 = get_node(pos)
  387. local meta1 = get_meta(pos):to_table()
  388. local value1, value2 = pos[axis1], pos[axis2] -- Save position values
  389. pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
  390. local node2 = get_node(pos)
  391. local meta2 = get_meta(pos):to_table()
  392. set_node(pos, node1)
  393. get_meta(pos):from_table(meta1)
  394. pos[axis1], pos[axis2] = value1, value2 -- Restore position values
  395. set_node(pos, node2)
  396. get_meta(pos):from_table(meta2)
  397. end
  398. pos.z = pos.z + 1
  399. end
  400. pos.y = pos.y + 1
  401. end
  402. pos.x = pos.x + 1
  403. end
  404. return worldedit.volume(pos1, pos2), pos1, new_pos2
  405. end
  406. --- Flips a region along `axis`.
  407. -- @return The number of nodes flipped.
  408. function worldedit.flip(pos1, pos2, axis)
  409. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  410. worldedit.keep_loaded(pos1, pos2)
  411. --- TODO: Flip the region slice by slice along the flip axis using schematic method.
  412. local pos = {x=pos1.x, y=0, z=0}
  413. local start = pos1[axis] + pos2[axis]
  414. pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
  415. local get_node, get_meta, set_node = minetest.get_node,
  416. minetest.get_meta, minetest.set_node
  417. while pos.x <= pos2.x do
  418. pos.y = pos1.y
  419. while pos.y <= pos2.y do
  420. pos.z = pos1.z
  421. while pos.z <= pos2.z do
  422. local node1 = get_node(pos)
  423. local meta1 = get_meta(pos):to_table()
  424. local value = pos[axis] -- Save position
  425. pos[axis] = start - value -- Shift position
  426. local node2 = get_node(pos)
  427. local meta2 = get_meta(pos):to_table()
  428. set_node(pos, node1)
  429. get_meta(pos):from_table(meta1)
  430. pos[axis] = value -- Restore position
  431. set_node(pos, node2)
  432. get_meta(pos):from_table(meta2)
  433. pos.z = pos.z + 1
  434. end
  435. pos.y = pos.y + 1
  436. end
  437. pos.x = pos.x + 1
  438. end
  439. return worldedit.volume(pos1, pos2)
  440. end
  441. --- Rotates a region clockwise around an axis.
  442. -- @param pos1
  443. -- @param pos2
  444. -- @param axis Axis ("x", "y", or "z").
  445. -- @param angle Angle in degrees (90 degree increments only).
  446. -- @return The number of nodes rotated.
  447. -- @return The new first position.
  448. -- @return The new second position.
  449. function worldedit.rotate(pos1, pos2, axis, angle)
  450. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  451. local other1, other2 = worldedit.get_axis_others(axis)
  452. angle = angle % 360
  453. local count
  454. if angle == 90 then
  455. worldedit.flip(pos1, pos2, other1)
  456. count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
  457. elseif angle == 180 then
  458. worldedit.flip(pos1, pos2, other1)
  459. count = worldedit.flip(pos1, pos2, other2)
  460. elseif angle == 270 then
  461. worldedit.flip(pos1, pos2, other2)
  462. count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
  463. else
  464. error("Only 90 degree increments are supported!")
  465. end
  466. return count, pos1, pos2
  467. end
  468. --- Rotates all oriented nodes in a region clockwise around the Y axis.
  469. -- @param pos1
  470. -- @param pos2
  471. -- @param angle Angle in degrees (90 degree increments only).
  472. -- @return The number of nodes oriented.
  473. -- TODO: Support 6D facedir rotation along arbitrary axis.
  474. function worldedit.orient(pos1, pos2, angle)
  475. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  476. local registered_nodes = minetest.registered_nodes
  477. local wallmounted = {
  478. [90] = {[0]=0, 1, 5, 4, 2, 3},
  479. [180] = {[0]=0, 1, 3, 2, 5, 4},
  480. [270] = {[0]=0, 1, 4, 5, 3, 2}
  481. }
  482. local facedir = {
  483. [90] = {[0]=1, 2, 3, 0},
  484. [180] = {[0]=2, 3, 0, 1},
  485. [270] = {[0]=3, 0, 1, 2}
  486. }
  487. angle = angle % 360
  488. if angle == 0 then
  489. return 0
  490. end
  491. if angle % 90 ~= 0 then
  492. error("Only 90 degree increments are supported!")
  493. end
  494. local wallmounted_substitution = wallmounted[angle]
  495. local facedir_substitution = facedir[angle]
  496. worldedit.keep_loaded(pos1, pos2)
  497. local count = 0
  498. local set_node, get_node, get_meta, swap_node = minetest.set_node,
  499. minetest.get_node, minetest.get_meta, minetest.swap_node
  500. local pos = {x=pos1.x, y=0, z=0}
  501. while pos.x <= pos2.x do
  502. pos.y = pos1.y
  503. while pos.y <= pos2.y do
  504. pos.z = pos1.z
  505. while pos.z <= pos2.z do
  506. local node = get_node(pos)
  507. local def = registered_nodes[node.name]
  508. if def then
  509. if def.paramtype2 == "wallmounted" then
  510. node.param2 = wallmounted_substitution[node.param2]
  511. local meta = get_meta(pos):to_table()
  512. set_node(pos, node)
  513. get_meta(pos):from_table(meta)
  514. count = count + 1
  515. elseif def.paramtype2 == "facedir" then
  516. node.param2 = facedir_substitution[node.param2]
  517. local meta = get_meta(pos):to_table()
  518. set_node(pos, node)
  519. get_meta(pos):from_table(meta)
  520. count = count + 1
  521. end
  522. end
  523. pos.z = pos.z + 1
  524. end
  525. pos.y = pos.y + 1
  526. end
  527. pos.x = pos.x + 1
  528. end
  529. return count
  530. end
  531. --- Attempts to fix the lighting in a region.
  532. -- @return The number of nodes updated.
  533. function worldedit.fixlight(pos1, pos2)
  534. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  535. local vmanip = minetest.get_voxel_manip(pos1, pos2)
  536. vmanip:write_to_map()
  537. vmanip:update_map() -- this updates the lighting
  538. return worldedit.volume(pos1, pos2)
  539. end
  540. --- Clears all objects in a region.
  541. -- @return The number of objects cleared.
  542. function worldedit.clear_objects(pos1, pos2)
  543. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  544. worldedit.keep_loaded(pos1, pos2)
  545. -- Offset positions to include full nodes (positions are in the center of nodes)
  546. local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
  547. local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
  548. -- Center of region
  549. local center = {
  550. x = pos1x + ((pos2x - pos1x) / 2),
  551. y = pos1y + ((pos2y - pos1y) / 2),
  552. z = pos1z + ((pos2z - pos1z) / 2)
  553. }
  554. -- Bounding sphere radius
  555. local radius = math.sqrt(
  556. (center.x - pos1x) ^ 2 +
  557. (center.y - pos1y) ^ 2 +
  558. (center.z - pos1z) ^ 2)
  559. local count = 0
  560. for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
  561. local entity = obj:get_luaentity()
  562. -- Avoid players and WorldEdit entities
  563. if not obj:is_player() and (not entity or
  564. not entity.name:find("^worldedit:")) then
  565. local pos = obj:getpos()
  566. if pos.x >= pos1x and pos.x <= pos2x and
  567. pos.y >= pos1y and pos.y <= pos2y and
  568. pos.z >= pos1z and pos.z <= pos2z then
  569. -- Inside region
  570. obj:remove()
  571. count = count + 1
  572. end
  573. end
  574. end
  575. return count
  576. end