]> git.lizzy.rs Git - worldedit.git/blob - worldedit/manipulations.lua
Fix performance of //stack, //stack2
[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         if not inverse then\r
80                 for i in area:iterp(pos1, pos2) do\r
81                         if data[i] == search_id then\r
82                                 data[i] = replace_id\r
83                                 count = count + 1\r
84                         end\r
85                 end\r
86         else\r
87                 for i in area:iterp(pos1, pos2) do\r
88                         if data[i] ~= search_id then\r
89                                 data[i] = replace_id\r
90                                 count = count + 1\r
91                         end\r
92                 end\r
93         end\r
94 \r
95         mh.finish(manip, data)\r
96 \r
97         return count\r
98 end\r
99 \r
100 \r
101 local function deferred_execution(next_one, finished)\r
102         -- Allocate 100% of server step for execution (might lag a little)\r
103         local allocated_usecs =\r
104                 tonumber(minetest.settings:get("dedicated_server_step")) * 1000000\r
105         local function f()\r
106                 local deadline = minetest.get_us_time() + allocated_usecs\r
107                 repeat\r
108                         local is_done = next_one()\r
109                         if is_done then\r
110                                 if finished then\r
111                                         finished()\r
112                                 end\r
113                                 return\r
114                         end\r
115                 until minetest.get_us_time() >= deadline\r
116                 minetest.after(0, f)\r
117         end\r
118         f()\r
119 end\r
120 \r
121 --- Duplicates a region `amount` times with offset vector `direction`.\r
122 -- Stacking is spread across server steps.\r
123 -- @return The number of nodes stacked.\r
124 function worldedit.stack2(pos1, pos2, direction, amount, finished)\r
125         -- Protect arguments from external changes during execution\r
126         pos1 = table.copy(pos1)\r
127         pos2 = table.copy(pos2)\r
128         direction = table.copy(direction)\r
129 \r
130         local i = 0\r
131         local translated = {x=0, y=0, z=0}\r
132         local function step()\r
133                 translated.x = translated.x + direction.x\r
134                 translated.y = translated.y + direction.y\r
135                 translated.z = translated.z + direction.z\r
136                 worldedit.copy2(pos1, pos2, translated)\r
137                 i = i + 1\r
138                 return i >= amount\r
139         end\r
140         deferred_execution(step, finished)\r
141 \r
142         return worldedit.volume(pos1, pos2) * amount\r
143 end\r
144 \r
145 \r
146 --- Copies a region along `axis` by `amount` nodes.\r
147 -- @param pos1\r
148 -- @param pos2\r
149 -- @param axis Axis ("x", "y", or "z")\r
150 -- @param amount\r
151 -- @return The number of nodes copied.\r
152 function worldedit.copy(pos1, pos2, axis, amount)\r
153         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
154 \r
155         local dim = vector.add(vector.subtract(pos2, pos1), 1)\r
156         if amount > 0 and amount < dim[axis] then\r
157                 -- Source and destination region are overlapping and moving needs to\r
158                 -- happen in reverse.\r
159                 -- FIXME: I can't be bothered, so just defer to the legacy code for now.\r
160                 return worldedit.legacy_copy(pos1, pos2, axis, amount)\r
161         end\r
162 \r
163         local off = {x=0, y=0, z=0}\r
164         off[axis] = amount\r
165         return worldedit.copy2(pos1, pos2, off)\r
166 end\r
167 \r
168 -- This function is not offical part of the API and may be removed at any time.\r
169 function worldedit.legacy_copy(pos1, pos2, axis, amount)\r
170         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
171 \r
172         worldedit.keep_loaded(pos1, pos2)\r
173 \r
174         local get_node, get_meta, set_node = minetest.get_node,\r
175                         minetest.get_meta, minetest.set_node\r
176         -- Copy things backwards\r
177         local pos = {}\r
178         pos.x = pos2.x\r
179         while pos.x >= pos1.x do\r
180                 pos.y = pos2.y\r
181                 while pos.y >= pos1.y do\r
182                         pos.z = pos2.z\r
183                         while pos.z >= pos1.z do\r
184                                 local node = get_node(pos) -- Obtain current node\r
185                                 local meta = get_meta(pos):to_table() -- Get meta of current node\r
186                                 local value = pos[axis] -- Store current position\r
187                                 pos[axis] = value + amount -- Move along axis\r
188                                 set_node(pos, node) -- Copy node to new position\r
189                                 get_meta(pos):from_table(meta) -- Set metadata of new node\r
190                                 pos[axis] = value -- Restore old position\r
191                                 pos.z = pos.z - 1\r
192                         end\r
193                         pos.y = pos.y - 1\r
194                 end\r
195                 pos.x = pos.x - 1\r
196         end\r
197         return worldedit.volume(pos1, pos2)\r
198 end\r
199 \r
200 --- Copies a region by offset vector `off`.\r
201 -- @param pos1\r
202 -- @param pos2\r
203 -- @param off\r
204 -- @return The number of nodes copied.\r
205 function worldedit.copy2(pos1, pos2, off)\r
206         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
207 \r
208         local src_manip, src_area = mh.init(pos1, pos2)\r
209         local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}\r
210         local src_offset = vector.subtract(pos1, src_area.MinEdge)\r
211 \r
212         local dpos1 = vector.add(pos1, off)\r
213         local dpos2 = vector.add(pos2, off)\r
214         local dim = vector.add(vector.subtract(pos2, pos1), 1)\r
215 \r
216         local dst_manip, dst_area = mh.init(dpos1, dpos2)\r
217         local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}\r
218         local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)\r
219 \r
220         local function do_copy(src_data, dst_data)\r
221                 for z = 0, dim.z-1 do\r
222                         local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing\r
223                         local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1\r
224                         for y = 0, dim.y-1 do\r
225                                 local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y\r
226                                 local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y\r
227                                 -- Copy entire row at once\r
228                                 local src_index_x = src_index_y + src_offset.x\r
229                                 local dst_index_x = dst_index_y + dst_offset.x\r
230                                 for x = 0, dim.x-1 do\r
231                                         dst_data[dst_index_x + x] = src_data[src_index_x + x]\r
232                                 end\r
233                         end\r
234                 end\r
235         end\r
236 \r
237         -- Copy node data\r
238         local src_data = src_manip:get_data()\r
239         local dst_data = dst_manip:get_data()\r
240         do_copy(src_data, dst_data)\r
241         dst_manip:set_data(dst_data)\r
242 \r
243         -- Copy param1\r
244         src_manip:get_light_data(src_data)\r
245         dst_manip:get_light_data(dst_data)\r
246         do_copy(src_data, dst_data)\r
247         dst_manip:set_light_data(dst_data)\r
248 \r
249         -- Copy param2\r
250         src_manip:get_param2_data(src_data)\r
251         dst_manip:get_param2_data(dst_data)\r
252         do_copy(src_data, dst_data)\r
253         dst_manip:set_param2_data(dst_data)\r
254 \r
255         mh.finish(dst_manip)\r
256         src_data = nil\r
257         dst_data = nil\r
258 \r
259         -- Copy metadata\r
260         local get_meta = minetest.get_meta\r
261         for z = 0, dim.z-1 do\r
262                 for y = 0, dim.y-1 do\r
263                         for x = 0, dim.x-1 do\r
264                                 local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}\r
265                                 local meta = get_meta(pos):to_table()\r
266                                 pos = vector.add(pos, off)\r
267                                 get_meta(pos):from_table(meta)\r
268                         end\r
269                 end\r
270         end\r
271 \r
272         return worldedit.volume(pos1, pos2)\r
273 end\r
274 \r
275 --- Moves a region along `axis` by `amount` nodes.\r
276 -- @return The number of nodes moved.\r
277 function worldedit.move(pos1, pos2, axis, amount)\r
278         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
279 \r
280         local dim = vector.add(vector.subtract(pos2, pos1), 1)\r
281         if math.abs(amount) < dim[axis] then\r
282                 -- Source and destination region are overlapping\r
283                 -- FIXME: I can't be bothered, so just defer to the legacy code for now.\r
284                 return worldedit.legacy_move(pos1, pos2, axis, amount)\r
285         end\r
286 \r
287         -- Copy stuff to new location\r
288         local off = {x=0, y=0, z=0}\r
289         off[axis] = amount\r
290         worldedit.copy2(pos1, pos2, off)\r
291         -- Nuke old area\r
292         worldedit.set(pos1, pos2, "air")\r
293 \r
294         return worldedit.volume(pos1, pos2)\r
295 end\r
296 \r
297 -- This function is not offical part of the API and may be removed at any time.\r
298 function worldedit.legacy_move(pos1, pos2, axis, amount)\r
299         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
300 \r
301         worldedit.keep_loaded(pos1, pos2)\r
302 \r
303         local get_node, get_meta, set_node, remove_node = minetest.get_node,\r
304                         minetest.get_meta, minetest.set_node, minetest.remove_node\r
305         -- Copy things backwards when negative to avoid corruption.\r
306         if amount < 0 then\r
307                 local pos = {}\r
308                 pos.x = pos1.x\r
309                 while pos.x <= pos2.x do\r
310                         pos.y = pos1.y\r
311                         while pos.y <= pos2.y do\r
312                                 pos.z = pos1.z\r
313                                 while pos.z <= pos2.z do\r
314                                         local node = get_node(pos) -- Obtain current node\r
315                                         local meta = get_meta(pos):to_table() -- Get metadata of current node\r
316                                         remove_node(pos) -- Remove current node\r
317                                         local value = pos[axis] -- Store current position\r
318                                         pos[axis] = value + amount -- Move along axis\r
319                                         set_node(pos, node) -- Move node to new position\r
320                                         get_meta(pos):from_table(meta) -- Set metadata of new node\r
321                                         pos[axis] = value -- Restore old position\r
322                                         pos.z = pos.z + 1\r
323                                 end\r
324                                 pos.y = pos.y + 1\r
325                         end\r
326                         pos.x = pos.x + 1\r
327                 end\r
328         else\r
329                 local pos = {}\r
330                 pos.x = pos2.x\r
331                 while pos.x >= pos1.x do\r
332                         pos.y = pos2.y\r
333                         while pos.y >= pos1.y do\r
334                                 pos.z = pos2.z\r
335                                 while pos.z >= pos1.z do\r
336                                         local node = get_node(pos) -- Obtain current node\r
337                                         local meta = get_meta(pos):to_table() -- Get metadata of current node\r
338                                         remove_node(pos) -- Remove current node\r
339                                         local value = pos[axis] -- Store current position\r
340                                         pos[axis] = value + amount -- Move along axis\r
341                                         set_node(pos, node) -- Move node to new position\r
342                                         get_meta(pos):from_table(meta) -- Set metadata of new node\r
343                                         pos[axis] = value -- Restore old position\r
344                                         pos.z = pos.z - 1\r
345                                 end\r
346                                 pos.y = pos.y - 1\r
347                         end\r
348                         pos.x = pos.x - 1\r
349                 end\r
350         end\r
351         return worldedit.volume(pos1, pos2)\r
352 end\r
353 \r
354 \r
355 --- Duplicates a region along `axis` `amount` times.\r
356 -- Stacking is spread across server steps.\r
357 -- @param pos1\r
358 -- @param pos2\r
359 -- @param axis Axis direction, "x", "y", or "z".\r
360 -- @param count\r
361 -- @return The number of nodes stacked.\r
362 function worldedit.stack(pos1, pos2, axis, count, finished)\r
363         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
364         local length = pos2[axis] - pos1[axis] + 1\r
365         if count < 0 then\r
366                 count = -count\r
367                 length = -length\r
368         end\r
369 \r
370         local i, distance = 0, 0\r
371         local function step()\r
372                 distance = distance + length\r
373                 worldedit.copy(pos1, pos2, axis, distance)\r
374                 i = i + 1\r
375                 return i >= count\r
376         end\r
377         deferred_execution(step, finished)\r
378 \r
379         return worldedit.volume(pos1, pos2) * count\r
380 end\r
381 \r
382 \r
383 --- Stretches a region by a factor of positive integers along the X, Y, and Z\r
384 -- axes, respectively, with `pos1` as the origin.\r
385 -- @param pos1\r
386 -- @param pos2\r
387 -- @param stretch_x Amount to stretch along X axis.\r
388 -- @param stretch_y Amount to stretch along Y axis.\r
389 -- @param stretch_z Amount to stretch along Z axis.\r
390 -- @return The number of nodes scaled.\r
391 -- @return The new scaled position 1.\r
392 -- @return The new scaled position 2.\r
393 function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)\r
394         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
395 \r
396         -- Prepare schematic of large node\r
397         local get_node, get_meta, place_schematic = minetest.get_node,\r
398                         minetest.get_meta, minetest.place_schematic\r
399         local placeholder_node = {name="", param1=255, param2=0}\r
400         local nodes = {}\r
401         for i = 1, stretch_x * stretch_y * stretch_z do\r
402                 nodes[i] = placeholder_node\r
403         end\r
404         local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}\r
405 \r
406         local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1\r
407 \r
408         local new_pos2 = {\r
409                 x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,\r
410                 y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,\r
411                 z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,\r
412         }\r
413         worldedit.keep_loaded(pos1, new_pos2)\r
414 \r
415         local pos = {x=pos2.x, y=0, z=0}\r
416         local big_pos = {x=0, y=0, z=0}\r
417         while pos.x >= pos1.x do\r
418                 pos.y = pos2.y\r
419                 while pos.y >= pos1.y do\r
420                         pos.z = pos2.z\r
421                         while pos.z >= pos1.z do\r
422                                 local node = get_node(pos) -- Get current node\r
423                                 local meta = get_meta(pos):to_table() -- Get meta of current node\r
424 \r
425                                 -- Calculate far corner of the big node\r
426                                 local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x\r
427                                 local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y\r
428                                 local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z\r
429 \r
430                                 -- Create large node\r
431                                 placeholder_node.name = node.name\r
432                                 placeholder_node.param2 = node.param2\r
433                                 big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z\r
434                                 place_schematic(big_pos, schematic)\r
435 \r
436                                 -- Fill in large node meta\r
437                                 if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then\r
438                                         -- Node has meta fields\r
439                                         for x = 0, size_x do\r
440                                         for y = 0, size_y do\r
441                                         for z = 0, size_z do\r
442                                                 big_pos.x = pos_x + x\r
443                                                 big_pos.y = pos_y + y\r
444                                                 big_pos.z = pos_z + z\r
445                                                 -- Set metadata of new node\r
446                                                 get_meta(big_pos):from_table(meta)\r
447                                         end\r
448                                         end\r
449                                         end\r
450                                 end\r
451                                 pos.z = pos.z - 1\r
452                         end\r
453                         pos.y = pos.y - 1\r
454                 end\r
455                 pos.x = pos.x - 1\r
456         end\r
457         return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2\r
458 end\r
459 \r
460 \r
461 --- Transposes a region between two axes.\r
462 -- @return The number of nodes transposed.\r
463 -- @return The new transposed position 1.\r
464 -- @return The new transposed position 2.\r
465 function worldedit.transpose(pos1, pos2, axis1, axis2)\r
466         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
467 \r
468         local compare\r
469         local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]\r
470 \r
471         if extent1 > extent2 then\r
472                 compare = function(extent1, extent2)\r
473                         return extent1 > extent2\r
474                 end\r
475         else\r
476                 compare = function(extent1, extent2)\r
477                         return extent1 < extent2\r
478                 end\r
479         end\r
480 \r
481         -- Calculate the new position 2 after transposition\r
482         local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
483         new_pos2[axis1] = pos1[axis1] + extent2\r
484         new_pos2[axis2] = pos1[axis2] + extent1\r
485 \r
486         local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}\r
487         if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end\r
488         if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end\r
489         worldedit.keep_loaded(pos1, upper_bound)\r
490 \r
491         local pos = {x=pos1.x, y=0, z=0}\r
492         local get_node, get_meta, set_node = minetest.get_node,\r
493                         minetest.get_meta, minetest.set_node\r
494         while pos.x <= pos2.x do\r
495                 pos.y = pos1.y\r
496                 while pos.y <= pos2.y do\r
497                         pos.z = pos1.z\r
498                         while pos.z <= pos2.z do\r
499                                 local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]\r
500                                 if compare(extent1, extent2) then -- Transpose only if below the diagonal\r
501                                         local node1 = get_node(pos)\r
502                                         local meta1 = get_meta(pos):to_table()\r
503                                         local value1, value2 = pos[axis1], pos[axis2] -- Save position values\r
504                                         pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents\r
505                                         local node2 = get_node(pos)\r
506                                         local meta2 = get_meta(pos):to_table()\r
507                                         set_node(pos, node1)\r
508                                         get_meta(pos):from_table(meta1)\r
509                                         pos[axis1], pos[axis2] = value1, value2 -- Restore position values\r
510                                         set_node(pos, node2)\r
511                                         get_meta(pos):from_table(meta2)\r
512                                 end\r
513                                 pos.z = pos.z + 1\r
514                         end\r
515                         pos.y = pos.y + 1\r
516                 end\r
517                 pos.x = pos.x + 1\r
518         end\r
519         return worldedit.volume(pos1, pos2), pos1, new_pos2\r
520 end\r
521 \r
522 \r
523 --- Flips a region along `axis`.\r
524 -- @return The number of nodes flipped.\r
525 function worldedit.flip(pos1, pos2, axis)\r
526         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
527 \r
528         worldedit.keep_loaded(pos1, pos2)\r
529 \r
530         --- TODO: Flip the region slice by slice along the flip axis using schematic method.\r
531         local pos = {x=pos1.x, y=0, z=0}\r
532         local start = pos1[axis] + pos2[axis]\r
533         pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)\r
534         local get_node, get_meta, set_node = minetest.get_node,\r
535                         minetest.get_meta, minetest.set_node\r
536         while pos.x <= pos2.x do\r
537                 pos.y = pos1.y\r
538                 while pos.y <= pos2.y do\r
539                         pos.z = pos1.z\r
540                         while pos.z <= pos2.z do\r
541                                 local node1 = get_node(pos)\r
542                                 local meta1 = get_meta(pos):to_table()\r
543                                 local value = pos[axis] -- Save position\r
544                                 pos[axis] = start - value -- Shift position\r
545                                 local node2 = get_node(pos)\r
546                                 local meta2 = get_meta(pos):to_table()\r
547                                 set_node(pos, node1)\r
548                                 get_meta(pos):from_table(meta1)\r
549                                 pos[axis] = value -- Restore position\r
550                                 set_node(pos, node2)\r
551                                 get_meta(pos):from_table(meta2)\r
552                                 pos.z = pos.z + 1\r
553                         end\r
554                         pos.y = pos.y + 1\r
555                 end\r
556                 pos.x = pos.x + 1\r
557         end\r
558         return worldedit.volume(pos1, pos2)\r
559 end\r
560 \r
561 \r
562 --- Rotates a region clockwise around an axis.\r
563 -- @param pos1\r
564 -- @param pos2\r
565 -- @param axis Axis ("x", "y", or "z").\r
566 -- @param angle Angle in degrees (90 degree increments only).\r
567 -- @return The number of nodes rotated.\r
568 -- @return The new first position.\r
569 -- @return The new second position.\r
570 function worldedit.rotate(pos1, pos2, axis, angle)\r
571         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
572 \r
573         local other1, other2 = worldedit.get_axis_others(axis)\r
574         angle = angle % 360\r
575 \r
576         local count\r
577         if angle == 90 then\r
578                 worldedit.flip(pos1, pos2, other1)\r
579                 count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
580         elseif angle == 180 then\r
581                 worldedit.flip(pos1, pos2, other1)\r
582                 count = worldedit.flip(pos1, pos2, other2)\r
583         elseif angle == 270 then\r
584                 worldedit.flip(pos1, pos2, other2)\r
585                 count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
586         else\r
587                 error("Only 90 degree increments are supported!")\r
588         end\r
589         return count, pos1, pos2\r
590 end\r
591 \r
592 \r
593 --- Rotates all oriented nodes in a region clockwise around the Y axis.\r
594 -- @param pos1\r
595 -- @param pos2\r
596 -- @param angle Angle in degrees (90 degree increments only).\r
597 -- @return The number of nodes oriented.\r
598 function worldedit.orient(pos1, pos2, angle)\r
599         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
600         local registered_nodes = minetest.registered_nodes\r
601 \r
602         local wallmounted = {\r
603                 [90]  = {0, 1, 5, 4, 2, 3, 0, 0},\r
604                 [180] = {0, 1, 3, 2, 5, 4, 0, 0},\r
605                 [270] = {0, 1, 4, 5, 3, 2, 0, 0}\r
606         }\r
607         local facedir = {\r
608                 [90]  = { 1,  2,  3,  0, 13, 14, 15, 12, 17, 18, 19, 16,\r
609                                   9, 10, 11,  8,  5,  6,  7,  4, 23, 20, 21, 22},\r
610                 [180] = { 2,  3,  0,  1, 10, 11,  8,  9,  6,  7,  4,  5,\r
611                                  18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},\r
612                 [270] = { 3,  0,  1,  2, 19, 16, 17, 18, 15, 12, 13, 14,\r
613                                   7,  4,  5,  6, 11,  8,  9, 10, 21, 22, 23, 20}\r
614         }\r
615 \r
616         angle = angle % 360\r
617         if angle == 0 then\r
618                 return 0\r
619         end\r
620         if angle % 90 ~= 0 then\r
621                 error("Only 90 degree increments are supported!")\r
622         end\r
623         local wallmounted_substitution = wallmounted[angle]\r
624         local facedir_substitution = facedir[angle]\r
625 \r
626         worldedit.keep_loaded(pos1, pos2)\r
627 \r
628         local count = 0\r
629         local get_node, swap_node = minetest.get_node, minetest.swap_node\r
630         local pos = {x=pos1.x, y=0, z=0}\r
631         while pos.x <= pos2.x do\r
632                 pos.y = pos1.y\r
633                 while pos.y <= pos2.y do\r
634                         pos.z = pos1.z\r
635                         while pos.z <= pos2.z do\r
636                                 local node = get_node(pos)\r
637                                 local def = registered_nodes[node.name]\r
638                                 if def then\r
639                                         local paramtype2 = def.paramtype2\r
640                                         if paramtype2 == "wallmounted" or\r
641                                                         paramtype2 == "colorwallmounted" then\r
642                                                 local orient = node.param2 % 8\r
643                                                 node.param2 = node.param2 - orient +\r
644                                                                 wallmounted_substitution[orient + 1]\r
645                                                 swap_node(pos, node)\r
646                                                 count = count + 1\r
647                                         elseif paramtype2 == "facedir" or\r
648                                                         paramtype2 == "colorfacedir" then\r
649                                                 local orient = node.param2 % 32\r
650                                                 node.param2 = node.param2 - orient +\r
651                                                                 facedir_substitution[orient + 1]\r
652                                                 swap_node(pos, node)\r
653                                                 count = count + 1\r
654                                         end\r
655                                 end\r
656                                 pos.z = pos.z + 1\r
657                         end\r
658                         pos.y = pos.y + 1\r
659                 end\r
660                 pos.x = pos.x + 1\r
661         end\r
662         return count\r
663 end\r
664 \r
665 \r
666 --- Attempts to fix the lighting in a region.\r
667 -- @return The number of nodes updated.\r
668 function worldedit.fixlight(pos1, pos2)\r
669         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
670 \r
671         local vmanip = minetest.get_voxel_manip(pos1, pos2)\r
672         vmanip:write_to_map()\r
673         vmanip:update_map() -- this updates the lighting\r
674 \r
675         return worldedit.volume(pos1, pos2)\r
676 end\r
677 \r
678 \r
679 --- Clears all objects in a region.\r
680 -- @return The number of objects cleared.\r
681 function worldedit.clear_objects(pos1, pos2)\r
682         pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
683 \r
684         worldedit.keep_loaded(pos1, pos2)\r
685 \r
686         -- Offset positions to include full nodes (positions are in the center of nodes)\r
687         local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5\r
688         local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5\r
689 \r
690         -- Center of region\r
691         local center = {\r
692                 x = pos1x + ((pos2x - pos1x) / 2),\r
693                 y = pos1y + ((pos2y - pos1y) / 2),\r
694                 z = pos1z + ((pos2z - pos1z) / 2)\r
695         }\r
696         -- Bounding sphere radius\r
697         local radius = math.sqrt(\r
698                         (center.x - pos1x) ^ 2 +\r
699                         (center.y - pos1y) ^ 2 +\r
700                         (center.z - pos1z) ^ 2)\r
701         local count = 0\r
702         for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do\r
703                 local entity = obj:get_luaentity()\r
704                 -- Avoid players and WorldEdit entities\r
705                 if not obj:is_player() and (not entity or\r
706                                 not entity.name:find("^worldedit:")) then\r
707                         local pos = obj:get_pos()\r
708                         if pos.x >= pos1x and pos.x <= pos2x and\r
709                                         pos.y >= pos1y and pos.y <= pos2y and\r
710                                         pos.z >= pos1z and pos.z <= pos2z then\r
711                                 -- Inside region\r
712                                 obj:remove()\r
713                                 count = count + 1\r
714                         end\r
715                 end\r
716         end\r
717         return count\r
718 end\r
719 \r