]> git.lizzy.rs Git - worldedit.git/blobdiff - worldedit/manipulations.lua
Correct spelling of Minetest
[worldedit.git] / worldedit / manipulations.lua
index b5bcd3c42af4dcadf3e4a8458432983f51d93cf6..ee5156137ab780fbe131de0cad6044ecd2fcbb32 100644 (file)
-worldedit = worldedit or {}\r
-\r
---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
-worldedit.sort_pos = function(pos1, pos2)\r
-       pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}\r
-       pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
-       if pos1.x > pos2.x then\r
-               pos2.x, pos1.x = pos1.x, pos2.x\r
-       end\r
-       if pos1.y > pos2.y then\r
-               pos2.y, pos1.y = pos1.y, pos2.y\r
-       end\r
-       if pos1.z > pos2.z then\r
-               pos2.z, pos1.z = pos1.z, pos2.z\r
+--- Generic node manipulations.\r
+-- @module worldedit.manipulations\r
+\r
+local mh = worldedit.manip_helpers\r
+\r
+\r
+--- Sets a region to `node_names`.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param node_names Node name or list of node names.\r
+-- @return The number of nodes set.\r
+function worldedit.set(pos1, pos2, node_names)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local data = mh.get_empty_data(area)\r
+\r
+       if type(node_names) == "string" then -- Only one type of node\r
+               local id = minetest.get_content_id(node_names)\r
+               -- Fill area with node\r
+               for i in area:iterp(pos1, pos2) do\r
+                       data[i] = id\r
+               end\r
+       else -- Several types of nodes specified\r
+               local node_ids = {}\r
+               for i, v in ipairs(node_names) do\r
+                       node_ids[i] = minetest.get_content_id(v)\r
+               end\r
+               -- Fill area randomly with nodes\r
+               local id_count, rand = #node_ids, math.random\r
+               for i in area:iterp(pos1, pos2) do\r
+                       data[i] = node_ids[rand(id_count)]\r
+               end\r
        end\r
-       return pos1, pos2\r
-end\r
 \r
---determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume\r
-worldedit.volume = function(pos1, pos2)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)\r
+       mh.finish(manip, data)\r
+\r
+       return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled\r
-worldedit.set = function(pos1, pos2, nodename, env)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
+--- Sets param2 of a region.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param param2 Value of param2 to set\r
+-- @return The number of nodes set.\r
+function worldedit.set_param2(pos1, pos2, param2)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       local node = {name=nodename}\r
-       local pos = {x=pos1.x, y=0, z=0}\r
-       while pos.x <= pos2.x do\r
-               pos.y = pos1.y\r
-               while pos.y <= pos2.y do\r
-                       pos.z = pos1.z\r
-                       while pos.z <= pos2.z do\r
-                               env:add_node(pos, node)\r
-                               pos.z = pos.z + 1\r
-                       end\r
-                       pos.y = pos.y + 1\r
-               end\r
-               pos.x = pos.x + 1\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local param2_data = manip:get_param2_data()\r
+\r
+       -- Set param2 for every node\r
+       for i in area:iterp(pos1, pos2) do\r
+               param2_data[i] = param2\r
        end\r
+\r
+       -- Update map\r
+       manip:set_param2_data(param2_data)\r
+       manip:write_to_map()\r
+       manip:update_map()\r
+\r
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
-worldedit.replace = function(pos1, pos2, searchnode, replacenode, env)\r
+--- Replaces all instances of `search_node` with `replace_node` in a region.\r
+-- When `inverse` is `true`, replaces all instances that are NOT `search_node`.\r
+-- @return The number of nodes replaced.\r
+function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
-       if minetest.registered_nodes[searchnode] == nil then\r
-               searchnode = "default:" .. searchnode\r
-       end\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local data = manip:get_data()\r
+\r
+       local search_id = minetest.get_content_id(search_node)\r
+       local replace_id = minetest.get_content_id(replace_node)\r
 \r
