2023-12-10 04:16:28 +01:00
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ██████ ██████ ████████ █████ ████████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ██ ██ ██ ███████ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██████ ██ ██ ██ ██ ███████
2023-12-14 00:05:00 +01:00
worldeditadditions_core.register_command ( " rotate+ " , {
2023-12-10 04:16:28 +01:00
params = " <axis> <degrees> [<axis> <degrees> ...] [origin|o [<pos_number>]] " ,
2024-06-04 23:43:09 +02:00
description = " Rotates the defined region around 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! " ,
2023-12-10 04:16:28 +01:00
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 )
2023-12-14 00:05:00 +01:00
print ( " DEBUG:rotate/parse parts " , wea_c.inspect ( parts ) )
2023-12-10 04:16:28 +01:00
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
2023-12-14 00:05:00 +01:00
-- 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
2023-12-10 04:16:28 +01:00
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
2023-12-16 02:11:25 +01:00
-- TODO: Somehow move this parsing ----> main func to get player reference to allow for relative stuff
2023-12-14 00:05:00 +01:00
success , axis_next = wea_c.parse . axes_rotation.axis_name ( part )
2023-12-10 04:16:28 +01:00
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
2023-12-14 00:05:00 +01:00
print ( " DEBUG:rotate/parse ORIGIN " , origin , " rotlist " , wea_c.inspect ( rotlist ) )
2023-12-10 04:16:28 +01:00
return true , origin , rotlist
end ,
2023-12-14 00:05:00 +01:00
nodes_needed = function ( name , origin , rotlist )
2024-06-04 23:49:41 +02:00
-- BUG: .......this is a good question, actually. This naïve implementation 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.
2023-12-14 00:05:00 +01:00
return Vector3.volume ( wea_c.pos . get1 ( name ) , wea_c.pos . get2 ( name ) ) * 2
2023-12-10 04:16:28 +01:00
end ,
func = function ( name , origin , rotlist )
local start_time = wea_c.get_ms_time ( )
2023-12-14 00:05:00 +01:00
-------------------------------------------------
2023-12-14 00:20:32 +01:00
2023-12-14 00:05:00 +01:00
local pos1 , pos2 = wea_c.pos . get1 ( name ) , wea_c.pos . get2 ( name )
2023-12-14 00:20:32 +01:00
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
2023-12-10 04:16:28 +01:00
2023-12-14 00:05:00 +01:00
local success , stats = worldeditadditions.rotate (
pos1 , pos2 ,
pos_origin ,
rotlist
)
if not success then return success , stats end
2023-12-14 01:31:12 +01:00
2023-12-16 01:01:34 +01:00
2023-12-16 01:35:52 +01:00
wea_c.pos . set1 ( name , stats.pos1_dstvm )
wea_c.pos . set2 ( name , stats.pos2_dstvm )
2023-12-14 01:31:12 +01:00
-- 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
2023-12-14 00:05:00 +01:00
-------------------------------------------------
2023-12-10 04:16:28 +01:00
local time_taken = wea_c.get_ms_time ( ) - start_time
2023-12-14 00:05:00 +01:00
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 )
2023-12-10 04:16:28 +01:00
end
} )