mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-23 15:43:50 +01:00
implement initial //spline, but it isn't finished yet
This commit is contained in:
parent
a768378dfd
commit
258a9c1cde
80
worldeditadditions/lib/spline.lua
Normal file
80
worldeditadditions/lib/spline.lua
Normal file
@ -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
|
91
worldeditadditions_commands/commands/spline.lua
Normal file
91
worldeditadditions_commands/commands/spline.lua
Normal file
@ -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/count.lua")
|
||||
dofile(wea_cmd.modpath.."/commands/sculpt.lua")
|
||||
dofile(wea_cmd.modpath.."/commands/spline.lua")
|
||||
|
||||
-- Meta Commands
|
||||
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")
|
||||
|
||||
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/format/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.register_command = dofile(modpath.."/core/register_command.lua")
|
||||
wea_c.fetch_command_def = dofile(modpath.."/core/fetch_command_def.lua")
|
||||
|
43
worldeditadditions_core/utils/chaikin.lua
Normal file
43
worldeditadditions_core/utils/chaikin.lua
Normal file
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user