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