-       local pos = {x=pos1.x, y=0, z=0}\r
-       local node = {name=replacenode}\r
        local count = 0\r
-       while pos.x <= pos2.x do\r
-               pos.y = pos1.y\r
-               while pos.y <= pos2.y do\r
-                       pos.z = pos1.z\r
-                       while pos.z <= pos2.z do\r
-                               if env:get_node(pos).name == searchnode then\r
-                                       env:add_node(pos, node)\r
-                                       count = count + 1\r
-                               end\r
-                               pos.z = pos.z + 1\r
+\r
+       --- TODO: This could be shortened by checking `inverse` in the loop,\r
+       -- but that would have a speed penalty.  Is the penalty big enough\r
+       -- to matter?\r
+       if not inverse then\r
+               for i in area:iterp(pos1, pos2) do\r
+                       if data[i] == search_id then\r
+                               data[i] = replace_id\r
+                               count = count + 1\r
+                       end\r
+               end\r
+       else\r
+               for i in area:iterp(pos1, pos2) do\r
+                       if data[i] ~= search_id then\r
+                               data[i] = replace_id\r
+                               count = count + 1\r
                        end\r
-                       pos.y = pos.y + 1\r
                end\r
-               pos.x = pos.x + 1\r
        end\r
+\r
+       mh.finish(manip, data)\r
+\r
        return count\r
 end\r
 \r
---replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
-worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode, env)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
-\r
-       if minetest.registered_nodes[searchnode] == nil then\r
-               searchnode = "default:" .. searchnode\r
-       end\r
 \r
-       local pos = {x=pos1.x, y=0, z=0}\r
-       local node = {name=replacenode}\r
-       local count = 0\r
-       while pos.x <= pos2.x do\r
-               pos.y = pos1.y\r
-               while pos.y <= pos2.y do\r
-                       pos.z = pos1.z\r
-                       while pos.z <= pos2.z do\r
-                               local name = env:get_node(pos).name\r
-                               if name ~= "ignore" and name ~= searchnode then\r
-                                       env:add_node(pos, node)\r
-                                       count = count + 1\r
-                               end\r
-                               pos.z = pos.z + 1\r
+--- Duplicates a region `amount` times with offset vector `direction`.\r
+-- Stacking is spread across server steps, one copy per step.\r
+-- @return The number of nodes stacked.\r
+function worldedit.stack2(pos1, pos2, direction, amount, finished)\r
+       local i = 0\r
+       local translated = {x=0, y=0, z=0}\r
+       local function next_one()\r
+               if i < amount then\r
+                       i = i + 1\r
+                       translated.x = translated.x + direction.x\r
+                       translated.y = translated.y + direction.y\r
+                       translated.z = translated.z + direction.z\r
+                       worldedit.copy2(pos1, pos2, translated)\r
+                       minetest.after(0, next_one)\r
+               else\r
+                       if finished then\r
+                               finished()\r
                        end\r
-                       pos.y = pos.y + 1\r
                end\r
-               pos.x = pos.x + 1\r
        end\r
-       return count\r
+       next_one()\r
+       return worldedit.volume(pos1, pos2) * amount\r
 end\r
 \r
---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
-worldedit.copy = function(pos1, pos2, axis, amount, env)\r
+\r
+--- Copies a region along `axis` by `amount` nodes.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis ("x", "y", or "z")\r
+-- @param amount\r
+-- @return The number of nodes copied.\r
+function worldedit.copy(pos1, pos2, axis, amount)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
+       -- Copy things backwards when negative to avoid corruption.\r
+       -- FIXME: Lots of code duplication here.\r
        if amount < 0 then\r
