//rotate: fix bug in which regions are accidentally cut off

This commit is contained in:
Starbeamrainbowlabs 2023-12-16 00:35:52 +00:00
parent 0ac76bc694
commit 54c66e0dc9
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
5 changed files with 107 additions and 38 deletions

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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()

View file

@ -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
}