diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 656baad..163a071 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -30,6 +30,9 @@ dofile(worldeditadditions.modpath.."/lib/replacemix.lua") dofile(worldeditadditions.modpath.."/lib/maze2d.lua") dofile(worldeditadditions.modpath.."/lib/maze3d.lua") dofile(worldeditadditions.modpath.."/lib/hollow.lua") +dofile(worldeditadditions.modpath.."/lib/scale_up.lua") +dofile(worldeditadditions.modpath.."/lib/scale_down.lua") +dofile(worldeditadditions.modpath.."/lib/scale.lua") dofile(worldeditadditions.modpath.."/lib/conv/conv.lua") dofile(worldeditadditions.modpath.."/lib/erode/erode.lua") diff --git a/worldeditadditions/lib/scale.lua b/worldeditadditions/lib/scale.lua new file mode 100644 index 0000000..f76475b --- /dev/null +++ b/worldeditadditions/lib/scale.lua @@ -0,0 +1,44 @@ + +--- Scales the defined region by the given scale factor in the given directions. +-- Scale factor vectors containing both scale up and scale down operations are +-- split into 2 different scale operations automatically. +-- @param pos1 Vector Position 1 of the defined region, +-- @param pos2 Vector Position 2 of the defined region. +-- @param scale Vector The scale factor - as a vector - by which to scale (values between -1 and 1 are considered a scale down operation). +-- @param direction Vector The direction to scale in - as a vector. e.g. { x = -1, y = 1, z = -1 } would mean scale in the negative x, positive y, and nevative z directions. +-- @return boolean, string|table Whether the operation was successful or not. If not, then an error messagea as a string is also passed. If it was, then a statistics object is returned instead. +function worldeditadditions.scale(pos1, pos2, scale, direction) + if scale.x == 0 or scale.y == 0 or scale.z == 0 then + return false, "A component of the scale factoro was 0." + end + + local scale_down = vector.new(1, 1, 1) + local scale_up = vector.new(1, 1, 1) + + if scale.x > -1 and scale.x < 1 then scale_down.x = scale.x end + if scale.y > -1 and scale.y < 1 then scale_down.y = scale.y end + if scale.z > -1 and scale.z < 1 then scale_down.z = scale.z end + + if scale.x > 1 or scale.x < -1 then scale_up.x = scale.x end + if scale.y > 1 or scale.y < -1 then scale_up.y = scale.y end + if scale.z > 1 or scale.z < -1 then scale_up.z = scale.z end + + local stats_total = { updated = 0, operations = 0 } + + local success, stats + if scale_up.x ~= 1 or scale_up.y ~= 1 or scale_up.z ~= 1 then + success, stats = worldeditadditions.scale_up(pos1, pos2, scale_up, direction) + if not success then return success, stats end + stats_total.updated = stats.updated + stats_total.operations = stats_total.operations + 1 + stats_total.scale_down = stats.scale + end + if scale_down.x ~= 1 or scale_down.y ~= 1 or scale_down.z ~= 1 then + success, stats = worldeditadditions.scale_down(pos1, pos2, scale_down, direction) + if not success then return success, stats end + stats_total.updated = stats_total.updated + stats.updated + stats_total.operations = stats_total.operations + 1 + end + + return true, stats_total +end diff --git a/worldeditadditions/lib/scale_down.lua b/worldeditadditions/lib/scale_down.lua new file mode 100644 index 0000000..09a399e --- /dev/null +++ b/worldeditadditions/lib/scale_down.lua @@ -0,0 +1,78 @@ +-- ███████ ██████ █████ ██ ███████ ██████ ██████ ██ ██ ███ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +-- ███████ ██ ███████ ██ █████ ██ ██ ██ ██ ██ █ ██ ██ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ +-- ███████ ██████ ██ ██ ███████ ███████ ███████ ██████ ██████ ███ ███ ██ ████ + +--- Scales the defined region down by the given scale factor in the given directions. +-- @param pos1 Vector Position 1 of the defined region, +-- @param pos2 Vector Position 2 of the defined region. +-- @param scale Vector The scale factor - as a vector - by which to scale down. +-- @param direction Vector The direction to scale in - as a vector. e.g. { x = -1, y = 1, z = -1 } would mean scale in the negative x, positive y, and nevative z directions. +-- @return boolean, string|table Whether the operation was successful or not. If not, then an error messagea as a string is also passed. If it was, then a statistics object is returned instead. +function worldeditadditions.scale_down(pos1, pos2, scale, direction) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + if scale.x > 1 or scale.y > 1 or scale.z > 1 or scale.x < -1 or scale.y < -1 or scale.z < -1 then + return false, "Error: Scale factor vectors may not mix values -1 < factor < 1 and (1 < factor or factor < -1) - in other words, you can't scale both up and down at the same time (try worldeditadditions.scale, which automatically applies such scale factor vectors as 2 successive operations)" + end + if direction.x == 0 or direction.y == 0 or direction.z == 0 then + return false, "Error: One of the components of the direction vector was 0 (direction components should either be greater than or less than 0 to indicate the direction to scale in.)" + end + + local scale_down = { + x = math.floor(1 / scale.x), + y = math.floor(1 / scale.y), + z = math.floor(1 / scale.z) + } + print("[DEBUG] scale_down", worldeditadditions.vector.tostring(scale_down)) + local size = vector.subtract(pos2, pos1) + + local data = manip:get_data() + local data_copy = worldeditadditions.shallowcopy(data) + + local node_id_air = minetest.get_content_id("air") + + + local count = 0 -- The number of nodes replaced + + local stats = { updated = 0, scale = scale_down } + -- Zero out the area we're scaling down into + for i in area:iterp(pos1, pos2) do + data_copy[i] = node_id_air + -- We update the entire area, even though we're scaling down + -- ....because we fill in the area we left behind with air + stats.updated = stats.updated + 1 + end + + for z = pos2.z, pos1.z, -1 do + for y = pos2.y, pos1.y, -1 do + for x = pos2.x, pos1.x, -1 do + local posi_rel = vector.subtract({ x = x, y = y, z = z }, pos1) + + local posi_copy = worldeditadditions.shallowcopy(posi_rel) + posi_copy = vector.floor(vector.divide(scale_down)) + + if direction.x < 0 then posi_copy.x = size.x - posi_copy.x end + if direction.y < 0 then posi_copy.y = size.y - posi_copy.y end + if direction.z < 0 then posi_copy.z = size.z - posi_copy.z end + + local posi_copy = vector.add(pos1, posi_copy) + + local i_source = area:index(x, y, z) + local i_target = area:index(posi_copy.x, posi_copy.y, posi_copy.z) + + -- Technically we could save some operations here by not setting + -- the target multiple times per copy, but the calculations + -- above are probably a lot more taxing + -- TODO Be more intelligent about deciding what node to replace with here + data_copy[i_target] = data[i_source] + end + end + end + + + -- Save the modified nodes back to disk & return + worldedit.manip_helpers.finish(manip, data_copy) + + return true, changes +end diff --git a/worldeditadditions/lib/scale_up.lua b/worldeditadditions/lib/scale_up.lua new file mode 100644 index 0000000..10b69cb --- /dev/null +++ b/worldeditadditions/lib/scale_up.lua @@ -0,0 +1,75 @@ +-- ███████ ██████ █████ ██ ███████ ██ ██ ██████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██ ███████ ██ █████ ██ ██ ██████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██████ ██ ██ ███████ ███████ ███████ ██████ ██ + +--- Scales the defined region down by the given scale factor in the given directions. +-- @param pos1 Vector Position 1 of the defined region, +-- @param pos2 Vector Position 2 of the defined region. +-- @param scale Vector The scale factor - as a vector - by which to scale down. +-- @param direction Vector The direction to scale in - as a vector. e.g. { x = -1, y = 1, z = -1 } would mean scale in the negative x, positive y, and nevative z directions. +-- @return boolean, string|table Whether the operation was successful or not. If not, then an error messagea as a string is also passed. If it was, then a statistics object is returned instead. +function worldeditadditions.scale_up(pos1, pos2, scale, direction) + return false, "Error: Scaling up isn't implemented yet." + -- pos1, pos2 = worldedit.sort_pos(pos1, pos2) + -- if scale.x > 1 or scale.y > 1 or scale.z > 1 or scale.x < -1 or scale.y < -1 or scale.z < -1 then + -- return false, "Error: Scale factor vectors may not mix values -1 < factor < 1 and (1 < factor or factor < -1) - in other words, you can't scale both up and down at the same time (try worldeditadditions.scale, which automatically applies such scale factor vectors as 2 successive operations)" + -- end + -- if direction.x == 0 or direction.y == 0 or direction.z == 0 then + -- return false, "Error: One of the components of the direction vector was 0 (direction components should either be greater than or less than 0 to indicate the direction to scale in.)" + -- end + -- + -- local scale_down = { + -- x = math.floor(1 / scale.x), + -- y = math.floor(1 / scale.y), + -- z = math.floor(1 / scale.z) + -- } + -- local size = vector.subtract(pos2, pos1) + -- + -- local data = manip:get_data() + -- local data_copy = worldeditadditions.shallowcopy(data) + -- + -- local node_id_air = minetest.get_content_id("air") + -- + -- + -- local count = 0 -- The number of nodes replaced + -- + -- -- Zero out the area we're scaling down into + -- for i in area:iterp(pos1, pos2) do + -- data_copy[i] = node_id_air + -- end + -- + -- local changes = { replaced = 0 } + -- for z = pos2.z, pos1.z, -1 do + -- for y = pos2.y, pos1.y, -1 do + -- for x = pos2.x, pos1.x, -1 do + -- local posi_rel = vector.subtract({ x = x, y = y, z = z }, pos1) + -- + -- local posi_copy = worldeditadditions.shallowcopy(posi_rel) + -- posi_copy = vector.floor(vector.divide(scale_down)) + -- + -- if direction.x < 0 then posi_copy.x = size.x - posi_copy.x end + -- if direction.y < 0 then posi_copy.y = size.y - posi_copy.y end + -- if direction.z < 0 then posi_copy.z = size.z - posi_copy.z end + -- + -- local posi_copy = vector.add(pos1, posi_copy) + -- + -- local i_source = area:index(x, y, z) + -- local i_target = area:index(posi_copy.x, posi_copy.y, posi_copy.z) + -- + -- -- Technically we could save some operations here by not setting + -- -- the target multiple times per copy, but the calculations + -- -- above are probably a lot more taxing + -- -- TODO Be more intelligent about deciding what node to replace with here + -- data_copy[i_target] = data[i_source] + -- end + -- end + -- end + -- + -- + -- -- Save the modified nodes back to disk & return + -- worldedit.manip_helpers.finish(manip, data_copy) + -- + -- return true, changes +end diff --git a/worldeditadditions_commands/commands/scale.lua b/worldeditadditions_commands/commands/scale.lua new file mode 100644 index 0000000..a236da0 --- /dev/null +++ b/worldeditadditions_commands/commands/scale.lua @@ -0,0 +1,98 @@ +-- ███████ ██████ █████ ██ ███████ +-- ██ ██ ██ ██ ██ ██ +-- ███████ ██ ███████ ██ █████ +-- ██ ██ ██ ██ ██ ██ +-- ███████ ██████ ██ ██ ███████ ███████ +worldedit.register_command("scale", { + params = " | [ [ ]]", + description = "Combined scale up / down. Takes either an axis name + a scale factor (e.g. y 3 or -z 2; negative values swap the anchor point for the scale operation), or 3 scale factor values for x, y, and z respectively. In the latter mode, a set of anchors can also be specified, which indicate which size the scale operation should be anchored to.", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + if not params_text then params_text = "" end + + local parts = worldeditadditions.split(params_text, "%s+", false) + + local scale = vector.new(1, 1, 1) + local direction = vector.new(1, 1, 1) + + if #parts == 2 then + if parts[1] ~= "x" or parts[1] ~= "y" or parts[1] ~= "z" + or parts[1] ~= "-x" or parts[1] ~= "-y" or parts[1] ~= "-z" then + return false, "Error: Got 2 arguments, but the first doesn't look like the name of an axis." + end + local axis = parts[1] + local factor = tonumber(parts[2]) + if not factor then return false, "Error: Invalid scale factor." end + + if axis:sub(1, 1) == "-" then + axis = axis:sub(2, 2) + direction[axis] = -1 + end + + scale[axis] = factor + elseif #parts >= 3 then + local val = tonumber(parts[1]) + if not val then return false, "Error: x axis scale factor wasn't a number." end + scale.x = val + val = tonumber(parts[2]) + if not val then return false, "Error: y axis scale factor wasn't a number." end + scale.y = val + val = tonumber(parts[3]) + if not val then return false, "Error: z axis scale factor wasn't a number." end + scale.z = val + else + local val = tonumber(parts[1]) + if not val then + return false, "Error: scale factor wasn't a number." + end + scale.x = val + scale.y = val + scale.z = val + end + + if #parts == 6 then + local val = tonumber(parts[4]) + if not val then return false, "Error: x axis anchor wasn't a number." end + direction.x = val + val = tonumber(parts[5]) + if not val then return false, "Error: y axis anchor wasn't a number." end + direction.y = val + val = tonumber(parts[6]) + if not val then return false, "Error: z axis anchor wasn't a number." end + direction.z = val + end + + if scale.x == 0 then return false, "Error: x scale factor was 0" end + if scale.y == 0 then return false, "Error: y scale factor was 0" end + if scale.z == 0 then return false, "Error: z scale factor was 0" end + + if direction.x == 0 then return false, "Error: x axis anchor was 0" end + if direction.y == 0 then return false, "Error: y axis anchor was 0" end + if direction.z == 0 then return false, "Error: z axis anchor was 0" end + + return true, scale, direction + end, + nodes_needed = function(name, scale, direction) + local volume = worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) + local factor = math.ceil(math.abs(scale.x)) + * math.ceil(math.abs(scale.y)) + * math.ceil(math.abs(scale.z)) + return volume * factor + end, + func = function(name, scale, direction) + local start_time = worldeditadditions.get_ms_time() + + local success, stats = worldeditadditions.scale(pos1, pos2, scale, direction) + if not success then return success, stats end + + -- TODO read stats here + + + local time_taken = worldeditadditions.get_ms_time() - start_time + + + minetest.log("action", name.." used //scale at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", updating "..stats.updated.." nodes in "..time_taken.."s") + return true, stats.updated.." nodes updated in " .. worldeditadditions.human_time(time_taken) + end +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 4ff4f02..ff3aa89 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -32,6 +32,7 @@ dofile(we_c.modpath.."/commands/replacemix.lua") dofile(we_c.modpath.."/commands/convolve.lua") dofile(we_c.modpath.."/commands/erode.lua") dofile(we_c.modpath.."/commands/hollow.lua") +dofile(we_c.modpath.."/commands/scale.lua") dofile(we_c.modpath.."/commands/count.lua") dofile(we_c.modpath.."/commands/saplingaliases.lua")