Minetest-WorldEdit/worldedit/manipulations.lua

441 lines
15 KiB
Lua
Raw Normal View History

worldedit = worldedit or {}
local minetest = minetest --local copy of global
--wip: test the entire API again to make sure it works
--wip: remove env parameter where no longer needed in chat commands module
--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
worldedit.sort_pos = function(pos1, pos2)
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x
end
if pos1.y > pos2.y then
pos2.y, pos1.y = pos1.y, pos2.y
end
if pos1.z > pos2.z then
pos2.z, pos1.z = pos1.z, pos2.z
end
return pos1, pos2
end
--determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume
worldedit.volume = function(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)
end
--sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled
worldedit.set = function(pos1, pos2, nodename)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--set up voxel manipulator
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
--fill nodes table with node to be set
local nodes = {}
local node_id = minetest.get_content_id(nodename)
for i = 1, (pos2.x - pos1.x) * (pos2.y - pos1.y) * (pos2.z - pos1.z) do
nodes[i] = node_id
end
--update map nodes
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return worldedit.volume(pos1, pos2)
end
--replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
worldedit.replace = function(pos1, pos2, searchnode, replacenode)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local node = {name=replacenode}
local add_node = minetest.add_node
local nodes = minetest.find_nodes_in_area(pos1, pos2, searchnode)
for _, pos in ipairs(nodes) do
add_node(pos, node)
end
return #nodes
end
--replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode) --wip: use voxelmanip get_data for this
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local node = {name=replacenode}
local get_node, add_node = minetest.get_node, minetest.add_node
local count = 0
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local name = get_node(pos).name
if name ~= "ignore" and name ~= searchnode then
add_node(pos, node)
count = count + 1
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return count
end
--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
2013-04-28 19:08:09 +02:00
worldedit.copy = function(pos1, pos2, axis, amount, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
2013-04-28 19:08:09 +02:00
if env == nil then env = minetest.env end
--wip: copy slice by slice using schematic method in the copy 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)
if amount < 0 then
local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = env:get_node(pos) --obtain current node
local meta = env:get_meta(pos):to_table() --get meta of current node
local value = pos[axis] --store current position
pos[axis] = value + amount --move along axis
env:add_node(pos, node) --copy node to new position
env:get_meta(pos):from_table(meta) --set metadata of new node
pos[axis] = value --restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {x=pos2.x, y=0, z=0}
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = minetest.env:get_node(pos) --obtain current node
local meta = env:get_meta(pos):to_table() --get meta of current node
local value = pos[axis] --store current position
pos[axis] = value + amount --move along axis
minetest.env:add_node(pos, node) --copy node to new position
env:get_meta(pos):from_table(meta) --set metadata of new node
pos[axis] = value --restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
end
--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
2013-04-28 19:08:09 +02:00
worldedit.move = function(pos1, pos2, axis, amount, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
2013-04-28 19:08:09 +02:00
if env == nil then env = minetest.env end
--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)
if amount < 0 then
local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = env:get_node(pos) --obtain current node
local meta = env:get_meta(pos):to_table() --get metadata of current node
env:remove_node(pos)
local value = pos[axis] --store current position
pos[axis] = value + amount --move along axis
env:add_node(pos, node) --move node to new position
env:get_meta(pos):from_table(meta) --set metadata of new node
pos[axis] = value --restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {x=pos2.x, y=0, z=0}
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = env:get_node(pos) --obtain current node
local meta = env:get_meta(pos):to_table() --get metadata of current node
env:remove_node(pos)
local value = pos[axis] --store current position
pos[axis] = value + amount --move along axis
env:add_node(pos, node) --move node to new position
env:get_meta(pos):from_table(meta) --set metadata of new node
pos[axis] = value --restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
end
--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
2013-04-28 19:08:09 +02:00
worldedit.stack = function(pos1, pos2, axis, count, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local length = pos2[axis] - pos1[axis] + 1
if count < 0 then
count = -count
length = -length
end
local amount = 0
local copy = worldedit.copy
for i = 1, count do
amount = amount + length
2013-04-28 19:08:09 +02:00
copy(pos1, pos2, axis, amount, env)
end
return worldedit.volume(pos1, pos2)
end
--scales the region defined by positions `pos1` and `pos2` by an factor of positive integer `factor` with `pos1` as the origin, returning the number of nodes scaled, the new scaled position 1, and the new scaled position 2
worldedit.scale = function(pos1, pos2, factor)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--prepare schematic of large node
local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic
local placeholder_node = {name="", param1=0, param2=0}
local nodes = {}
for i = 1, size ^ 3 do
nodes[i] = placeholder_node
end
local schematic = {size={x=size, y=size, z=size}, data=nodes}
local pos = {x=pos2.x, y=0, z=0}
local bigpos = {x=0, y=0, z=0}
size = factor - 1
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = get_node(pos) --obtain current node
local meta = get_meta(pos):to_table() --get meta of current node
local value = pos[axis] --store current position
local posx, posy, posz = pos1.x + (pos.x - pos1.x) * factor, pos1.y + (pos.y - pos1.y) * factor, pos1.z + (pos.z - pos1.z) * factor
--create large node
placeholder_node[1], placeholder_node[3] = node.name, node.param2
bigpos.x, bigpos.y, bigpos.z = posx, posy, posz
place_schematic(bigpos, schematic)
for x = 0, size do --fill in large node meta
for y = 0, size do
for z = 0, size do
bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z
get_meta(bigpos):from_table(meta) --set metadata of new node
end
end
end
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
local newpos2 = {x=pos1.x + (pos2.x - pos1.x) * factor + size, y=pos1.y + (pos2.y - pos1.y) * factor + size, z=pos1.z + (pos2.z - pos1.z) * factor + size}
return worldedit.volume(pos1, pos2), pos1, newpos2
end
--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
2013-04-28 19:08:09 +02:00
worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local compare
local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
if extent1 > extent2 then
compare = function(extent1, extent2)
return extent1 > extent2
end
else
compare = function(extent1, extent2)
return extent1 < extent2
end
end
--calculate the new position 2 after transposition
local newpos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
newpos2[axis1] = pos1[axis1] + extent2
newpos2[axis2] = pos1[axis2] + extent1
local pos = {x=pos1.x, y=0, z=0}
local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
if compare(extent1, extent2) then --transpose only if below the diagonal
local node1 = get_node(pos)
local meta1 = get_meta(pos):to_table()
local value1, value2 = pos[axis1], pos[axis2] --save position values
pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents
local node2 = get_node(pos)
local meta2 = get_meta(pos):to_table()
add_node(pos, node1)
get_meta(pos):from_table(meta1)
pos[axis1], pos[axis2] = value1, value2 --restore position values
add_node(pos, node2)
get_meta(pos):from_table(meta2)
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2), pos1, newpos2
end
--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
2013-04-28 19:08:09 +02:00
worldedit.flip = function(pos1, pos2, axis, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--wip: flip the region slice by slice along the flip axis using schematic method
local pos = {x=pos1.x, y=0, z=0}
local start = pos1[axis] + pos2[axis]
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
2013-04-28 19:08:09 +02:00
if env == nil then env = minetest.env end
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node1 = env:get_node(pos)
local meta1 = env:get_meta(pos):to_table()
local value = pos[axis]
pos[axis] = start - value
local node2 = env:get_node(pos)
local meta2 = env:get_meta(pos):to_table()
env:add_node(pos, node1)
env:get_meta(pos):from_table(meta1)
pos[axis] = value
env:add_node(pos, node2)
env:get_meta(pos):from_table(meta2)
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2)
end
--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
2013-04-28 19:08:09 +02:00
worldedit.rotate = function(pos1, pos2, axis, angle, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local axis1, axis2
if axis == "x" then
axis1, axis2 = "z", "y"
elseif axis == "y" then
axis1, axis2 = "x", "z"
else --axis == "z"
axis1, axis2 = "y", "x"
end
angle = angle % 360
local count
if angle == 90 then
2013-04-28 19:08:09 +02:00
worldedit.flip(pos1, pos2, axis1, env)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)
elseif angle == 180 then
2013-04-28 19:08:09 +02:00
worldedit.flip(pos1, pos2, axis1, env)
count = worldedit.flip(pos1, pos2, axis2, env)
elseif angle == 270 then
2013-04-28 19:08:09 +02:00
worldedit.flip(pos1, pos2, axis2, env)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)
end
return count, pos1, pos2
end
--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
2013-04-28 19:08:09 +02:00
worldedit.orient = function(pos1, pos2, angle, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes
local wallmounted = {
[90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},
[180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},
[270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}
}
local facedir = {
[90]={[0]=1, [1]=2, [2]=3, [3]=0},
[180]={[0]=2, [1]=3, [2]=0, [3]=1},
[270]={[0]=3, [1]=0, [2]=1, [3]=2}
}
angle = angle % 360
if angle == 0 then
return 0
end
local wallmounted_substitution = wallmounted[angle]
local facedir_substitution = facedir[angle]
local count = 0
local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = get_node(pos)
local def = registered_nodes[node.name]
if def then
if def.paramtype2 == "wallmounted" then
node.param2 = wallmounted_substitution[node.param2]
local meta = get_meta(pos):to_table()
add_node(pos, node)
get_meta(pos):from_table(meta)
count = count + 1
elseif def.paramtype2 == "facedir" then
node.param2 = facedir_substitution[node.param2]
local meta = get_meta(pos):to_table()
add_node(pos, node)
get_meta(pos):from_table(meta)
count = count + 1
end
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return count
end
--fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated
2013-04-28 19:08:09 +02:00
worldedit.fixlight = function(pos1, pos2, env)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
local dig_node = minetest.dig_node
for _, pos in ipairs(nodes) do
dig_node(pos)
end
return #nodes
end