]> git.lizzy.rs Git - worldedit.git/blob - worldedit/manipulations.lua
Merge pull request #51 from cyisfor/master
[worldedit.git] / worldedit / manipulations.lua
1 worldedit = worldedit or {}\r
2 local minetest = minetest --local copy of global\r
3 \r
4 --modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions\r
5 worldedit.sort_pos = function(pos1, pos2)\r
6         pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}\r
7         pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
8         if pos1.x > pos2.x then\r
9                 pos2.x, pos1.x = pos1.x, pos2.x\r
10         end\r
11         if pos1.y > pos2.y then\r
12                 pos2.y, pos1.y = pos1.y, pos2.y\r
13         end\r
14         if pos1.z > pos2.z then\r
15                 pos2.z, pos1.z = pos1.z, pos2.z\r
16         end\r
17         return pos1, pos2\r
18 end\r
19 \r
20 --determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume\r
21 worldedit.volume = function(pos1, pos2)\r
22         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
23         return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)\r
24 end\r
25 \r
26 --sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled\r
27 worldedit.set = function(pos1, pos2, nodenames)\r
28     local oneNode\r
29     if type(nodenames) == 'string' then\r
30         oneNode = true\r
31     else\r
32         oneNode = false\r
33     end\r
34 \r
35         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
36 \r
37         --set up voxel manipulator\r
38         local manip = minetest.get_voxel_manip()\r
39         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
40         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
41 \r
42         --fill emerged area with ignore\r
43         local nodes = {}\r
44         local ignore = minetest.get_content_id("ignore")\r
45         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
46                 nodes[i] = ignore\r
47         end\r
48 \r
49         --fill selected area with node\r
50         local node_ids = {}\r
51     for i,v in ipairs(nodenames) do\r
52         node_ids[i] = minetest.get_content_id(nodenames[i])\r
53     end\r
54         if oneNode then --only one type of node\r
55                 local id = node_ids\r
56                 for i in area:iterp(pos1, pos2) do nodes[i] = id end --fill area with node\r
57         else --several types of nodes specified\r
58                 local id_count, rand = #node_ids, math.random\r
59                 for i in area:iterp(pos1, pos2) do nodes[i] = node_ids[rand(id_count)] end --fill randomly with all types of specified nodes\r
60         end\r
61 \r
62         --update map nodes\r
63         manip:set_data(nodes)\r
64         manip:write_to_map()\r
65         manip:update_map()\r
66 \r
67         return worldedit.volume(pos1, pos2)\r
68 end\r
69 \r
70 --replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
71 worldedit.replace = function(pos1, pos2, searchnode, replacenode)\r
72         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
73 \r
74         --set up voxel manipulator\r
75         local manip = minetest.get_voxel_manip()\r
76         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
77         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
78 \r
79         local nodes = manip:get_data()\r
80         local searchnode_id = minetest.get_content_id(searchnode)\r
81         local replacenode_id = minetest.get_content_id(replacenode)\r
82         local count = 0\r
83         for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode\r
84                 if nodes[i] == searchnode_id then\r
85                         nodes[i] = replacenode_id\r
86                         count = count + 1\r
87                 end\r
88         end\r
89 \r
90         --update map nodes\r
91         manip:set_data(nodes)\r
92         manip:write_to_map()\r
93         manip:update_map()\r
94 \r
95         return count\r
96 end\r
97 \r
98 --replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
99 worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode)\r
100         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
101 \r
102         --set up voxel manipulator\r
103         local manip = minetest.get_voxel_manip()\r
104         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
105         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
106 \r
107         local nodes = manip:get_data()\r
108         local searchnode_id = minetest.get_content_id(searchnode)\r
109         local replacenode_id = minetest.get_content_id(replacenode)\r
110         local count = 0\r
111         for i in area:iterp(pos1, pos2) do --replace anything that is not searchnode with replacenode\r
112                 if nodes[i] ~= searchnode_id then\r
113                         nodes[i] = replacenode_id\r
114                         count = count + 1\r
115                 end\r
116         end\r
117 \r
118         --update map nodes\r
119         manip:set_data(nodes)\r
120         manip:write_to_map()\r
121         manip:update_map()\r
122 \r
123         return count\r
124 end\r
125 \r
126 --copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied\r
127 worldedit.copy = function(pos1, pos2, axis, amount) --wip: replace the old version below\r
128         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
129 \r
130         if amount == 0 then\r
131                 return\r
132         end\r
133 \r
134         local other1, other2\r
135         if axis == "x" then\r
136                 other1, other2 = "y", "z"\r
137         elseif axis == "y" then\r
138                 other1, other2 = "x", "z"\r
139         else --axis == "z"\r
140                 other1, other2 = "x", "y"\r
141         end\r
142 \r
143         --make area stay loaded\r
144         local manip = minetest.get_voxel_manip()\r
145         manip:read_from_map(pos1, pos2)\r
146 \r
147         --prepare slice along axis\r
148         local extent = {\r
149                 [axis] = 1,\r
150                 [other1]=pos2[other1] - pos1[other1] + 1,\r
151                 [other2]=pos2[other2] - pos1[other2] + 1,\r
152         }\r
153         local nodes = {}\r
154         local schematic = {size=extent, data=nodes}\r
155 \r
156         local currentpos = {x=pos1.x, y=pos1.y, z=pos1.z}\r
157         local stride = {x=1, y=extent.x, z=extent.x * extent.y}\r
158         local get_node = minetest.get_node\r
159         for index1 = 1, extent[axis] do --go through each slice\r
160                 --copy slice into schematic\r
161                 local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed\r
162                 for index2 = 1, extent[other1] do\r
163                         local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1]\r
164                         for index3 = 1, extent[other2] do\r
165                                 local i = newindex2 + (index3 + offset[other2]) * stride[other2]\r
166                                 local node = get_node(pos)\r
167                                 node.param1 = 255 --node will always appear\r
168                                 nodes[i] = node\r
169                         end\r
170                 end\r
171 \r
172                 --copy schematic to target\r
173                 currentpos[axis] = currentpos[axis] + amount\r
174                 place_schematic(currentpos, schematic)\r
175 \r
176                 --wip: copy meta\r
177 \r
178                 currentpos[axis] = currentpos[axis] + 1\r
179         end\r
180         return worldedit.volume(pos1, pos2)\r
181 end\r
182 \r
183 worldedit.copy2 = function(pos1, pos2, direction, volume)\r
184         -- the overlap shouldn't matter as long as we\r
185         -- 1) start at the furthest separated corner\r
186         -- 2) complete an edge before moving inward, either edge works\r
187         -- 3) complete a face before moving inward, similarly\r
188         --\r
189         -- to do this I\r
190         -- 1) find the furthest destination in the direction, of each axis\r
191         -- 2) call those the furthest separated corner\r
192         -- 3) make sure to iterate inward from there\r
193         -- 4) nested loop to make sure complete edge, complete face, then complete cube.\r
194 \r
195         local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
196         local somemeta = get_meta(pos1) -- hax lol\r
197         local to_table = somemeta.to_table\r
198         local from_table = somemeta.from_table\r
199         somemeta = nil\r
200 \r
201         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
202         local manip = minetest.get_voxel_manip()\r
203         manip:read_from_map(pos1, pos2)\r
204 \r
205         local sx, sy, sz -- direction sign\r
206         local ix, iy, iz -- initial destination\r
207         local ex, ey, ez -- final destination\r
208         local originalx, originaly, originalz -- source\r
209         -- vim -> :'<,'>s/\<\([ioes]\?\)x\>/\1y/g\r
210         if direction.x > 0 then\r
211                 originalx = pos2.x\r
212                 ix = originalx + direction.x\r
213                 ex = pos1.x + direction.x\r
214                 sx = -1\r
215         elseif direction.x < 0 then\r
216                 originalx = pos1.x\r
217                 ix = originalx + direction.x\r
218                 ex = pos2.x + direction.x\r
219                 sx = 1\r
220         else\r
221                 originalx = pos1.x\r
222                 ix = originalx -- whatever\r
223                 ex = pos2.x\r
224                 sx = 1\r
225         end\r
226 \r
227         if direction.y > 0 then\r
228                 originaly = pos2.y\r
229                 iy = originaly + direction.y\r
230                 ey = pos1.y + direction.y\r
231                 sy = -1\r
232         elseif direction.y < 0 then\r
233                 originaly = pos1.y\r
234                 iy = originaly + direction.y\r
235                 ey = pos2.y + direction.y\r
236                 sy = 1\r
237         else\r
238                 originaly = pos1.y\r
239                 iy = originaly -- whatever\r
240                 ey = pos2.y\r
241                 sy = 1\r
242         end\r
243 \r
244         if direction.z > 0 then\r
245                 originalz = pos2.z\r
246                 iz = originalz + direction.z\r
247                 ez = pos1.z + direction.z\r
248                 sz = -1\r
249         elseif direction.z < 0 then\r
250                 originalz = pos1.z\r
251                 iz = originalz + direction.z\r
252                 ez = pos2.z + direction.z\r
253                 sz = 1\r
254         else\r
255                 originalz = pos1.z\r
256                 iz = originalz -- whatever\r
257                 ez = pos2.z\r
258                 sz = 1\r
259         end\r
260         -- print('copy',originalx,ix,ex,sx,originaly,iy,ey,sy,originalz,iz,ez,sz)\r
261 \r
262         local ox,oy,oz\r
263 \r
264         ox = originalx\r
265         for x = ix, ex, sx do\r
266                 oy = originaly\r
267                 for y = iy, ey, sy do\r
268                         oz = originalz\r
269                         for z = iz, ez, sz do\r
270                                 -- reusing pos1/pos2 as source/dest here\r
271                                 pos1.x, pos1.y, pos1.z = ox, oy, oz\r
272                                 pos2.x, pos2.y, pos2.z = x, y, z\r
273                                 local node = get_node(pos1)\r
274                                 local meta = to_table(get_meta(pos1)) --get meta of current node\r
275                                 add_node(pos2,node)\r
276                                 from_table(get_meta(pos2),meta)\r
277                                 oz = oz + sz\r
278                         end\r
279                         oy = oy + sy\r
280                 end\r
281                 ox = ox + sx\r
282         end\r
283 end\r
284 \r
285 --duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`, returning the number of nodes stacked\r
286 worldedit.stack2 = function(pos1, pos2, direction, amount, finished)\r
287         local i = 0\r
288         local translated = {x=0,y=0,z=0}\r
289         local function nextone()\r
290                 if i <= amount then\r
291                         i = i + 1\r
292                         translated.x = translated.x + direction.x\r
293                         translated.y = translated.y + direction.y\r
294                         translated.z = translated.z + direction.z\r
295                         worldedit.copy2(pos1, pos2, translated, volume)\r
296                         minetest.after(0, nextone)\r
297                 else\r
298                         if finished then\r
299                                 finished()\r
300                         end\r
301                 end\r
302         end\r
303         nextone()\r
304         return worldedit.volume(pos1, pos2) * amount\r
305 end\r
306 \r
307 --copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied\r
308 worldedit.copy = function(pos1, pos2, axis, amount)\r
309         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
310 \r
311         --make area stay loaded\r
312         local manip = minetest.get_voxel_manip()\r
313         manip:read_from_map(pos1, pos2)\r
314 \r
315         local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
316         if amount < 0 then\r
317                 local pos = {x=pos1.x, y=0, z=0}\r
318                 while pos.x <= pos2.x do\r
319                         pos.y = pos1.y\r
320                         while pos.y <= pos2.y do\r
321                                 pos.z = pos1.z\r
322                                 while pos.z <= pos2.z do\r
323                                         local node = get_node(pos) --obtain current node\r
324                                         local meta = get_meta(pos):to_table() --get meta of current node\r
325                                         local value = pos[axis] --store current position\r
326                                         pos[axis] = value + amount --move along axis\r
327                                         add_node(pos, node) --copy node to new position\r
328                                         get_meta(pos):from_table(meta) --set metadata of new node\r
329                                         pos[axis] = value --restore old position\r
330                                         pos.z = pos.z + 1\r
331                                 end\r
332                                 pos.y = pos.y + 1\r
333                         end\r
334                         pos.x = pos.x + 1\r
335                 end\r
336         else\r
337                 local pos = {x=pos2.x, y=0, z=0}\r
338                 while pos.x >= pos1.x do\r
339                         pos.y = pos2.y\r
340                         while pos.y >= pos1.y do\r
341                                 pos.z = pos2.z\r
342                                 while pos.z >= pos1.z do\r
343                                         local node = get_node(pos) --obtain current node\r
344                                         local meta = get_meta(pos):to_table() --get meta of current node\r
345                                         local value = pos[axis] --store current position\r
346                                         pos[axis] = value + amount --move along axis\r
347                                         add_node(pos, node) --copy node to new position\r
348                                         get_meta(pos):from_table(meta) --set metadata of new node\r
349                                         pos[axis] = value --restore old position\r
350                                         pos.z = pos.z - 1\r
351                                 end\r
352                                 pos.y = pos.y - 1\r
353                         end\r
354                         pos.x = pos.x - 1\r
355                 end\r
356         end\r
357         return worldedit.volume(pos1, pos2)\r
358 end\r
359 \r
360 --moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes moved\r
361 worldedit.move = function(pos1, pos2, axis, amount)\r
362         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
363 \r
364         --make area stay loaded\r
365         local manip = minetest.get_voxel_manip()\r
366         manip:read_from_map(pos1, pos2)\r
367 \r
368         --wip: move slice by slice using schematic method in the move axis and transfer metadata in separate loop (and if the amount is greater than the length in the axis, copy whole thing at a time and erase original after, using schematic method)\r
369         local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node\r
370         if amount < 0 then\r
371                 local pos = {x=pos1.x, y=0, z=0}\r
372                 while pos.x <= pos2.x do\r
373                         pos.y = pos1.y\r
374                         while pos.y <= pos2.y do\r
375                                 pos.z = pos1.z\r
376                                 while pos.z <= pos2.z do\r
377                                         local node = get_node(pos) --obtain current node\r
378                                         local meta = get_meta(pos):to_table() --get metadata of current node\r
379                                         remove_node(pos)\r
380                                         local value = pos[axis] --store current position\r
381                                         pos[axis] = value + amount --move along axis\r
382                                         add_node(pos, node) --move node to new position\r
383                                         get_meta(pos):from_table(meta) --set metadata of new node\r
384                                         pos[axis] = value --restore old position\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         else\r
392                 local pos = {x=pos2.x, y=0, z=0}\r
393                 while pos.x >= pos1.x do\r
394                         pos.y = pos2.y\r
395                         while pos.y >= pos1.y do\r
396                                 pos.z = pos2.z\r
397                                 while pos.z >= pos1.z do\r
398                                         local node = get_node(pos) --obtain current node\r
399                                         local meta = get_meta(pos):to_table() --get metadata of current node\r
400                                         remove_node(pos)\r
401                                         local value = pos[axis] --store current position\r
402                                         pos[axis] = value + amount --move along axis\r
403                                         add_node(pos, node) --move node to new position\r
404                                         get_meta(pos):from_table(meta) --set metadata of new node\r
405                                         pos[axis] = value --restore old position\r
406                                         pos.z = pos.z - 1\r
407                                 end\r
408                                 pos.y = pos.y - 1\r
409                         end\r
410                         pos.x = pos.x - 1\r
411                 end\r
412         end\r
413         return worldedit.volume(pos1, pos2)\r
414 end\r
415 \r
416 --duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times, returning the number of nodes stacked\r
417 worldedit.stack = function(pos1, pos2, axis, count)\r
418         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
419         local length = pos2[axis] - pos1[axis] + 1\r
420         if count < 0 then\r
421                 count = -count\r
422                 length = -length\r
423     end\r
424     local amount = 0\r
425     local copy = worldedit.copy\r
426     local i = 1\r
427     function nextone() \r
428         if i <= count then\r
429             i = i + 1\r
430             amount = amount + length\r
431             copy(pos1, pos2, axis, amount)\r
432             minetest.after(0,nextone)\r
433         end\r
434     end\r
435     nextone()\r
436         return worldedit.volume(pos1, pos2) * count\r
437 end\r
438 \r
439 --stretches the region defined by positions `pos1` and `pos2` by an factor of positive integers `stretchx`, `stretchy`. and `stretchz` along the X, Y, and Z axes, respectively, with `pos1` as the origin, returning the number of nodes scaled, the new scaled position 1, and the new scaled position 2\r
440 worldedit.stretch = function(pos1, pos2, stretchx, stretchy, stretchz) --wip: test this\r
441         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
442 \r
443         --prepare schematic of large node\r
444         local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic\r
445         local placeholder_node = {name="", param1=255, param2=0}\r
446         local nodes = {}\r
447         for i = 1, stretchx * stretchy * stretchz do\r
448                 nodes[i] = placeholder_node\r
449         end\r
450         local schematic = {size={x=stretchx, y=stretchy, z=stretchz}, data=nodes}\r
451 \r
452         local sizex, sizey, sizez = stretchx - 1, stretchy - 1, stretchz - 1\r
453 \r
454         --make area stay loaded\r
455         local manip = minetest.get_voxel_manip()\r
456         local new_pos2 = {\r
457                 x=pos1.x + (pos2.x - pos1.x) * stretchx + sizex,\r
458                 y=pos1.y + (pos2.y - pos1.y) * stretchy + sizey,\r
459                 z=pos1.z + (pos2.z - pos1.z) * stretchz + sizez,\r
460         }\r
461         manip:read_from_map(pos1, new_pos2)\r
462 \r
463         local pos = {x=pos2.x, y=0, z=0}\r
464         local bigpos = {x=0, y=0, z=0}\r
465         while pos.x >= pos1.x do\r
466                 pos.y = pos2.y\r
467                 while pos.y >= pos1.y do\r
468                         pos.z = pos2.z\r
469                         while pos.z >= pos1.z do\r
470                                 local node = get_node(pos) --obtain current node\r
471                                 local meta = get_meta(pos):to_table() --get meta of current node\r
472 \r
473                                 --calculate far corner of the big node\r
474                                 local posx = pos1.x + (pos.x - pos1.x) * stretchx\r
475                                 local posy = pos1.y + (pos.y - pos1.y) * stretchy\r
476                                 local posz = pos1.z + (pos.z - pos1.z) * stretchz\r
477 \r
478                                 --create large node\r
479                                 placeholder_node.name = node.name\r
480                                 placeholder_node.param2 = node.param2\r
481                                 bigpos.x, bigpos.y, bigpos.z = posx, posy, posz\r
482                                 place_schematic(bigpos, schematic)\r
483 \r
484                                 --fill in large node meta\r
485                                 if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then --node has meta fields\r
486                                         for x = 0, sizex do\r
487                                                 for y = 0, sizey do\r
488                                                         for z = 0, sizez do\r
489                                                                 bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z\r
490                                                                 get_meta(bigpos):from_table(meta) --set metadata of new node\r
491                                                         end\r
492                                                 end\r
493                                         end\r
494                                 end\r
495                                 pos.z = pos.z - 1\r
496                         end\r
497                         pos.y = pos.y - 1\r
498                 end\r
499                 pos.x = pos.x - 1\r
500         end\r
501         return worldedit.volume(pos1, pos2) * stretchx * stretchy * stretchz, pos1, new_pos2\r
502 end\r
503 \r
504 --transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes, returning the number of nodes transposed, the new transposed position 1, and the new transposed position 2\r
505 worldedit.transpose = function(pos1, pos2, axis1, axis2)\r
506         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
507 \r
508         local compare\r
509         local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]\r
510 \r
511         if extent1 > extent2 then\r
512                 compare = function(extent1, extent2)\r
513                         return extent1 > extent2\r
514                 end\r
515         else\r
516                 compare = function(extent1, extent2)\r
517                         return extent1 < extent2\r
518                 end\r
519         end\r
520 \r
521         --calculate the new position 2 after transposition\r
522         local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
523         new_pos2[axis1] = pos1[axis1] + extent2\r
524         new_pos2[axis2] = pos1[axis2] + extent1\r
525 \r
526         --make area stay loaded\r
527         local manip = minetest.get_voxel_manip()\r
528         local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z}\r
529         if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end\r
530         if upperbound[axis2] < new_pos2[axis2] then upperbound[axis2] = new_pos2[axis2] end\r
531         manip:read_from_map(pos1, upperbound)\r
532 \r
533         local pos = {x=pos1.x, y=0, z=0}\r
534         local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
535         while pos.x <= pos2.x do\r
536                 pos.y = pos1.y\r
537                 while pos.y <= pos2.y do\r
538                         pos.z = pos1.z\r
539                         while pos.z <= pos2.z do\r
540                                 local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]\r
541                                 if compare(extent1, extent2) then --transpose only if below the diagonal\r
542                                         local node1 = get_node(pos)\r
543                                         local meta1 = get_meta(pos):to_table()\r
544                                         local value1, value2 = pos[axis1], pos[axis2] --save position values\r
545                                         pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents\r
546                                         local node2 = get_node(pos)\r
547                                         local meta2 = get_meta(pos):to_table()\r
548                                         add_node(pos, node1)\r
549                                         get_meta(pos):from_table(meta1)\r
550                                         pos[axis1], pos[axis2] = value1, value2 --restore position values\r
551                                         add_node(pos, node2)\r
552                                         get_meta(pos):from_table(meta2)\r
553                                 end\r
554                                 pos.z = pos.z + 1\r
555                         end\r
556                         pos.y = pos.y + 1\r
557                 end\r
558                 pos.x = pos.x + 1\r
559         end\r
560         return worldedit.volume(pos1, pos2), pos1, new_pos2\r
561 end\r
562 \r
563 --flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z"), returning the number of nodes flipped\r
564 worldedit.flip = function(pos1, pos2, axis)\r
565         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
566 \r
567         --make area stay loaded\r
568         local manip = minetest.get_voxel_manip()\r
569         manip:read_from_map(pos1, pos2)\r
570 \r
571         --wip: flip the region slice by slice along the flip axis using schematic method\r
572         local pos = {x=pos1.x, y=0, z=0}\r
573         local start = pos1[axis] + pos2[axis]\r
574         pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)\r
575         local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
576         while pos.x <= pos2.x do\r
577                 pos.y = pos1.y\r
578                 while pos.y <= pos2.y do\r
579                         pos.z = pos1.z\r
580                         while pos.z <= pos2.z do\r
581                                 local node1 = get_node(pos)\r
582                                 local meta1 = get_meta(pos):to_table()\r
583                                 local value = pos[axis]\r
584                                 pos[axis] = start - value\r
585                                 local node2 = get_node(pos)\r
586                                 local meta2 = get_meta(pos):to_table()\r
587                                 add_node(pos, node1)\r
588                                 get_meta(pos):from_table(meta1)\r
589                                 pos[axis] = value\r
590                                 add_node(pos, node2)\r
591                                 get_meta(pos):from_table(meta2)\r
592                                 pos.z = pos.z + 1\r
593                         end\r
594                         pos.y = pos.y + 1\r
595                 end\r
596                 pos.x = pos.x + 1\r
597         end\r
598         return worldedit.volume(pos1, pos2)\r
599 end\r
600 \r
601 --rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around axis `axis` (90 degree increment), returning the number of nodes rotated\r
602 worldedit.rotate = function(pos1, pos2, axis, angle)\r
603         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
604 \r
605         local axis1, axis2\r
606         if axis == "x" then\r
607                 axis1, axis2 = "z", "y"\r
608         elseif axis == "y" then\r
609                 axis1, axis2 = "x", "z"\r
610         else --axis == "z"\r
611                 axis1, axis2 = "y", "x"\r
612         end\r
613         angle = angle % 360\r
614 \r
615         local count\r
616         if angle == 90 then\r
617                 worldedit.flip(pos1, pos2, axis1)\r
618                 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\r
619         elseif angle == 180 then\r
620                 worldedit.flip(pos1, pos2, axis1)\r
621                 count = worldedit.flip(pos1, pos2, axis2)\r
622         elseif angle == 270 then\r
623                 worldedit.flip(pos1, pos2, axis2)\r
624                 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\r
625         end\r
626         return count, pos1, pos2\r
627 end\r
628 \r
629 --rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis, returning the number of nodes oriented\r
630 worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotation along arbitrary axis\r
631         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
632         local registered_nodes = minetest.registered_nodes\r
633 \r
634         local wallmounted = {\r
635                 [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},\r
636                 [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},\r
637                 [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}\r
638         }\r
639         local facedir = {\r
640                 [90]={[0]=1, [1]=2, [2]=3, [3]=0},\r
641                 [180]={[0]=2, [1]=3, [2]=0, [3]=1},\r
642                 [270]={[0]=3, [1]=0, [2]=1, [3]=2}\r
643         }\r
644 \r
645         angle = angle % 360\r
646         if angle == 0 then\r
647                 return 0\r
648         end\r
649         local wallmounted_substitution = wallmounted[angle]\r
650         local facedir_substitution = facedir[angle]\r
651 \r
652         --make area stay loaded\r
653         local manip = minetest.get_voxel_manip()\r
654         manip:read_from_map(pos1, pos2)\r
655 \r
656         local count = 0\r
657         local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
658         local pos = {x=pos1.x, y=0, z=0}\r
659         while pos.x <= pos2.x do\r
660                 pos.y = pos1.y\r
661                 while pos.y <= pos2.y do\r
662                         pos.z = pos1.z\r
663                         while pos.z <= pos2.z do\r
664                                 local node = get_node(pos)\r
665                                 local def = registered_nodes[node.name]\r
666                                 if def then\r
667                                         if def.paramtype2 == "wallmounted" then\r
668                                                 node.param2 = wallmounted_substitution[node.param2]\r
669                                                 local meta = get_meta(pos):to_table()\r
670                                                 add_node(pos, node)\r
671                                                 get_meta(pos):from_table(meta)\r
672                                                 count = count + 1\r
673                                         elseif def.paramtype2 == "facedir" then\r
674                                                 node.param2 = facedir_substitution[node.param2]\r
675                                                 local meta = get_meta(pos):to_table()\r
676                                                 add_node(pos, node)\r
677                                                 get_meta(pos):from_table(meta)\r
678                                                 count = count + 1\r
679                                         end\r
680                                 end\r
681                                 pos.z = pos.z + 1\r
682                         end\r
683                         pos.y = pos.y + 1\r
684                 end\r
685                 pos.x = pos.x + 1\r
686         end\r
687         return count\r
688 end\r
689 \r
690 --fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated\r
691 worldedit.fixlight = function(pos1, pos2)\r
692         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
693 \r
694         --make area stay loaded\r
695         local manip = minetest.get_voxel_manip()\r
696         manip:read_from_map(pos1, pos2)\r
697 \r
698         local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")\r
699         local dig_node = minetest.dig_node\r
700         for _, pos in ipairs(nodes) do\r
701                 dig_node(pos)\r
702         end\r
703         return #nodes\r
704 end\r
705 \r
706 --clears all objects in a region defined by the positions `pos1` and `pos2`, returning the number of objects cleared\r
707 worldedit.clearobjects = function(pos1, pos2)\r
708         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
709 \r
710         --make area stay loaded\r
711         local manip = minetest.get_voxel_manip()\r
712         manip:read_from_map(pos1, pos2)\r
713 \r
714         local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z\r
715         local pos2x, pos2y, pos2z = pos2.x + 1, pos2.y + 1, pos2.z + 1\r
716         local center = {x=(pos1x + pos2x) / 2, y=(pos1y + pos2y) / 2, z=(pos1z + pos2z) / 2} --center of region\r
717         local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius\r
718         local count = 0\r
719         for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere\r
720                 local entity = obj:get_luaentity()\r
721                 if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities\r
722                         local pos = obj:getpos()\r
723                         if pos.x >= pos1x and pos.x <= pos2x\r
724                         and pos.y >= pos1y and pos.y <= pos2y\r
725                         and pos.z >= pos1z and pos.z <= pos2z then --inside region\r
726                                 obj:remove()\r
727                                 count = count + 1\r
728                         end\r
729                 end\r
730         end\r
731         return count\r
732 end\r