-               local pos = {x=pos1.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos1.x\r
                while pos.x <= pos2.x do\r
                        pos.y = pos1.y\r
                        while pos.y <= pos2.y do\r
                                pos.z = pos1.z\r
                                while pos.z <= pos2.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get meta of current node\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --copy node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Copy node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z + 1\r
                                end\r
                                pos.y = pos.y + 1\r
@@ -131,19 +163,20 @@ worldedit.copy = function(pos1, pos2, axis, amount, env)
                        pos.x = pos.x + 1\r
                end\r
        else\r
-               local pos = {x=pos2.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos2.x\r
                while pos.x >= pos1.x do\r
                        pos.y = pos2.y\r
                        while pos.y >= pos1.y do\r
                                pos.z = pos2.z\r
                                while pos.z >= pos1.z do\r
-                                       local node = minetest.env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get meta of current node\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       minetest.env:add_node(pos, node) --copy node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Copy node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z - 1\r
                                end\r
                                pos.y = pos.y - 1\r
@@ -154,26 +187,70 @@ worldedit.copy = function(pos1, pos2, axis, amount, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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
-worldedit.move = function(pos1, pos2, axis, amount, env)\r
+--- Copies a region by offset vector `off`.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param off\r
+-- @return The number of nodes copied.\r
+function worldedit.copy2(pos1, pos2, off)\r
+       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
+       local pos = {}\r
+       pos.x = pos2.x\r
+       while pos.x >= pos1.x do\r
+               pos.y = pos2.y\r
+               while pos.y >= pos1.y do\r
+                       pos.z = pos2.z\r
+                       while pos.z >= pos1.z do\r
+                               local node = get_node(pos) -- Obtain current node\r
+                               local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                               local newpos = vector.add(pos, off) -- Calculate new position\r
+                               set_node(newpos, node) -- Copy node to new position\r
+                               get_meta(newpos):from_table(meta) -- Set metadata of new node\r
+                               pos.z = pos.z - 1\r
+                       end\r
+                       pos.y = pos.y - 1\r
+               end\r
+               pos.x = pos.x - 1\r
+       end\r
+       return worldedit.volume(pos1, pos2)\r
+end\r
+\r
+--- Moves a region along `axis` by `amount` nodes.\r
+-- @return The number of nodes moved.\r
+function worldedit.move(pos1, pos2, axis, amount)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       --- TODO: Move slice by slice using schematic method in the move axis\r
+       -- and transfer metadata in separate loop (and if the amount is\r
+       -- greater than the length in the axis, copy whole thing at a time and\r
+       -- erase original after, using schematic method).\r
+       local get_node, get_meta, set_node, remove_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node, minetest.remove_node\r
+       -- Copy things backwards when negative to avoid corruption.\r
+       --- FIXME: Lots of code duplication here.\r
        if amount < 0 then\r
-               local pos = {x=pos1.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos1.x\r
                while pos.x <= pos2.x do\r
                        pos.y = pos1.y\r
                        while pos.y <= pos2.y do\r
                                pos.z = pos1.z\r
                                while pos.z <= pos2.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get metadata of current node\r
-                                       env:remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --move node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get metadata of current node\r
+                                       remove_node(pos) -- Remove current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Move node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z + 1\r
                                end\r
                                pos.y = pos.y + 1\r
@@ -181,20 +258,21 @@ worldedit.move = function(pos1, pos2, axis, amount, env)
                        pos.x = pos.x + 1\r
                end\r
        else\r
-               local pos = {x=pos2.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos2.x\r
                while pos.x >= pos1.x do\r
                        pos.y = pos2.y\r
                        while pos.y >= pos1.y do\r
                                pos.z = pos2.z\r
                                while pos.z >= pos1.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get metadata of current node\r
-                                       env:remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --move node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get metadata of current node\r
+                                       remove_node(pos) -- Remove current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Move node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z - 1\r
                                end\r
                                pos.y = pos.y - 1\r
@@ -205,8 +283,15 @@ worldedit.move = function(pos1, pos2, axis, amount, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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
-worldedit.stack = function(pos1, pos2, axis, count, env)\r
+\r
+--- Duplicates a region along `axis` `amount` times.\r
+-- Stacking is spread across server steps, one copy per step.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis direction, "x", "y", or "z".\r
+-- @param count\r
+-- @return The number of nodes stacked.\r
+function worldedit.stack(pos1, pos2, axis, count)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
        local length = pos2[axis] - pos1[axis] + 1\r
        if count < 0 then\r
@@ -215,15 +300,103 @@ worldedit.stack = function(pos1, pos2, axis, count, env)
        end\r
        local amount = 0\r
        local copy = worldedit.copy\r
-       for i = 1, count do\r
-               amount = amount + length\r
-               copy(pos1, pos2, axis, amount, env)\r
+       local i = 1\r
+       local function next_one()\r
+               if i <= count then\r
+                       i = i + 1\r
+                       amount = amount + length\r
+                       copy(pos1, pos2, axis, amount)\r
+                       minetest.after(0, next_one)\r
+               end\r
        end\r
-       return worldedit.volume(pos1, pos2)\r
+       next_one()\r
+       return worldedit.volume(pos1, pos2) * count\r
+end\r
+\r
+\r
+--- Stretches a region by a factor of positive integers along the X, Y, and Z\r
+-- axes, respectively, with `pos1` as the origin.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param stretch_x Amount to stretch along X axis.\r
+-- @param stretch_y Amount to stretch along Y axis.\r
+-- @param stretch_z Amount to stretch along Z axis.\r
+-- @return The number of nodes scaled.\r
+-- @return The new scaled position 1.\r
+-- @return The new scaled position 2.\r
+function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)\r
+       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       -- Prepare schematic of large node\r
+       local get_node, get_meta, place_schematic = minetest.get_node,\r
+                       minetest.get_meta, minetest.place_schematic\r
+       local placeholder_node = {name="", param1=255, param2=0}\r
+       local nodes = {}\r
+       for i = 1, stretch_x * stretch_y * stretch_z do\r
+               nodes[i] = placeholder_node\r
+       end\r
+       local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}\r
+\r
+       local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1\r
+\r
+       local new_pos2 = {\r
+               x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,\r
+               y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,\r
+               z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,\r
+       }\r
+       worldedit.keep_loaded(pos1, new_pos2)\r
+\r
+       local pos = {x=pos2.x, y=0, z=0}\r
+       local big_pos = {x=0, y=0, z=0}\r
+       while pos.x >= pos1.x do\r
+               pos.y = pos2.y\r
+               while pos.y >= pos1.y do\r
+                       pos.z = pos2.z\r
+                       while pos.z >= pos1.z do\r
+                               local node = get_node(pos) -- Get current node\r
+                               local meta = get_meta(pos):to_table() -- Get meta of current node\r
+\r
+                               -- Calculate far corner of the big node\r
+                               local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x\r
+                               local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y\r
+                               local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z\r
+\r
+                               -- Create large node\r
+                               placeholder_node.name = node.name\r
+                               placeholder_node.param2 = node.param2\r
+                               big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z\r
+                               place_schematic(big_pos, schematic)\r
+\r
+                               -- Fill in large node meta\r
+                               if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then\r
+                                       -- Node has meta fields\r
+                                       for x = 0, size_x do\r
+                                       for y = 0, size_y do\r
+                                       for z = 0, size_z do\r
+                                               big_pos.x = pos_x + x\r
+                                               big_pos.y = pos_y + y\r
+                                               big_pos.z = pos_z + z\r
+                                               -- Set metadata of new node\r
+                                               get_meta(big_pos):from_table(meta)\r
+                                       end\r
+                                       end\r
+                                       end\r
+                               end\r
+                               pos.z = pos.z - 1\r
+                       end\r
+                       pos.y = pos.y - 1\r
+               end\r
+               pos.x = pos.x - 1\r
+       end\r
+       return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2\r
 end\r
 \r
---transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes, returning the number of nodes transposed, the new position 1, and the new position 2\r
-worldedit.transpose = function(pos1, pos2, axis1, axis2, env)\r
+\r
+--- Transposes a region between two axes.\r
+-- @return The number of nodes transposed.\r
+-- @return The new transposed position 1.\r
+-- @return The new transposed position 2.\r
+function worldedit.transpose(pos1, pos2, axis1, axis2)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
        local compare\r
@@ -239,31 +412,37 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
                end\r
        end\r
 \r
-       --calculate the new position 2 after transposition\r
-       local newpos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
-       newpos2[axis1] = pos1[axis1] + extent2\r
-       newpos2[axis2] = pos1[axis2] + extent1\r
+       -- Calculate the new position 2 after transposition\r
+       local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
+       new_pos2[axis1] = pos1[axis1] + extent2\r
+       new_pos2[axis2] = pos1[axis2] + extent1\r
+\r
+       local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}\r
+       if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end\r
+       if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end\r
+       worldedit.keep_loaded(pos1, upper_bound)\r
 \r
        local pos = {x=pos1.x, y=0, z=0}\r
-       if env == nil then env = minetest.env end\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
                                local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]\r
-                               if compare(extent1, extent2) then --transpose only if below the diagonal\r
-                                       local node1 = env:get_node(pos)\r
-                                       local meta1 = env:get_meta(pos):to_table()\r
-                                       local value1, value2 = pos[axis1], pos[axis2] --save position values\r
-                                       pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents\r
-                                       local node2 = env:get_node(pos)\r
-                                       local meta2 = env:get_meta(pos):to_table()\r
-                                       env:add_node(pos, node1)\r
-                                       env:get_meta(pos):from_table(meta1)\r
-                                       pos[axis1], pos[axis2] = value1, value2 --restore position values\r
-                                       env:add_node(pos, node2)\r
-                                       env:get_meta(pos):from_table(meta2)\r
+                               if compare(extent1, extent2) then -- Transpose only if below the diagonal\r
+                                       local node1 = get_node(pos)\r
+                                       local meta1 = get_meta(pos):to_table()\r
+                                       local value1, value2 = pos[axis1], pos[axis2] -- Save position values\r
+                                       pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents\r
+                                       local node2 = get_node(pos)\r
+                                       local meta2 = get_meta(pos):to_table()\r
+                                       set_node(pos, node1)\r
+                                       get_meta(pos):from_table(meta1)\r
+                                       pos[axis1], pos[axis2] = value1, value2 -- Restore position values\r
+                                       set_node(pos, node2)\r
+                                       get_meta(pos):from_table(meta2)\r
                                end\r
                                pos.z = pos.z + 1\r
                        end\r
@@ -271,33 +450,39 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
                end\r
                pos.x = pos.x + 1\r
        end\r
-       return worldedit.volume(pos1, pos2), pos1, newpos2\r
+       return worldedit.volume(pos1, pos2), pos1, new_pos2\r
 end\r
 \r
---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
-worldedit.flip = function(pos1, pos2, axis, env)\r
+\r
+--- Flips a region along `axis`.\r
+-- @return The number of nodes flipped.\r
+function worldedit.flip(pos1, pos2, axis)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       --- TODO: Flip the region slice by slice along the flip axis using schematic method.\r
        local pos = {x=pos1.x, y=0, z=0}\r
        local start = pos1[axis] + pos2[axis]\r
        pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)\r
-       if env == nil then env = minetest.env end\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
-                               local node1 = env:get_node(pos)\r
-                               local meta1 = env:get_meta(pos):to_table()\r
-                               local value = pos[axis]\r
-                               pos[axis] = start - value\r
-                               local node2 = env:get_node(pos)\r
-                               local meta2 = env:get_meta(pos):to_table()\r
-                               env:add_node(pos, node1)\r
-                               env:get_meta(pos):from_table(meta1)\r
-                               pos[axis] = value\r
-                               env:add_node(pos, node2)\r
-                               env:get_meta(pos):from_table(meta2)\r
+                               local node1 = get_node(pos)\r
+                               local meta1 = get_meta(pos):to_table()\r
+                               local value = pos[axis] -- Save position\r
+                               pos[axis] = start - value -- Shift position\r
+                               local node2 = get_node(pos)\r
+                               local meta2 = get_meta(pos):to_table()\r
+                               set_node(pos, node1)\r
+                               get_meta(pos):from_table(meta1)\r
+                               pos[axis] = value -- Restore position\r
+                               set_node(pos, node2)\r
+                               get_meta(pos):from_table(meta2)\r
                                pos.z = pos.z + 1\r
                        end\r
                        pos.y = pos.y + 1\r
@@ -307,78 +492,94 @@ worldedit.flip = function(pos1, pos2, axis, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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
-worldedit.rotate = function(pos1, pos2, axis, angle, env)\r
+\r
+--- Rotates a region clockwise around an axis.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis ("x", "y", or "z").\r
+-- @param angle Angle in degrees (90 degree increments only).\r
+-- @return The number of nodes rotated.\r
+-- @return The new first position.\r
+-- @return The new second position.\r
+function worldedit.rotate(pos1, pos2, axis, angle)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       local axis1, axis2\r
-       if axis == "x" then\r
-               axis1, axis2 = "z", "y"\r
-       elseif axis == "y" then\r
-               axis1, axis2 = "x", "z"\r
-       else --axis == "z"\r
-               axis1, axis2 = "y", "x"\r
-       end\r
+       local other1, other2 = worldedit.get_axis_others(axis)\r
        angle = angle % 360\r
 \r
        local count\r
        if angle == 90 then\r
-               worldedit.flip(pos1, pos2, axis1, env)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)\r
+               worldedit.flip(pos1, pos2, other1)\r
+               count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
        elseif angle == 180 then\r
-               worldedit.flip(pos1, pos2, axis1, env)\r
-               count = worldedit.flip(pos1, pos2, axis2, env)\r
+               worldedit.flip(pos1, pos2, other1)\r
+               count = worldedit.flip(pos1, pos2, other2)\r
        elseif angle == 270 then\r
-               worldedit.flip(pos1, pos2, axis2, env)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)\r
+               worldedit.flip(pos1, pos2, other2)\r
+               count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
+       else\r
+               error("Only 90 degree increments are supported!")\r
        end\r
        return count, pos1, pos2\r
 end\r
 \r
