From 54c66e0dc99c63dd40c307f74b87b11d573ccabe Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 16 Dec 2023 00:35:52 +0000 Subject: [PATCH] //rotate: fix bug in which regions are accidentally cut off --- worldeditadditions/lib/rotate.lua | 48 +++------- .../commands/rotate.lua | 4 +- worldeditadditions_core/init.lua | 2 + .../utils/parse/axes_parser.lua | 2 +- worldeditadditions_core/utils/rotation.lua | 89 +++++++++++++++++++ 5 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 worldeditadditions_core/utils/rotation.lua diff --git a/worldeditadditions/lib/rotate.lua b/worldeditadditions/lib/rotate.lua index b028579..bb75610 100644 --- a/worldeditadditions/lib/rotate.lua +++ b/worldeditadditions/lib/rotate.lua @@ -7,32 +7,6 @@ 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|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.axis:clone() - else - -- Otherwise, treat it as a string - 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 - if rot.axis:sub(1, 1) == "-" then - rotval = rotval * -1 - end - end - - - --- 2: Rotate & apply amount of rotation to apply in radians - return rotval * rot.rad - end) -end --- Rotates the given region around a given origin point using a set of rotations. @@ -50,22 +24,25 @@ function worldeditadditions.rotate(pos1, pos2, origin, rotlist) pos1, pos2 = Vector3.sort(pos1, pos2) --- 1: Compile the rotation list - local rotlist_c = __compile_rotlist(rotlist) + local rotlist_c = weac.rotation.rotlist_compile(rotlist) --- 2: Find the target area we will be rotating into -- First, rotate the defined region to find the target region - local pos1_rot, pos2_rot = pos1:clone(), pos2:clone() - for i, rot in ipairs(rotlist_c) do - pos1_rot = Vector3.rotate3d(origin, pos1_rot, rot) - pos2_rot = Vector3.rotate3d(origin, pos2_rot, rot) - end + -- local pos1_rot, pos2_rot = pos1:clone(), pos2:clone() + -- for i, rot in ipairs(rotlist_c) do + -- pos1_rot = Vector3.rotate3d(origin, pos1_rot, rot) + -- pos2_rot = Vector3.rotate3d(origin, pos2_rot, rot) + -- end + -- Then, align it to the world axis so we can grab a VoxelManipulator -- We add 1 node either side for safety just in case of rounding errors when actually rotating - local pos1_dstvm, pos2_dstvm = Vector3.sort(pos1_rot, pos2_rot) + local pos1_dstvm, pos2_dstvm = weac.rotation.find_rotated_vm(pos1, pos2, origin, rotlist) pos1_dstvm = pos1_dstvm:floor() - Vector3.new(1, 1, 1) pos2_dstvm = pos2_dstvm:ceil() + Vector3.new(1, 1, 1) + -- print("DEBUG:rotate pos1", pos1, "pos1_rot", pos1_rot, "pos1_dstvm", pos1_dstvm, "pos2", pos2, "pos2_rot", pos2_rot, "pos2_dstvm", pos2_dstvm) + --- 3: Check out a VoxelManipulator for the source and target regions -- TODO support param2 here @@ -137,7 +114,8 @@ function worldeditadditions.rotate(pos1, pos2, origin, rotlist) --- 8: Return return true, { count_rotated = count_rotated, - pos1_dstvm = pos1_dstvm, - pos2_dstvm = pos2_dstvm + -- Undo the +/-1 when passing back + pos1_dstvm = pos1_dstvm + Vector3.new(1, 1, 1), + pos2_dstvm = pos2_dstvm - Vector3.new(1, 1, 1) } end \ No newline at end of file diff --git a/worldeditadditions_commands/commands/rotate.lua b/worldeditadditions_commands/commands/rotate.lua index 6489dac..df07de7 100644 --- a/worldeditadditions_commands/commands/rotate.lua +++ b/worldeditadditions_commands/commands/rotate.lua @@ -119,8 +119,8 @@ worldeditadditions_core.register_command("rotate+", { 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)) + wea_c.pos.set1(name, stats.pos1_dstvm) + wea_c.pos.set2(name, stats.pos2_dstvm) -- 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 diff --git a/worldeditadditions_core/init.lua b/worldeditadditions_core/init.lua index 85409b3..b3915cf 100644 --- a/worldeditadditions_core/init.lua +++ b/worldeditadditions_core/init.lua @@ -26,6 +26,7 @@ wea_c.Set = dofile(wea_c.modpath.."/utils/set.lua") wea_c.Vector3 = dofile(wea_c.modpath.."/utils/vector3.lua") wea_c.Mesh, wea_c.Face = dofile(wea_c.modpath.."/utils/mesh.lua") +wea_c.rotation = dofile(wea_c.modpath .. "/utils/rotation.lua") wea_c.Queue = dofile(wea_c.modpath.."/utils/queue.lua") wea_c.LRU = dofile(wea_c.modpath.."/utils/lru.lua") @@ -48,6 +49,7 @@ dofile(wea_c.modpath.."/utils/format/init.lua") dofile(wea_c.modpath.."/utils/parse/init.lua") dofile(wea_c.modpath.."/utils/table/init.lua") + dofile(wea_c.modpath.."/utils/numbers.lua") dofile(wea_c.modpath.."/utils/nodes.lua") dofile(wea_c.modpath.."/utils/node_identification.lua") diff --git a/worldeditadditions_core/utils/parse/axes_parser.lua b/worldeditadditions_core/utils/parse/axes_parser.lua index e9dc624..1f47ef1 100644 --- a/worldeditadditions_core/utils/parse/axes_parser.lua +++ b/worldeditadditions_core/utils/parse/axes_parser.lua @@ -137,7 +137,7 @@ end -- @param: tbl: Table: Keyword table to parse -- @param: facing: Table: Output from worldeditadditions_core.player_dir(name) -- @param: sum: Bool | String | nil: Return a single vector by summing the 2 output vectors together --- @returns: Vector3, [Vector3]: returns min, max Vector3s or sum Vector3 (if @param: sum ~= nil) +-- @returns: Vector3, [Vector3]: returns min, max Vector3 or sum Vector3 (if @param: sum ~= nil) -- if error: @returns: false, String: error message function parse.keytable(tbl, facing, sum) local min, max = Vector3.new(), Vector3.new() diff --git a/worldeditadditions_core/utils/rotation.lua b/worldeditadditions_core/utils/rotation.lua new file mode 100644 index 0000000..450ae39 --- /dev/null +++ b/worldeditadditions_core/utils/rotation.lua @@ -0,0 +1,89 @@ +local weac = worldeditadditions_core +local Vector3 = weac.Vector3 + +--- Compiles a list of rotations into something we can iteratively pass to Vector3.rotate3d. +-- This function is called internally. You are unlikely to need to call this function unless you are implementing something like worldeditadditions.rotate() or similar. +-- +-- TODO Learn Quaternions. +-- @internal +-- @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 rotlist_compile(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.axis:clone() + else + -- Otherwise, treat it as a string + 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 + if rot.axis:sub(1, 1) == "-" then + rotval = rotval * -1 + end + end + + + --- 2: Rotate & apply amount of rotation to apply in radians + return rotval * rot.rad + end) +end + + +--- Applies a given list of rotatiosn rotlist to rotate pos1 and pos2 around a given origin, and returns a pos1/pos2 pair of a region that bounds the rotated area. +-- The returned pos1/pos2 are guaranteed to be integer values that fully enclose the rotated region. This function is designed to be used to e.g. find the bounds of a region to pass to a VoxelManip to ensure we grab everything. +-- @param pos1 Vector3 Position 1 to rotate. +-- @param pos2 Vector3 Position 2 to rotate. +-- @param origin Vector3 The position to rotate pos1 and pos2 around. May be a decimal value. +-- @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). See worldeditadditions.rotate() for more information. +-- @returns Vector3,Vector3 A pos1 & pos2 that fully enclose the rotated region as described above. +local function find_rotated_vm(pos1, pos2, origin, rotlist) + local rotlist_c = rotlist_compile(rotlist) + pos1, pos2 = Vector3.sort(pos1, pos2) + + local corners = { + Vector3.new(pos1.x, pos1.y, pos1.z), + Vector3.new(pos2.x, pos1.y, pos1.z), + Vector3.new(pos1.x, pos2.y, pos1.z), + Vector3.new(pos1.x, pos1.y, pos2.z), + + Vector3.new(pos2.x, pos2.y, pos1.z), + Vector3.new(pos1.x, pos2.y, pos2.z), + Vector3.new(pos2.x, pos1.y, pos2.z), + Vector3.new(pos2.x, pos2.y, pos2.z), + } + local corners_rot = weac.table.map(corners, function(vec) + local result = vec:clone() + for i, rot in ipairs(rotlist_c) do + result = Vector3.rotate3d(origin, result, rot) + end + return result + end) + + local pos1_dstvm = weac.table.reduce(corners_rot, function(acc, vec) + return Vector3.min(acc, vec) + end, corners_rot[1]:clone()) + local pos2_dstvm = weac.table.reduce(corners_rot, function(acc, vec) + return Vector3.max(acc, vec) + end, corners_rot[1]:clone()) + + print("DEBUG:find_rotated_vm pos1_dstvm", pos1_dstvm, "pos2_dstvm", pos2_dstvm) + + pos1_dstvm = pos1_dstvm:floor() - Vector3.new(1, 1, 1) + pos2_dstvm = pos2_dstvm:ceil() + Vector3.new(1, 1, 1) + + return pos1_dstvm, pos2_dstvm +end + + + +return { + find_rotated_vm = find_rotated_vm, + rotlist_compile = rotlist_compile +} \ No newline at end of file