mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-27 17:43:53 +01:00
Start implementing a //sculpt command, but it's not finished or tested yet.
First up: test that our initial basic dynamic brushes work as intended with the //sculptlist [preview] command. Also on the todo list: document it in the chat command reference!
This commit is contained in:
parent
c030acfd7e
commit
10c9d6f886
@ -57,6 +57,7 @@ dofile(wea.modpath.."/lib/spiral_circle.lua")
|
||||
dofile(wea.modpath.."/lib/conv/conv.lua")
|
||||
dofile(wea.modpath.."/lib/erode/erode.lua")
|
||||
dofile(wea.modpath.."/lib/noise/init.lua")
|
||||
dofile(wea.modpath.."/lib/sculpt/init.lua")
|
||||
|
||||
dofile(wea.modpath.."/lib/copy.lua")
|
||||
dofile(wea.modpath.."/lib/move.lua")
|
||||
|
54
worldeditadditions/lib/sculpt/apply.lua
Normal file
54
worldeditadditions/lib/sculpt/apply.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
--- Applies the given brush at the given x/z position to the given heightmap.
|
||||
-- Important: Where a Vector3 is mentioned in the parameter list, it reall MUST
|
||||
-- be a Vector3 instance.
|
||||
-- Also important: Remember that the position there is RELATIVE TO THE HEIGHTMAP'S origin (0, 0) and is on the X and Z axes!
|
||||
-- @param brush table The ZERO-indexed brush to apply. Values should be normalised to be between 0 and 1.
|
||||
-- @param brush_size Vector3 The size of the brush on the x/y axes.
|
||||
-- @pram height number The multiplier to apply to each brush pixel value just before applying it. Negative values are allowed - this will cause a subtraction operation instead of an addition.
|
||||
-- @param position Vector3 The position RELATIVE TO THE HEIGHTMAP on the x/z coordinates to centre the brush application on.
|
||||
-- @param heightmap table The heightmap to apply the brush to. See worldeditadditions.make_heightmap for how to obtain one of these.
|
||||
-- @param heightmap_size Vector3 The size of the aforementioned heightmap. See worldeditadditions.make_heightmap for more information.
|
||||
-- @returns true,number,number|false,string If the operation was not successful, then false followed by an error message as a string is returned. If it was successful however, 3 values are returned: true, then the number of nodes added, then the number of nodes removed.
|
||||
local function apply(brush, brush_size, height, position, heightmap, heightmap_size)
|
||||
-- Convert brush_size to match the scheme used in the heightmap
|
||||
brush_size = brush_size:clone()
|
||||
brush_size.z = brush_size.y
|
||||
brush_size.y = 0
|
||||
|
||||
local brush_radius = (brush_size/2):ceil() - 1
|
||||
local pos_start = (position - brush_radius)
|
||||
:clamp(Vector3.new(0, 0, 0), heightmap_size)
|
||||
local pos_end = (pos_start + brush_size)
|
||||
:clamp(Vector3.new(0, 0, 0), heightmap_size)
|
||||
|
||||
local added = 0
|
||||
local removed = 0
|
||||
|
||||
-- Iterate over the heightmap and apply the brush
|
||||
-- Note that we do not iterate over the brush, because we don't know if the
|
||||
-- brush actually fits inside the region.... O.o
|
||||
for z = pos_end, pos_start, -1 do
|
||||
for x = pos_end, pos_start, -1 do
|
||||
local hi = z*heightmap_size.x + x
|
||||
local pos_brush = Vector3.new(x, 0, z) - pos_start
|
||||
local bi = pos_brush.z*brush_size.x + pos_brush.x
|
||||
|
||||
local adjustment = math.floor(brush[bi]*height)
|
||||
if adjustment > 0 then
|
||||
added = added + adjustment
|
||||
elseif adjustment < 0 then
|
||||
removed = removed + math.abs(adjustment)
|
||||
end
|
||||
|
||||
heightmap[hi] = heightmap[hi] + adjustment
|
||||
end
|
||||
end
|
||||
|
||||
return true, added, removed
|
||||
end
|
||||
|
||||
|
||||
return apply
|
13
worldeditadditions/lib/sculpt/brushes/__smooth.lua
Normal file
13
worldeditadditions/lib/sculpt/brushes/__smooth.lua
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
--- Returns a smooth gaussian brush.
|
||||
-- @param size Vector3 The target size of the brush. Note that the actual size fo the brush will be different, as the gaussian function has some limitations.
|
||||
-- @param sigma=2 number The 'smoothness' of the brush. Higher values are more smooth.
|
||||
return function(size, sigma)
|
||||
local size = math.min(size.x, size.y)
|
||||
if size % 2 == 0 then size = size - 1 end
|
||||
if size < 1 then
|
||||
return false, "Error: Invalid brush size."
|
||||
end
|
||||
local success, gaussian = worldeditadditions.conv.kernel_gaussian(size, sigma)
|
||||
return success, gaussian, { x = size, y = size }
|
||||
end
|
8
worldeditadditions/lib/sculpt/brushes/default.lua
Normal file
8
worldeditadditions/lib/sculpt/brushes/default.lua
Normal file
@ -0,0 +1,8 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua")
|
||||
|
||||
return function(size)
|
||||
local success, brush, size_actual = __smooth(size, 2)
|
||||
return success, brush, size_actual
|
||||
end
|
8
worldeditadditions/lib/sculpt/brushes/default_hard.lua
Normal file
8
worldeditadditions/lib/sculpt/brushes/default_hard.lua
Normal file
@ -0,0 +1,8 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua")
|
||||
|
||||
return function(size)
|
||||
local success, brush, size_actual = __smooth(size, 1)
|
||||
return success, brush, size_actual
|
||||
end
|
8
worldeditadditions/lib/sculpt/brushes/default_soft.lua
Normal file
8
worldeditadditions/lib/sculpt/brushes/default_soft.lua
Normal file
@ -0,0 +1,8 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua")
|
||||
|
||||
return function(size)
|
||||
local success, brush, size_actual = __smooth(size, 5)
|
||||
return success, brush, size_actual
|
||||
end
|
11
worldeditadditions/lib/sculpt/brushes/square.lua
Normal file
11
worldeditadditions/lib/sculpt/brushes/square.lua
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
--- Returns a simple square brush with 100% weight for every pixel.
|
||||
return function(size)
|
||||
local result = {}
|
||||
for y=0, size.y do
|
||||
for x=0, size.x do
|
||||
result[y*size.x + x] = 1
|
||||
end
|
||||
end
|
||||
return true, result, size
|
||||
end
|
20
worldeditadditions/lib/sculpt/init.lua
Normal file
20
worldeditadditions/lib/sculpt/init.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
local sculpt = {
|
||||
brushes = {
|
||||
default_hard = dofile(wea.modpath.."/lib/sculpt/brushes/default_hard.lua"),
|
||||
default = dofile(wea.modpath.."/lib/sculpt/brushes/default.lua"),
|
||||
default_soft = dofile(wea.modpath.."/lib/sculpt/brushes/default_soft.lua"),
|
||||
square = dofile(wea.modpath.."/lib/sculpt/brushes/square.lua")
|
||||
},
|
||||
make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"),
|
||||
preview_brush = dofile(wea.modpath.."/lib/sculpt/preview_brush.lua"),
|
||||
read_brush_static = dofile(wea.modpath.."/lib/sculpt/read_brush_static.lua"),
|
||||
apply = dofile(wea.modpath.."/lib/sculpt/apply.lua")
|
||||
}
|
||||
|
||||
-- TODO: Automatically find & register all text file based brushes in the brushes directory
|
||||
|
||||
-- TODO: Implement automatic scaling of static brushes to the correct size. We have ..scale already, but we probably need to implement a proper 2d canvas scaling algorithm. Some options to consider: linear < [bi]cubic < nohalo/lohalo
|
||||
|
||||
-- Note that we do NOT automatically find & register computed brushes because that's an easy way to execute arbitrary Lua code & cause a security issue unless handled very carefully
|
24
worldeditadditions/lib/sculpt/make_brush.lua
Normal file
24
worldeditadditions/lib/sculpt/make_brush.lua
Normal file
@ -0,0 +1,24 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
--- Makes a sculpting brush that is as close to a target size as possible.
|
||||
-- @param brush_name string The name of the brush to create.
|
||||
-- @param target_size Vector3 The target size of the brush to create.
|
||||
-- @returns true,table,Vector3|false,string If the operation was successful, true followed by the brush in a 1D ZERO-indexed table followed by the actual size of the brush as a Vector3 (x & y components used only). If the operation was not successful, false and an error message string is returned instead.
|
||||
local function make_brush(brush_name, target_size)
|
||||
if not wea.sculpt.brushes[brush_name] then return false, "Error: That brush does not exist. Try using //sculptbrushes to list all available sculpting brushes." end
|
||||
|
||||
local brush_def = wea.sculpt.brushes[brush_name]
|
||||
|
||||
local success, brush, size_actual
|
||||
if type(brush_def) == "function" then
|
||||
success, brush, size_actual = brush_def(target_size)
|
||||
if not success then return success, brush end
|
||||
else
|
||||
brush = brush_def.brush
|
||||
size_actual = brush_def.size
|
||||
end
|
||||
|
||||
return true, brush, size_actual
|
||||
end
|
||||
|
||||
return make_brush
|
46
worldeditadditions/lib/sculpt/preview_brush.lua
Normal file
46
worldeditadditions/lib/sculpt/preview_brush.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua")
|
||||
|
||||
--- Generates a textual preview of a given brush.
|
||||
-- @param brush_name string The name of the brush to create a preview for.
|
||||
-- @param target_size Vector3 The target size of the brush to create. Default: (10, 10, 0).
|
||||
-- @returns bool,string If the operation was successful, true followed by a preview of the brush as a string. If the operation was not successful, false and an error message string is returned instead.
|
||||
local function preview_brush(brush_name, target_size)
|
||||
if not target_size then target_size = Vector3.new(10, 10, 0) end
|
||||
local success, brush, brush_size = make_brush(brush_name, target_size)
|
||||
|
||||
-- Values to map brush pixel values to.
|
||||
-- Brush pixel values are first multiplied by 10 before comparing to these numbers
|
||||
local values = {}
|
||||
values["@"] = 9.5
|
||||
values["#"] = 8
|
||||
values["="] = 6
|
||||
values[":"] = 5
|
||||
values["-"] = 4
|
||||
values["."] = 1
|
||||
values[" "] = 0
|
||||
|
||||
local result = {}
|
||||
for z = target_size.z, 0, -1 do
|
||||
local row = {}
|
||||
for x = target_size.x, 0, -1 do
|
||||
local i = z*brush_size.x + x
|
||||
local pixel = " "
|
||||
local threshold_cur = -1
|
||||
for value,threshold in pairs(values) do
|
||||
if brush[i] > threshold and threshold_cur < threshold then
|
||||
pixel = value
|
||||
threshold_cur = threshold
|
||||
end
|
||||
end
|
||||
table.insert(row, pixel)
|
||||
end
|
||||
table.insert(result, table.concat(row))
|
||||
end
|
||||
|
||||
return true, table.concat(result)
|
||||
end
|
||||
|
||||
return preview_brush
|
11
worldeditadditions/lib/sculpt/read_brush_static.lua
Normal file
11
worldeditadditions/lib/sculpt/read_brush_static.lua
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
return function(filepath)
|
||||
local brush_size = { x = 0, y = 0 }
|
||||
local brush = { }
|
||||
|
||||
-- TODO: Import brush here
|
||||
|
||||
return false, "Error: Not implemented yet"
|
||||
|
||||
-- return true, brush, brush_size
|
||||
end
|
54
worldeditadditions_commands/commands/extra/sculptlist.lua
Normal file
54
worldeditadditions_commands/commands/extra/sculptlist.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
-- ███████ ██████ ██ ██ ██ ██████ ████████ ██ ██ ███████ ████████
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██ ██ ██ ██ ██████ ██ ██ ██ ███████ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██████ ██████ ███████ ██ ██ ███████ ██ ███████ ██
|
||||
minetest.register_chatcommand("/sculptlist", {
|
||||
params = "[preview]",
|
||||
description = "Lists all the currently registered sculpting brushes and their associated metadata. If the keyword preview is specified as an argument, a preview of each brush is also printed.",
|
||||
privs = { worldedit = true },
|
||||
func = function(name, params_text)
|
||||
if name == nil then return end
|
||||
if not params_text then params_text = "" end
|
||||
params_text = wea.trim(params_text)
|
||||
|
||||
local msg = {}
|
||||
|
||||
table.insert(msg, "Currently registered sculpting brushes:\n")
|
||||
|
||||
if params_text == "preview" then
|
||||
for brush_name, brush_def in pairs(wea.sculpt.brushes) do
|
||||
local preview = wea.sculpt.preview_brush(brush_name)
|
||||
|
||||
local brush_size = "dynamic"
|
||||
if type(brush_size) ~= "function" then
|
||||
brush_size = brush_def.size
|
||||
end
|
||||
|
||||
table.insert(msg, brush_name.." ["..brush_size.."]:\n")
|
||||
table.insert(msg, preview.."\n\n")
|
||||
end
|
||||
else
|
||||
local display = { { "Name", "Native Size" } }
|
||||
for brush_name, brush_def in pairs(wea.sculpt.brushes) do
|
||||
local brush_size = "dynamic"
|
||||
if type(brush_size) ~= "function" then
|
||||
brush_size = brush_def.size
|
||||
end
|
||||
|
||||
table.insert(display, {
|
||||
brush_name,
|
||||
brush_size
|
||||
})
|
||||
end
|
||||
-- Sort by brush name
|
||||
table.sort(display, function(a, b) return a[1] < b[1] end)
|
||||
|
||||
table.insert(msg, worldeditadditions.format.make_ascii_table(display))
|
||||
end
|
||||
|
||||
worldedit.player_notify(name, table.concat(msg))
|
||||
end
|
||||
})
|
@ -55,8 +55,9 @@ dofile(we_c.modpath.."/commands/wireframe/init.lua")
|
||||
|
||||
dofile(we_c.modpath.."/commands/extra/saplingaliases.lua")
|
||||
dofile(we_c.modpath.."/commands/extra/basename.lua")
|
||||
dofile(we_c.modpath.."/commands/extra/sculptlist.lua")
|
||||
|
||||
-- Don't registry the //bonemeal command if the bonemeal mod isn't present
|
||||
-- Don't register the //bonemeal command if the bonemeal mod isn't present
|
||||
if minetest.global_exists("bonemeal") then
|
||||
dofile(we_c.modpath.."/commands/bonemeal.lua")
|
||||
dofile(we_c.modpath.."/commands/forest.lua")
|
||||
@ -94,6 +95,7 @@ worldedit.alias_command("mfacing", "mface")
|
||||
-- These are commented out for now, as they could be potentially dangerous to stability
|
||||
-- Thorough testing is required of our replacement commands before these are uncommented
|
||||
-- TODO: Depend on worldeditadditions_core before uncommenting this
|
||||
-- BUG: //move+ seems to be leaving stuff behind for some strange reason --@sbrl 2021-12-26
|
||||
-- worldeditadditions_core.alias_override("copy", "copy+")
|
||||
-- worldeditadditions_core.alias_override("move", "move+") -- MAY have issues where it doesn't overwrite the old region properly, but haven't been able to reliably reproduce this
|
||||
-- worldeditadditions_core.alias_override("replace", "replacemix")
|
||||
|
Loading…
Reference in New Issue
Block a user