---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
-worldedit.orient = function(pos1, pos2, angle, env)\r
+\r
+--- Rotates all oriented nodes in a region clockwise around the Y axis.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param angle Angle in degrees (90 degree increments only).\r
+-- @return The number of nodes oriented.\r
+-- TODO: Support 6D facedir rotation along arbitrary axis.\r
+function worldedit.orient(pos1, pos2, angle)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       local nodes = minetest.registered_nodes\r
-       if env == nil then env = minetest.env end\r
+       local registered_nodes = minetest.registered_nodes\r
+\r
        local wallmounted = {\r
-               [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},\r
-               [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},\r
-               [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}\r
+               [90]  = {[0]=0, 1, 5, 4, 2, 3},\r
+               [180] = {[0]=0, 1, 3, 2, 5, 4},\r
+               [270] = {[0]=0, 1, 4, 5, 3, 2}\r
        }\r
        local facedir = {\r
-               [90]={[0]=1, [1]=2, [2]=3, [3]=0},\r
-               [180]={[0]=2, [1]=3, [2]=0, [3]=1},\r
-               [270]={[0]=3, [1]=0, [2]=1, [3]=2}\r
+               [90]  = {[0]=1, 2, 3, 0},\r
+               [180] = {[0]=2, 3, 0, 1},\r
+               [270] = {[0]=3, 0, 1, 2}\r
        }\r
 \r
        angle = angle % 360\r
        if angle == 0 then\r
                return 0\r
        end\r
