//rotate: start implementing a frontend

This is more complicated than I expected.
We've got a new parser and a state machine for the args, but we now need to put these values to use and pass them to worldeditadditions.rotate().
NOTE TO SELF: BE REAL CAREFUL WITH ORIGIN HANDLING.
We MUST NOT pull from sorted pos1/2 by accident! Though I doubt this would be an issue since weac.pos.get(player_name, i) is a thing
This commit is contained in:
Starbeamrainbowlabs 2023-12-10 03:16:28 +00:00
parent bce449d2e6
commit 74a8996afc
Signed by: sbrl
GPG Key ID: 1BE5172E637709C2
4 changed files with 183 additions and 2 deletions

View File

@ -9,12 +9,15 @@ local Vector3 = weac.Vector3
--- Compiles a list of rotations into something we can iteratively pass to Vector3.rotate3d.
-- TODO Learn Quaternions.
-- @param rotlist table<{axis: string, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter), and an amount in radians to rotate by (the rad parameter.
-- @param rotlist table<{axis: string|Vector3, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter) or a Vector3 (only a SINGLE AXIS set to anything other than 0, and ONLY with a value of 1 or -1), and an amount in radians to rotate by (the rad parameter.
-- @returns Vector3[] The list of the compiled rotations, in a form that Vector3.rotate3d understands.
local function __compile_rotlist(rotlist)
return weac.table.map(rotlist, function(rot)
--- 1: Construct a Vector3 to represent which axis we want to rotate on
local rotval = Vector3.new(0, 0, 0)
-- Assume that if it's a table, it's a Vector3 instance
if type(rot) == "table" then rotval = rot:clone() end
if rot.axis:find("x", 1, true) then rotval.x = 1
elseif rot.axis:find("y", 1, true) then rotval.y = 1
elseif rot.axis:find("z", 1, true) then rotval.z = 1 end
@ -33,7 +36,7 @@ end
-- @param pos1 Vector3 Position 1 of the defined region to rotate.
-- @param pos2 Vector3 Position 2 of the defined region to rotate.
-- @param origin Vector3 The coordinates of the origin point around which we should rotate the region defined by pos1..pos2.
-- @param rotlist table<{axis: string, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter), and an amount in radians to rotate by (the rad parameter.
-- @param rotlist table<{axis: string, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter), and an amount in radians to rotate by (the rad parameter).
-- @returns bool,string|table<{changed: number}> A success boolean (true == success; false == failure), followed by either an error message as a string if success == false or a table of statistics if success == true.
--
-- Currently the only parameter in the statistics table is changed, which is a number representing the number of nodes that were rotated.

View File

@ -0,0 +1,95 @@
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)
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
mode = "ORIGIN"
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
axis_next = wea_c.parse.axis_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
return true, origin, rotlist
end,
nodes_needed = function(name, times)
-- TODO: .......this is a good question, actually.
end,
func = function(name, origin, rotlist)
local start_time = wea_c.get_ms_time()
-- TODO: Do rotation operation here.
local time_taken = wea_c.get_ms_time() - start_time
-- TODO: Update logging below. This will obviously crash due to unknown variables right now.
minetest.log("action", name .. " used //rotate at "..pos1.." - "..pos2.." with origin "..origin..", replacing "..changed.." nodes in "..time_taken.."s")
return true, changed.." nodes replaced in "..wea_c.format.human_time(time_taken)
end
})

View File

@ -0,0 +1,81 @@
local weac = worldeditadditions_core
local Vector3 = weac.Vector3
-- From worldedit_hub_helper.
-- Written by me, @sbrl and just lifted :-)
local function calc_rotation_text(rotation)
if rotation <= math.pi / 4 or rotation >= (7 * math.pi) / 4 then
return Vector3.new(0, 0, 1) -- +z
elseif rotation > math.pi / 4 and rotation <= (3 * math.pi) / 4 then
return Vector3.new(-1, 0, 0) -- -x
elseif rotation > (3 * math.pi) / 4 and rotation <= (5 * math.pi) / 4 then
return Vector3.new(0, 0, -1) -- -z
else
return Vector3.new(1, 0, 0) -- +x
end
end
function rot_axis_left(axis)
if axis.x == 1 or axis.x == -1 then
axis.x, axis.z = 0, axis.x
elseif axis.z == 1 or axis.z == -1 then
axis.x, axis.z = -axis.z, 0
end
return axis -- CAUDION, this function mutates!
end
--- Parses an axis name into a string.
-- Currently handles:
-- - x/y/z -x/-y/-z
-- - front → downwards pitch
-- - back → upwards pitch
-- - left → rotate around Z to left
-- - right → rotate around Z to right
-- - pitch, yaw, roll - clockwise is considered positive.
-- @param str string The axis to parse, as a single (pre-trimmed) string.
-- @param [player=nil] Player Optional. A Minetest Player object representing the player to make any relative keywords like front-back/pitch/roll/etc relative to in parsing. Defautls to nil, which disables parsing of relatiive terms. Any object passed just needs to support player:get_look_horizontal(): https://github.com/minetest/minetest/blob/master/doc/lua_api.md#player-only-no-op-for-other-objects
-- @returns bool,Vector3|string A success bool (false=failure) followed by either an error message (if success=false) or otherwise the axis name, parsed into a Vector3 instance.
local function parse_rotation_axis_name(str, player)
local vector = Vector3.new(0, 0, 0)
-- At most ONE of these cases is true at a time.
-- Even if it were possible to handle more than one (which it isn't without quaternions which aren't used in the backend), we would have an undefined ordering problem with the application of such rotations. Rotations ≠ translations in this case!
if str:find("x", 1, true) then rotval.x = 1
elseif str:find("y", 1, true) then rotval.y = 1
elseif str:find("z", 1, true) then rotval.z = 1
elseif str:find("left", 1, true) then rotval.z = -1
elseif str:find("right", 1, true)
or str:find("yaw") then rotval.z = 1
elseif type(player) ~= "nil" then
local player_rotation_h = calc_rotation_text(player:get_look_horizontal())
-- FRONT BACK
if str:find("front", 1, true)
or str:find("f$")
or str:find("pitch") then
vector = rot_axis_left(player_rotation_h)
elseif str:find("back", 1, true)
or str:find("b$") then
vector = rot_axis_left(player_rotation_h) * -1
elseif str:find("roll") then
vector = player_rotation_h
else
return false, "Error: Could not understand rotational axis keyword '"..str.."'."
end
else
return false, "Error: Could not understand rotational axis keyword '" .. str .. "'."
end
--- Handle negatives
if str:sub(1, 1) == "-" then
vector = vector * -1
end
return true, vector
end
return {
axis_name = parse_rotation_axis_name
}

View File

@ -21,6 +21,8 @@ wea_c.parse = {
axes = axes.parse_axes,
axis_name = axes.parse_axis_name,
axes_rotation = dofile(wea_c.modpath .. "/utils/parse/axes_rotation.lua"),
seed = dofile(wea_c.modpath.."/utils/parse/seed.lua"),
chance = dofile(wea_c.modpath.."/utils/parse/chance.lua"),
map = dofile(wea_c.modpath.."/utils/parse/map.lua"),