1 worldedit = worldedit or {}
\r
2 local minetest = minetest --local copy of global
\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
11 if pos1.y > pos2.y then
\r
12 pos2.y, pos1.y = pos1.y, pos2.y
\r
14 if pos1.z > pos2.z then
\r
15 pos2.z, pos1.z = pos1.z, pos2.z
\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
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, nodename)
\r
28 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
30 --set up voxel manipulator
\r
31 local manip = minetest.get_voxel_manip()
\r
32 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
33 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
35 --fill emerged area with ignore
\r
37 local ignore = minetest.get_content_id("ignore")
\r
38 for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do
\r
42 --fill selected area with node
\r
43 local node_id = minetest.get_content_id(nodename)
\r
44 for i in area:iterp(pos1, pos2) do
\r
49 manip:set_data(nodes)
\r
50 manip:write_to_map()
\r
53 return worldedit.volume(pos1, pos2)
\r
56 --replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
\r
57 worldedit.replace = function(pos1, pos2, searchnode, replacenode)
\r
58 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
60 --set up voxel manipulator
\r
61 local manip = minetest.get_voxel_manip()
\r
62 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
63 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
65 local nodes = manip:get_data()
\r
66 local searchnode_id = minetest.get_content_id(searchnode)
\r
67 local replacenode_id = minetest.get_content_id(replacenode)
\r
69 for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode
\r
70 if nodes[i] == searchnode_id then
\r
71 nodes[i] = replacenode_id
\r
77 manip:set_data(nodes)
\r
78 manip:write_to_map()
\r
84 --replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
\r
85 worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode)
\r
86 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
88 --set up voxel manipulator
\r
89 local manip = minetest.get_voxel_manip()
\r
90 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
91 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
93 local nodes = manip:get_data()
\r
94 local searchnode_id = minetest.get_content_id(searchnode)
\r
95 local replacenode_id = minetest.get_content_id(replacenode)
\r
97 for i in area:iterp(pos1, pos2) do --replace anything that is not searchnode with replacenode
\r
98 if nodes[i] ~= searchnode_id then
\r
99 nodes[i] = replacenode_id
\r
105 manip:set_data(nodes)
\r
106 manip:write_to_map()
\r
112 worldedit.copy = function(pos1, pos2, axis, amount) --wip: replace the old version below
\r
113 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
115 if amount == 0 then
\r
119 local other1, other2
\r
120 if axis == "x" then
\r
121 other1, other2 = "y", "z"
\r
122 elseif axis == "y" then
\r
123 other1, other2 = "x", "z"
\r
125 other1, other2 = "x", "y"
\r
128 --make area stay loaded
\r
129 local manip = minetest.get_voxel_manip()
\r
130 manip:read_from_map(pos1, pos2)
\r
132 --prepare slice along axis
\r
135 [other1]=pos2[other1] - pos1[other1] + 1,
\r
136 [other2]=pos2[other2] - pos1[other2] + 1,
\r
139 local schematic = {size=extent, data=nodes}
\r
141 local currentpos = {x=pos1.x, y=pos1.y, z=pos1.z}
\r
142 local stride = {x=1, y=extent.x, z=extent.x * extent.y}
\r
143 local get_node = minetest.get_node
\r
144 for index1 = 1, extent[axis] do --go through each slice
\r
145 --copy slice into schematic
\r
146 local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed
\r
147 for index2 = 1, extent[other1] do
\r
148 local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1]
\r
149 for index3 = 1, extent[other2] do
\r
150 local i = newindex2 + (index3 + offset[other2]) * stride[other2]
\r
151 local node = get_node(pos)
\r
152 node.param1 = 255 --node will always appear
\r
157 --copy schematic to target
\r
158 currentpos[axis] = currentpos[axis] + amount
\r
159 place_schematic(currentpos, schematic)
\r
163 currentpos[axis] = currentpos[axis] + 1
\r
165 return worldedit.volume(pos1, pos2)
\r
168 --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
169 worldedit.copy = function(pos1, pos2, axis, amount)
\r
170 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
172 --make area stay loaded
\r
173 local manip = minetest.get_voxel_manip()
\r
174 manip:read_from_map(pos1, pos2)
\r
176 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
178 local pos = {x=pos1.x, y=0, z=0}
\r
179 while pos.x <= pos2.x do
\r
181 while pos.y <= pos2.y do
\r
183 while pos.z <= pos2.z do
\r
184 local node = get_node(pos) --obtain current node
\r
185 local meta = get_meta(pos):to_table() --get meta of current node
\r
186 local value = pos[axis] --store current position
\r
187 pos[axis] = value + amount --move along axis
\r
188 add_node(pos, node) --copy node to new position
\r
189 get_meta(pos):from_table(meta) --set metadata of new node
\r
190 pos[axis] = value --restore old position
\r
198 local pos = {x=pos2.x, y=0, z=0}
\r
199 while pos.x >= pos1.x do
\r
201 while pos.y >= pos1.y do
\r
203 while pos.z >= pos1.z do
\r
204 local node = get_node(pos) --obtain current node
\r
205 local meta = get_meta(pos):to_table() --get meta of current node
\r
206 local value = pos[axis] --store current position
\r
207 pos[axis] = value + amount --move along axis
\r
208 add_node(pos, node) --copy node to new position
\r
209 get_meta(pos):from_table(meta) --set metadata of new node
\r
210 pos[axis] = value --restore old position
\r
218 return worldedit.volume(pos1, pos2)
\r
221 --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
222 worldedit.move = function(pos1, pos2, axis, amount)
\r
223 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
225 --make area stay loaded
\r
226 local manip = minetest.get_voxel_manip()
\r
227 manip:read_from_map(pos1, pos2)
\r
229 --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
230 local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node
\r
232 local pos = {x=pos1.x, y=0, z=0}
\r
233 while pos.x <= pos2.x do
\r
235 while pos.y <= pos2.y do
\r
237 while pos.z <= pos2.z do
\r
238 local node = get_node(pos) --obtain current node
\r
239 local meta = get_meta(pos):to_table() --get metadata of current node
\r
241 local value = pos[axis] --store current position
\r
242 pos[axis] = value + amount --move along axis
\r
243 add_node(pos, node) --move node to new position
\r
244 get_meta(pos):from_table(meta) --set metadata of new node
\r
245 pos[axis] = value --restore old position
\r
253 local pos = {x=pos2.x, y=0, z=0}
\r
254 while pos.x >= pos1.x do
\r
256 while pos.y >= pos1.y do
\r
258 while pos.z >= pos1.z do
\r
259 local node = get_node(pos) --obtain current node
\r
260 local meta = get_meta(pos):to_table() --get metadata of current node
\r
262 local value = pos[axis] --store current position
\r
263 pos[axis] = value + amount --move along axis
\r
264 add_node(pos, node) --move node to new position
\r
265 get_meta(pos):from_table(meta) --set metadata of new node
\r
266 pos[axis] = value --restore old position
\r
274 return worldedit.volume(pos1, pos2)
\r
277 --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
278 worldedit.stack = function(pos1, pos2, axis, count)
\r
279 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
280 local length = pos2[axis] - pos1[axis] + 1
\r
286 local copy = worldedit.copy
\r
287 for i = 1, count do
\r
288 amount = amount + length
\r
289 copy(pos1, pos2, axis, amount)
\r
291 return worldedit.volume(pos1, pos2) * count
\r
294 --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
295 worldedit.stretch = function(pos1, pos2, stretchx, stretchy, stretchz) --wip: test this
\r
296 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
298 --prepare schematic of large node
\r
299 local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic
\r
300 local placeholder_node = {name="", param1=255, param2=0}
\r
302 for i = 1, stretchx * stretchy * stretchz do
\r
303 nodes[i] = placeholder_node
\r
305 local schematic = {size={x=stretchx, y=stretchy, z=stretchz}, data=nodes}
\r
307 local sizex, sizey, sizez = stretchx - 1, stretchy - 1, stretchz - 1
\r
309 --make area stay loaded
\r
310 local manip = minetest.get_voxel_manip()
\r
312 x=pos1.x + (pos2.x - pos1.x) * stretchx + sizex,
\r
313 y=pos1.y + (pos2.y - pos1.y) * stretchy + sizey,
\r
314 z=pos1.z + (pos2.z - pos1.z) * stretchz + sizez,
\r
316 manip:read_from_map(pos1, new_pos2)
\r
318 local pos = {x=pos2.x, y=0, z=0}
\r
319 local bigpos = {x=0, y=0, z=0}
\r
320 while pos.x >= pos1.x do
\r
322 while pos.y >= pos1.y do
\r
324 while pos.z >= pos1.z do
\r
325 local node = get_node(pos) --obtain current node
\r
326 local meta = get_meta(pos):to_table() --get meta of current node
\r
328 --calculate far corner of the big node
\r
329 local posx = pos1.x + (pos.x - pos1.x) * stretchx
\r
330 local posy = pos1.y + (pos.y - pos1.y) * stretchy
\r
331 local posz = pos1.z + (pos.z - pos1.z) * stretchz
\r
333 --create large node
\r
334 placeholder_node.name = node.name
\r
335 placeholder_node.param2 = node.param2
\r
336 bigpos.x, bigpos.y, bigpos.z = posx, posy, posz
\r
337 place_schematic(bigpos, schematic)
\r
339 --fill in large node meta
\r
340 if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then --node has meta fields
\r
341 for x = 0, sizex do
\r
342 for y = 0, sizey do
\r
343 for z = 0, sizez do
\r
344 bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z
\r
345 get_meta(bigpos):from_table(meta) --set metadata of new node
\r
356 return worldedit.volume(pos1, pos2) * stretchx * stretchy * stretchz, pos1, new_pos2
\r
359 --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
360 worldedit.transpose = function(pos1, pos2, axis1, axis2)
\r
361 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
364 local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
\r
366 if extent1 > extent2 then
\r
367 compare = function(extent1, extent2)
\r
368 return extent1 > extent2
\r
371 compare = function(extent1, extent2)
\r
372 return extent1 < extent2
\r
376 --calculate the new position 2 after transposition
\r
377 local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
378 new_pos2[axis1] = pos1[axis1] + extent2
\r
379 new_pos2[axis2] = pos1[axis2] + extent1
\r
381 --make area stay loaded
\r
382 local manip = minetest.get_voxel_manip()
\r
383 local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
384 if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end
\r
385 if upperbound[axis2] < new_pos2[axis2] then upperbound[axis2] = new_pos2[axis2] end
\r
386 manip:read_from_map(pos1, upperbound)
\r
388 local pos = {x=pos1.x, y=0, z=0}
\r
389 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
390 while pos.x <= pos2.x do
\r
392 while pos.y <= pos2.y do
\r
394 while pos.z <= pos2.z do
\r
395 local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
\r
396 if compare(extent1, extent2) then --transpose only if below the diagonal
\r
397 local node1 = get_node(pos)
\r
398 local meta1 = get_meta(pos):to_table()
\r
399 local value1, value2 = pos[axis1], pos[axis2] --save position values
\r
400 pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents
\r
401 local node2 = get_node(pos)
\r
402 local meta2 = get_meta(pos):to_table()
\r
403 add_node(pos, node1)
\r
404 get_meta(pos):from_table(meta1)
\r
405 pos[axis1], pos[axis2] = value1, value2 --restore position values
\r
406 add_node(pos, node2)
\r
407 get_meta(pos):from_table(meta2)
\r
415 return worldedit.volume(pos1, pos2), pos1, new_pos2
\r
418 --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
419 worldedit.flip = function(pos1, pos2, axis)
\r
420 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
422 --make area stay loaded
\r
423 local manip = minetest.get_voxel_manip()
\r
424 manip:read_from_map(pos1, pos2)
\r
426 --wip: flip the region slice by slice along the flip axis using schematic method
\r
427 local pos = {x=pos1.x, y=0, z=0}
\r
428 local start = pos1[axis] + pos2[axis]
\r
429 pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
\r
430 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
431 while pos.x <= pos2.x do
\r
433 while pos.y <= pos2.y do
\r
435 while pos.z <= pos2.z do
\r
436 local node1 = get_node(pos)
\r
437 local meta1 = get_meta(pos):to_table()
\r
438 local value = pos[axis]
\r
439 pos[axis] = start - value
\r
440 local node2 = get_node(pos)
\r
441 local meta2 = get_meta(pos):to_table()
\r
442 add_node(pos, node1)
\r
443 get_meta(pos):from_table(meta1)
\r
445 add_node(pos, node2)
\r
446 get_meta(pos):from_table(meta2)
\r
453 return worldedit.volume(pos1, pos2)
\r
456 --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
457 worldedit.rotate = function(pos1, pos2, axis, angle)
\r
458 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
461 if axis == "x" then
\r
462 axis1, axis2 = "z", "y"
\r
463 elseif axis == "y" then
\r
464 axis1, axis2 = "x", "z"
\r
466 axis1, axis2 = "y", "x"
\r
468 angle = angle % 360
\r
471 if angle == 90 then
\r
472 worldedit.flip(pos1, pos2, axis1)
\r
473 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
474 elseif angle == 180 then
\r
475 worldedit.flip(pos1, pos2, axis1)
\r
476 count = worldedit.flip(pos1, pos2, axis2)
\r
477 elseif angle == 270 then
\r
478 worldedit.flip(pos1, pos2, axis2)
\r
479 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
481 return count, pos1, pos2
\r
484 --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
485 worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotation along arbitrary axis
\r
486 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
487 local registered_nodes = minetest.registered_nodes
\r
489 local wallmounted = {
\r
490 [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},
\r
491 [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},
\r
492 [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}
\r
495 [90]={[0]=1, [1]=2, [2]=3, [3]=0},
\r
496 [180]={[0]=2, [1]=3, [2]=0, [3]=1},
\r
497 [270]={[0]=3, [1]=0, [2]=1, [3]=2}
\r
500 angle = angle % 360
\r
504 local wallmounted_substitution = wallmounted[angle]
\r
505 local facedir_substitution = facedir[angle]
\r
507 --make area stay loaded
\r
508 local manip = minetest.get_voxel_manip()
\r
509 manip:read_from_map(pos1, pos2)
\r
512 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
513 local pos = {x=pos1.x, y=0, z=0}
\r
514 while pos.x <= pos2.x do
\r
516 while pos.y <= pos2.y do
\r
518 while pos.z <= pos2.z do
\r
519 local node = get_node(pos)
\r
520 local def = registered_nodes[node.name]
\r
522 if def.paramtype2 == "wallmounted" then
\r
523 node.param2 = wallmounted_substitution[node.param2]
\r
524 local meta = get_meta(pos):to_table()
\r
525 add_node(pos, node)
\r
526 get_meta(pos):from_table(meta)
\r
528 elseif def.paramtype2 == "facedir" then
\r
529 node.param2 = facedir_substitution[node.param2]
\r
530 local meta = get_meta(pos):to_table()
\r
531 add_node(pos, node)
\r
532 get_meta(pos):from_table(meta)
\r
545 --fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated
\r
546 worldedit.fixlight = function(pos1, pos2)
\r
547 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
549 --make area stay loaded
\r
550 local manip = minetest.get_voxel_manip()
\r
551 manip:read_from_map(pos1, pos2)
\r
553 local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
\r
554 local dig_node = minetest.dig_node
\r
555 for _, pos in ipairs(nodes) do
\r
561 --clears all objects in a region defined by the positions `pos1` and `pos2`, returning the number of objects cleared
\r
562 worldedit.clearobjects = function(pos1, pos2)
\r
563 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
565 --make area stay loaded
\r
566 local manip = minetest.get_voxel_manip()
\r
567 manip:read_from_map(pos1, pos2)
\r
569 local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z
\r
570 local pos2x, pos2y, pos2z = pos2.x + 1, pos2.y + 1, pos2.z + 1
\r
571 local center = {x=(pos1x + pos2x) / 2, y=(pos1y + pos2y) / 2, z=(pos1z + pos2z) / 2} --center of region
\r
572 local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius
\r
574 for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere
\r
575 local entity = obj:get_luaentity()
\r
576 if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities
\r
577 local pos = obj:getpos()
\r
578 if pos.x >= pos1x and pos.x <= pos2x
\r
579 and pos.y >= pos1y and pos.y <= pos2y
\r
580 and pos.z >= pos1z and pos.z <= pos2z then --inside region
\r