+       if angle % 90 ~= 0 then\r
+               error("Only 90 degree increments are supported!")\r
+       end\r
        local wallmounted_substitution = wallmounted[angle]\r
        local facedir_substitution = facedir[angle]\r
 \r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
        local count = 0\r
+       local set_node, get_node, get_meta, swap_node = minetest.set_node,\r
+                       minetest.get_node, minetest.get_meta, minetest.swap_node\r
        local pos = {x=pos1.x, y=0, z=0}\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
-                               local node = env:get_node(pos)\r
-                               local def = nodes[node.name]\r
+                               local node = get_node(pos)\r
+                               local def = registered_nodes[node.name]\r
                                if def then\r
                                        if def.paramtype2 == "wallmounted" then\r
                                                node.param2 = wallmounted_substitution[node.param2]\r
-                                               local meta = env:get_meta(pos):to_table()\r
-                                               env:add_node(pos, node)\r
-                                               env:get_meta(pos):from_table(meta)\r
+                                               local meta = get_meta(pos):to_table()\r
+                                               set_node(pos, node)\r
+                                               get_meta(pos):from_table(meta)\r
                                                count = count + 1\r
                                        elseif def.paramtype2 == "facedir" then\r
                                                node.param2 = facedir_substitution[node.param2]\r
-                                               local meta = env:get_meta(pos):to_table()\r
-                                               env:add_node(pos, node)\r
-                                               env:get_meta(pos):from_table(meta)\r
+                                               local meta = get_meta(pos):to_table()\r
+                                               set_node(pos, node)\r
+                                               get_meta(pos):from_table(meta)\r
                                                count = count + 1\r
                                        end\r
                                end\r
