Initial //orient implementation

....unfortunately, it doesn't work yet.
Next up is a frankly ridiculous number of print()s to figure out what's going on here
This commit is contained in:
Starbeamrainbowlabs 2024-06-04 22:49:41 +01:00
parent c712b502e9
commit 02d95cec46
No known key found for this signature in database
GPG Key ID: 1BE5172E637709C2
9 changed files with 168 additions and 18 deletions

@ -41,6 +41,7 @@ dofile(wea.modpath.."/lib/dome.lua")
dofile(wea.modpath.."/lib/spline.lua") dofile(wea.modpath.."/lib/spline.lua")
dofile(wea.modpath.."/lib/revolve.lua") dofile(wea.modpath.."/lib/revolve.lua")
dofile(wea.modpath.."/lib/rotate.lua") dofile(wea.modpath.."/lib/rotate.lua")
dofile(wea.modpath.."/lib/orient.lua")
dofile(wea.modpath.."/lib/set.lua") dofile(wea.modpath.."/lib/set.lua")
dofile(wea.modpath.."/lib/conv/conv.lua") dofile(wea.modpath.."/lib/conv/conv.lua")
dofile(wea.modpath.."/lib/erode/erode.lua") dofile(wea.modpath.."/lib/erode/erode.lua")

@ -1,5 +1,5 @@
local weac = worldeditadditions_core local core = worldeditadditions_core
local Vector3 = weac.Vector3 local Vector3 = core.Vector3
--- ---
-- @module worldeditadditions -- @module worldeditadditions
@ -23,29 +23,61 @@ local Vector3 = weac.Vector3
-- --
-- Currently the only parameter in the statistics table is changed, which is a number representing the number of nodes that were rotated. -- Currently the only parameter in the statistics table is changed, which is a number representing the number of nodes that were rotated.
-- --
-- This is NOT NECESSARILY the number of nodes in the target region..... since rotations and roundings mean the target area the source region was rotated into could have slightly more or less nodes than the source region. -- This is NOT NECESSARILY the number of nodes in the target region..... since not every node in the target region will support reorientation.
function worldeditadditions.orient(pos1, pos2, rotlist) function worldeditadditions.orient(pos1, pos2, rotlist)
pos1, pos2 = Vector3.sort(pos1, pos2) pos1, pos2 = Vector3.sort(pos1, pos2)
--- 1: Compile the rotation list --- 1: Compile the rotation list
local rotlist_c = weac.rotation.rotlist_compile(rotlist) local rotlist_c = core.rotation.rotlist_compile(rotlist)
--- 2: Fetch the nodes in the specified area --- 2: Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2) local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data() local data = manip:get_data()
local data_param2 = manip:get_param2_data()
local stats = { local stats = {
count = 0 count = 0
} }
local cache_nodeid_2_param2_type = {}
local cache_orient = {}
local param2_cache = {} local param2_cache = {}
for i in area:iterp(pos1, pos2) do for i in area:iterp(pos1, pos2) do
-- data[i] -- nodeid = data[i]
local param2_type = "none"
if cache_nodeid_2_param2_type[data[i]] == nil then
local nodeid = minetest.get_name_from_content_id(data[i])
local ndef = minetest.registered_nodes[nodeid]
if type(ndef.paramtype2) ~= nil then
param2_type = ndef.paramtype2
end
cache_nodeid_2_param2_type[data[i]] = param2_type
else
param2_type = cache_nodeid_2_param2_type[data[i]]
end
stats.count = stats.count + 1 if param2_type ~= "none" then
local key = param2_type.."|"..data_param2[i]
if cache_orient[key] == nil then
cache_orient[key] = core.param2.orient(
data_param2[i],
param2_type,
rotlist_c
)
end
data_param2[i] = cache_orient[key]
stats.count = stats.count + 1
end
end end
manip:set_param2_data(data_param2)
manip:write_to_map(false)
manip:update_map()
--- 8: Return --- 8: Return
return true, stats return true, stats
end end

