Minetest-WorldEditAdditions/worldeditadditions_commands/commands/rotate.lua
2023-12-16 00:01:34 +00:00

132 lines
5.3 KiB
Lua

local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ██████ ██████ ████████ █████ ████████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ██ ██ ██ ███████ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██████ ██ ██ ██ ██ ███████
worldeditadditions_core.register_command("rotate+", {
params = "<axis> <degrees> [<axis> <degrees> ...] [origin|o [<pos_number>]]",
description = "Rotates the defined region arounnd the given axis by the given number of degrees. Angles are NOT limited to 90-degree increments. When multiple axes and angles are specified, these transformations are applied in order. If o [<pos_number>] is specified, then the specific position number (default: 3) is considered a custom rotation origin instead of the centre of the region. CAUTION: Rotating large areas of memory by 45° can be memory intensive!",
privs = { worldedit = true },
require_pos = 2,
parse = function (params_text)
if not params_text then params_text = "" end
local parts = wea_c.split_shell(params_text)
print("DEBUG:rotate/parse parts", wea_c.inspect(parts))
local mode_store
local mode = "AXIS"
local success, axis_next, angle
local origin = "__AUTO__"
local rotlist = {}
for i,part in ipairs(parts) do
if part == "origin" or part == "o" then
mode_store = mode
-- If the NEXT item is a number, then parse it.
if i < #parts and tonumber(parts[i+1]) ~= nil then
mode = "ORIGIN"
else
-- If not, then default to pos3 and continue in the original mode
origin = 3
end
elseif mode == "ORIGIN" then
origin = tonumber(part)
if not origin or origin < 1 then
return false, "Error: Expected positive integer as the WorldEditAdditions position that should be considered the origin point for rotation operation."
end
origin = math.floor(origin)
mode = mode_store
mode_store = nil
elseif mode == "AXIS" then
success, axis_next = wea_c.parse.axes_rotation.axis_name(part)
if not success then return success, axis_next end
mode = "ANGLE"
elseif mode == "ANGLE" then
angle = part
-- 1: Determine if radians; strip suffix
local pos_rad = part:find("ra?d?$")
if pos_rad then
angle = angle:sub(1, pos_rad-1)
end
-- 2: Parse as number
angle = tonumber(angle)
if not angle then
return false, "Error: Expected numerical angle value, but found '"..tostring(part).."'."
end
-- 3: Convert degrees → radians
if not pos_rad then
-- We have degrees! Convert em to radians 'cauuse mathematics
angle = math.rad(angle)
end
-- 4: Add to rotlist
table.insert(rotlist, {
axis = axis_next,
rad = angle
})
-- 5: Change mode and continue
mode = "AXIS"
else
return false, "Error: Unknown parsing mode "..tostring(mode)..". This is a bug."
end
end
print("DEBUG:rotate/parse ORIGIN", origin, "rotlist", wea_c.inspect(rotlist))
return true, origin, rotlist
end,
nodes_needed = function(name, origin, rotlist)
-- BUG: .......this is a good question, actually. This naïve is flawed, since if we rotate by e.g. 45° we could end up replacing more nodes than if we rotate by 90° increments. This is further complicated by the allowance of a custom point of origin.
return Vector3.volume(wea_c.pos.get1(name), wea_c.pos.get2(name)) * 2
end,
func = function(name, origin, rotlist)
local start_time = wea_c.get_ms_time()
-------------------------------------------------
local pos1, pos2 = wea_c.pos.get1(name), wea_c.pos.get2(name)
local pos_origin = nil
if type(origin) == "number" then
pos_origin = wea_c.pos.get(name, origin)
elseif type(origin) == "string" and origin == "__AUTO__" then
pos_origin = Vector3.mean(pos1, pos2)
end
if pos_origin == nil then
-- There's a very small chance that this could be a bug here if origin is (NOT type("number") and NOT type("string") and ~= "__AUTO__"), but this shouldn't happen because we constrain the output of the above parser. This means we can safely make this assumption here
return false, "Error: Failed to get origin point from position "..tostring(origin).." because it doesn't exist."
end
local success, stats = worldeditadditions.rotate(
pos1, pos2,
pos_origin,
rotlist
)
if not success then return success, stats end
wea_c.pos.set1(name, stats.pos1_dstvm + Vector3.new(1, 1, 1))
wea_c.pos.set2(name, stats.pos2_dstvm - Vector3.new(1, 1, 1))
-- TODO: Adjust the defined area to match the target here? Maybe make this optional somehow given the target may or may nor be axis-aligned
-------------------------------------------------
local time_taken = wea_c.get_ms_time() - start_time
minetest.log("action", name .. " used //rotate at "..pos1.." - "..pos2.." with origin "..pos_origin..", rotating "..stats.count_rotated.." nodes in "..time_taken.."s")
return true, stats.count_rotated.." nodes rotated through "..tostring(#rotlist).." rotations in "..wea_c.format.human_time(time_taken)
end
})