@@ -391,23 +592,58 @@ worldedit.orient = function(pos1, pos2, angle, env)
        return count\r
 end\r
 \r
---fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated\r
-worldedit.fixlight = function(pos1, pos2, env)\r
+\r
+--- Attempts to fix the lighting in a region.\r
+-- @return The number of nodes updated.\r
+function worldedit.fixlight(pos1, pos2)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
-       local count = 0\r
 \r
-       local pos = {x=pos1.x, y=pos2.y, z=0}\r
-       while pos.x <= pos2.x do\r
-               pos.z = pos1.z\r
-               while pos.z <= pos2.z do\r
-                       if env:get_node(pos).name == "air" then\r
-                               env:dig_node(pos)\r
+       local vmanip = minetest.get_voxel_manip(pos1, pos2)\r
+       vmanip:write_to_map()\r
+       vmanip:update_map() -- this updates the lighting\r
+\r
+       return worldedit.volume(pos1, pos2)\r
+end\r
+\r
+\r
+--- Clears all objects in a region.\r
+-- @return The number of objects cleared.\r
+function worldedit.clear_objects(pos1, pos2)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       -- Offset positions to include full nodes (positions are in the center of nodes)\r
+       local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5\r
+       local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5\r
+\r
+       -- Center of region\r
+       local center = {\r
+               x = pos1x + ((pos2x - pos1x) / 2),\r
+               y = pos1y + ((pos2y - pos1y) / 2),\r
+               z = pos1z + ((pos2z - pos1z) / 2)\r
+       }\r
+       -- Bounding sphere radius\r
+       local radius = math.sqrt(\r
+                       (center.x - pos1x) ^ 2 +\r
+                       (center.y - pos1y) ^ 2 +\r
+                       (center.z - pos1z) ^ 2)\r
+       local count = 0\r
+       for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do\r
+               local entity = obj:get_luaentity()\r
+               -- Avoid players and WorldEdit entities\r
+               if not obj:is_player() and (not entity or\r
+                               not entity.name:find("^worldedit:")) then\r
+                       local pos = obj:getpos()\r
+                       if pos.x >= pos1x and pos.x <= pos2x and\r
+                                       pos.y >= pos1y and pos.y <= pos2y and\r
+                                       pos.z >= pos1z and pos.z <= pos2z then\r
+                               -- Inside region\r
+                               obj:remove()\r
                                count = count + 1\r
                        end\r
-                       pos.z = pos.z + 1\r
                end\r
-               pos.x = pos.x + 1\r
        end\r
        return count\r
 end\r
+\r