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