@ -17,6 +17,7 @@ local Vector3 = wea_c.Vector3
-- @param pos1 Vector3 Position 1 of the define region to operate on. -- @param pos1 Vector3 Position 1 of the define region to operate on.
-- @param pos2 Vector3 Position 2 of the define region to operate on. -- @param pos2 Vector3 Position 2 of the define region to operate on.
-- @param param string="param|param2" The name of the parameter to set. Currently possible values: -- @param param string="param|param2" The name of the parameter to set. Currently possible values:
--
-- - **`param`:** Param1, aka the node id. -- - **`param`:** Param1, aka the node id.
-- - **`param2`:** The supplementary parameter that each node has. See also <https://api.minetest.net/nodes/#node-paramtypes>. The node type is set by the mod or game that provides each node in question. -- - **`param2`:** The supplementary parameter that each node has. See also <https://api.minetest.net/nodes/#node-paramtypes>. The node type is set by the mod or game that provides each node in question.
-- - **`light`:** Sets the light value of all nodes in the defined region. -- - **`light`:** Sets the light value of all nodes in the defined region.

@ -0,0 +1,95 @@
local core = worldeditadditions_core
local Vector3 = core.Vector3
-- ██████ ██████ ████████ █████ ████████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ██ ██ ██ ███████ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██████ ██ ██ ██ ██ ███████
worldeditadditions_core.register_command("orient+", {
params = "<axis> <degrees> [<axis> <degrees> ...]",
description = "Rotates nodes in the defined region around the given axis by the given number of degrees. Angles are not limited to 90-degree increments, but rounding is done at the end of all calculations because rotating blocks by non-cardinal directions is not supported by the Minetest engine. When multiple axes and angles are specified, these transformations are applied in order. Note that some nodes do not have support for orientation.",
privs = { worldedit = true },
require_pos = 2,
parse = function (params_text)
if not params_text then params_text = "" end
local parts = core.split_shell(params_text)
print("DEBUG:rotate/parse parts", core.inspect(parts))
local mode_store
local mode = "AXIS"
local success, axis_next, angle
-- TODO this rotlist parser is duplicated in the //rotate+ command - deduplicate it.
local rotlist = {}
for i,part in ipairs(parts) do
if mode == "AXIS" then
-- TODO: Somehow move this parsing ----> main func to get player reference to allow for relative stuff
success, axis_next = core.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:orient/parse ROTLIST", core.inspect(rotlist))
return true, rotlist
end,
nodes_needed = function(name, _rotlist)
return Vector3.volume(core.pos.get1(name), core.pos.get2(name))
end,
func = function(name, rotlist)
local start_time = core.get_ms_time()
-------------------------------------------------
local pos1, pos2 = core.pos.get12(name)
local success, stats = worldeditadditions.orient(
pos1, pos2,
rotlist
)
if not success then return success, stats end
-------------------------------------------------
local time_taken = core.get_ms_time() - start_time
minetest.log("action", name .. " used //orient at "..pos1.." - "..pos2..", reorienting "..stats.count.." nodes in "..time_taken.."s")
return true, stats.count.." nodes reoriented through "..tostring(#rotlist).." rotations in "..core.format.human_time(time_taken)
end
})

@ -90,7 +90,7 @@ worldeditadditions_core.register_command("rotate+", {
return true, origin, rotlist return true, origin, rotlist
end, end,
nodes_needed = function(name, origin, rotlist) 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. -- 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.
return Vector3.volume(wea_c.pos.get1(name), wea_c.pos.get2(name)) * 2 return Vector3.volume(wea_c.pos.get1(name), wea_c.pos.get2(name)) * 2
end, end,
func = function(name, origin, rotlist) func = function(name, origin, rotlist)

@ -39,6 +39,7 @@ dofile(wea_cmd.modpath.."/commands/sculpt.lua")
dofile(wea_cmd.modpath.."/commands/spline.lua") dofile(wea_cmd.modpath.."/commands/spline.lua")
dofile(wea_cmd.modpath.."/commands/revolve.lua") dofile(wea_cmd.modpath.."/commands/revolve.lua")
dofile(wea_cmd.modpath.."/commands/rotate.lua") dofile(wea_cmd.modpath.."/commands/rotate.lua")
dofile(wea_cmd.modpath.."/commands/orient.lua")
dofile(wea_cmd.modpath.."/commands/set.lua") dofile(wea_cmd.modpath.."/commands/set.lua")
-- Meta Commands -- Meta Commands

@ -27,6 +27,7 @@ wea_c.Set = dofile(wea_c.modpath.."/utils/set.lua")
wea_c.Vector3 = dofile(wea_c.modpath.."/utils/vector3.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.Mesh, wea_c.Face = dofile(wea_c.modpath.."/utils/mesh.lua")
wea_c.rotation = dofile(wea_c.modpath .. "/utils/rotation.lua") wea_c.rotation = dofile(wea_c.modpath .. "/utils/rotation.lua")
wea_c.param2 = dofile(wea_c.modpath .. "/utils/param2.lua")
wea_c.Queue = dofile(wea_c.modpath.."/utils/queue.lua") wea_c.Queue = dofile(wea_c.modpath.."/utils/queue.lua")
wea_c.LRU = dofile(wea_c.modpath.."/utils/lru.lua") wea_c.LRU = dofile(wea_c.modpath.."/utils/lru.lua")

@ -1,27 +1,49 @@
local core = worldeditadditions_core
local Vector3 = core.Vector3
--- ---
-- @module worldeditadditions_core.param2 -- @module worldeditadditions_core.param2
-- //set <nodename> -- //set <nodename>
-- //set param2|p2 <param2> -- //set param2|p2 <param2>
-- In future, we'll support more param2_type values here.
local function param2_to_dir(param2_type, param2) local function param2_to_dir(param2_type, param2)
if param2_type == "" then if param2_type == "facedir" then
return { return Vector3.clone(minetest.facedir_to_dir(param2_type))
}
else else
return nil return nil
end end
end end
local function dir_to_param2(dir) local function dir_to_param2(param2_type, dir)
if param2_type == "facedir" then
return minetest.dir_to_facedir(dir, true)
else
return nil
end
end end
local function orient(param2, rotlist)
--- Rotates the given param2 value of the given type by the given COMPILED list of rotations.
-- In other words, reorients a given param2 value of a given param2_type (aka paramtype2 in the Minetest engine but the Minetest engine naming scheme is dumb in this case) according to a given COMPILED rotation list.
-- @param param2 number The param2 value to rotate.
-- @param param2_type string The type of param2 value we're talking about here. Currently, only a value of `facedir` is supported.
-- @param rotlist_c Vector3[] The list of vector rotations to apply to param2. Call `worldeditadditions_core.rotation.rotlist_compile` on a rotation list to get this value. Each one is iteratively applied using the `rotate` argument to Vector3.rotate3d.
-- @returns number? Returns the rotated param2 value, or nil if an invalid param2_type value was passed.
local function orient(param2, param2_type, rotlist_c)
local dir = param2_to_dir(param2, param2_type)
if dir == nil then return nil end
local origin = Vector3.new(0, 0, 0)
for _i, rot in ipairs(rotlist_c) do
dir = Vector3.rotate3d(origin, dir, rot)
end
dir = dir:round() -- Deal with floating-point rounding errors ref https://en.wikipedia.org/wiki/Round-off_error
-- TODO may need to do this every iteration in the above for loop un the unlikely event we have issues here
return dir_to_param2(param2_type, dir)
end end

@ -422,9 +422,6 @@ end
-- @param origin Vector3 The origin point to rotate around -- @param origin Vector3 The origin point to rotate around
-- @param point Vector3 The point to rotate. -- @param point Vector3 The point to rotate.
-- @param rotate Vector3 Rotate this much around the 3 different axes, x, y, and z. Axial rotations are handled in this order: X→Y→Z. Values MUST be in radians! -- @param rotate Vector3 Rotate this much around the 3 different axes, x, y, and z. Axial rotations are handled in this order: X→Y→Z. Values MUST be in radians!
-- @param x number Rotate this much around the X axis (yz plane), in radians.
-- @param y number Rotate this much around the Y axis (xz plane), in radians.
-- @param z number Rotate this much around the Z axis (xy plane), in radians.
-- @return Vector3 The rotated point. -- @return Vector3 The rotated point.
function Vector3.rotate3d(origin, point, rotate) function Vector3.rotate3d(origin, point, rotate)
local point_norm = point - origin local point_norm = point - origin