implement initial //spline, but it isn't finished yet

This commit is contained in:
Starbeamrainbowlabs 2022-09-25 01:18:40 +01:00
parent a768378dfd
commit 258a9c1cde
No known key found for this signature in database
GPG Key ID: 1BE5172E637709C2
5 changed files with 219 additions and 1 deletions

@ -0,0 +1,80 @@
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ███████ ██████ ██ ██ ███ ██ ███████
-- ██ ██ ██ ██ ██ ████ ██ ██
-- ███████ ██████ ██ ██ ██ ██ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ██ ███████ ██ ██ ████ ███████
--- Creates a spline that follows the given list of points.
-- @param pos_list Vector3[] The list of points that define the path the spline should follow. Minimum: 2
-- @param width_start number The width of the spline at the start.
-- @param width_end number The width of the spline at the end. If nil, then width_start is used.
-- @param steps number The number of smoothing passes to apply to the list of points.
-- @param target_node Vector3 The *normalised* name of the node to use to build the square spiral with.
-- @returns bool,number|string A success boolean value, followed by either the number of the nodes set or an error message string.
function worldeditadditions.spline(pos_list, width_start, width_end, steps, target_node)
if #pos_list < 2 then return false, "Error: At least 2 positions are required to make a spline." end
if not width_end then width_end = width_start end
---
-- 0: Find bounding box
---
-- We can't use wea_c.pos.get_bounds 'cause that requires a player name
local pos1, pos2 = pos_list[1], pos_list[2]
for _, pos in pairs(pos_list) do
pos1, pos2 = Vector3.expand_region(pos, pos1, pos2)
end
local max_width = math.max(width_start, width_end)
pos1 = pos1 - max_width
pos2 = pos2 + max_width
---
-- 1: Interpolate points & widths
---
local pos_list_chaikin = wea_c.chaikin(pos_list, steps)
local widths_lerped = {}
for i = 1, #pos_list_chaikin do
table.insert(
widths_lerped,
wea_c.lerp(width_start, width_end, (1/#pos_list_chaikin)*(i-1))
)
end
---
-- 2: Fetch VoxelManipulator, prepare for replacement
---
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
local node_id = minetest.get_content_id(target_node)
local count = 0 -- The number of nodes replaced
---
-- 3: Replace nodes
---
for i = 1, #pos_list_chaikin do
local pos_next = pos_list_chaikin[i]
local width_next = widths_lerped[i]
-- For now, just plot a point at each node
local index_node = area:index(pos_next.x, pos_next.y, pos_next.z)
data[index_node] = node_id
count = count + 1
end
---
-- 4: Save changes and return
---
-- Save the modified nodes back to disk & return
worldedit.manip_helpers.finish(manip, data)
return true, count
end

@ -0,0 +1,91 @@
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ███████ ██████ ██ ██ ███ ██ ███████
-- ██ ██ ██ ██ ██ ████ ██ ██
-- ███████ ██████ ██ ██ ██ ██ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ██ ███████ ██ ██ ████ ███████
worldeditadditions_core.register_command("spline", {
params = "<replace_node> <width_start> [<width_end=width_start> [<steps=6>]]",
description = "Draws a spline through the defined points. NOTE: Uses the NEW worldeditadditions position system, not the existing WorldEdit one!",
privs = { worldedit = true },
require_pos = 3,
parse = function(params_text)
local parts = wea_c.split_shell(params_text)
local replace_node
local width_start
local width_end
local steps = 6
if #parts < 1 then
return false, "Error: The replace_node (e.g. dirt) was not specified."
end
if #parts < 2 then
return false, "Error: The starting width wasn't specified."
end
replace_node = worldedit.normalize_nodename(parts[1])
if not replace_node then
return false, "Error: Unknown node name '"..parts[1].."."
end
width_start = tonumber(parts[2])
if not width_start then
return false, "Error: width_start must be an integer greater than or equal to 1."
end
if width_start < 1 then
return false, "Error: width_start must be an integer greater than 0, but you passed '"..parts[2].."'."
end
if #parts >= 3 then
width_end = tonumber(parts[3])
if not width_end then
return false, "Error: width_end must be an integer greater than or equal to 1."
end
if width_end < 1 then
return false, "Error: width_end must be an integer greater than 0, but you passed '"..parts[3].."'."
end
else
width_end = width_start
end
if #parts >= 4 then
steps = tonumber(parts[4])
if not steps then
return false, "Error: steps must be an integer greater than or equal to 0."
end
if steps < 0 then
return false, "Error: steps must be an integer greater than or equal to 0, but you passed '"..parts[3].."'."
end
end
return true, replace_node, width_start, width_end, steps
end,
nodes_needed = function(player_name, _replace_node, _width_start, _width_end, steps)
if steps > 10 then return "may result in a large number of interpolated points" end
-- //overlay only modifies up to 1 node per column in the selected region
local pos1, pos2 = wea_c.pos.get_bounds(player_name)
return Vector3.volume(pos1, pos2)
end,
func = function(player_name, replace_node, width_start, width_end, steps)
local start_time = wea_c.get_ms_time()
local pos_list = wea_c.pos.get_all(player_name)
local success, nodes_replaced = worldeditadditions.spline(
pos_list,
width_start,
width_end,
replace_node
)
if not success then return success, nodes_replaced end
local time_taken = wea_c.format.human_time(wea_c.get_ms_time() - start_time)
minetest.log("action", player_name.." used //spline with "..#pos_list.." initial points and "..steps.." steps, replacing "..nodes_replaced.." nodes in "..time_taken)
return true, nodes_replaced.." nodes replaced in "..time_taken
end
})

@ -36,6 +36,7 @@ dofile(wea_cmd.modpath.."/commands/dome.lua")
dofile(wea_cmd.modpath.."/commands/metaball.lua") dofile(wea_cmd.modpath.."/commands/metaball.lua")
dofile(wea_cmd.modpath.."/commands/count.lua") dofile(wea_cmd.modpath.."/commands/count.lua")
dofile(wea_cmd.modpath.."/commands/sculpt.lua") dofile(wea_cmd.modpath.."/commands/sculpt.lua")
dofile(wea_cmd.modpath.."/commands/spline.lua")
-- Meta Commands -- Meta Commands
dofile(wea_cmd.modpath.."/commands/meta/init.lua") dofile(wea_cmd.modpath.."/commands/meta/init.lua")

@ -41,6 +41,10 @@ wea_c.bit = dofile(wea_c.modpath.."/utils/bit.lua")
wea_c.terrain = dofile(wea_c.modpath.."/utils/terrain/init.lua") wea_c.terrain = dofile(wea_c.modpath.."/utils/terrain/init.lua")
local chaikin = dofile(wea_c.modpath.."/utils/chaikin.lua")
wea_c.chaikin = chaikin.chaikin
wea_c.lerp = chaikin.linear_interpolate
dofile(wea_c.modpath.."/utils/strings/init.lua") dofile(wea_c.modpath.."/utils/strings/init.lua")
dofile(wea_c.modpath.."/utils/format/init.lua") dofile(wea_c.modpath.."/utils/format/init.lua")
dofile(wea_c.modpath.."/utils/parse/init.lua") dofile(wea_c.modpath.."/utils/parse/init.lua")
@ -57,7 +61,6 @@ dofile(wea_c.modpath.."/utils/player.lua") -- Player info functions
wea_c.pos = dofile(modpath.."/core/pos.lua") -- AFTER EventEmitter wea_c.pos = dofile(modpath.."/core/pos.lua") -- AFTER EventEmitter
wea_c.register_command = dofile(modpath.."/core/register_command.lua") wea_c.register_command = dofile(modpath.."/core/register_command.lua")
wea_c.fetch_command_def = dofile(modpath.."/core/fetch_command_def.lua") wea_c.fetch_command_def = dofile(modpath.."/core/fetch_command_def.lua")

@ -0,0 +1,43 @@
local wea_c = worldeditadditions_core
--- Interpolates between the 2 given points
-- @param a Vector3|number The starting point.
-- @param b Vector3|number The ending point.
-- @param time number The percentage between 0 and 1 to interpolate to.
-- @returns Vector3|number The interpolated point.
local function linear_interpolate(a, b, time)
return ((b - a) * time) + a
end
--- Chaikin curve smoothing implementation.
-- This algorithm works by taking a list of points that define a segmented line,
-- and then interpolating between them to smooth the line.
-- @source Ported from https://git.starbeamrainbowlabs.com/sbrl-GitLab/Chaikin-Generator/src/branch/master/ChaikinGenerator/ChaikinCurve.cs
-- @param arr_pos Vector3[] A list of points to interpolate.
-- @param steps number The number of interpolatioon passes to do.
-- @returns Vector3[] A (longer) list of interpolated points.
local function chaikin(arr_pos, steps)
local result = wea_c.table.shallowcopy(arr_pos)
for pass = 1, steps do
local pos_start = result[1]
local pos_end = result[#result]
for i = 1,#result-1,2 do
result[i] = linear_interpolate(result[i], result[i+1], 0.25)
table.insert(result, i+1, linear_interpolate(result[i], result[i+2]))
end
-- table.remove(result, #result-1) -- In the original, but I don't know why
-- Keep the starting & ending positions the same
result[1] = pos_start
result[#result] = pos_end
end
return result
end
return {
chaikin = chaikin,
linear_interpolate = linear_interpolate
}