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