From 50840ad5b99ee844bea40a6f4dc8a6921b359cbc Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 29 Nov 2021 23:20:40 +0000 Subject: [PATCH 01/79] Add BNF for axes parsing --- .docs/grammars/axes.bnf | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .docs/grammars/axes.bnf diff --git a/.docs/grammars/axes.bnf b/.docs/grammars/axes.bnf new file mode 100644 index 0000000..5150c21 --- /dev/null +++ b/.docs/grammars/axes.bnf @@ -0,0 +1,27 @@ +{% + +Examples: + +``` +front 3 left 10 y 77 x 30 back 99 +``` + +%} + + ::= * + + ::= + + ::= + | + + ::= + | "-" + + ::= front | back | left | right | up | down | "?" + + ::= x | y | z + + ::= * + + ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 From 2d8dcc7ebce15bbe93d3c9bb58e14e94e31d226c Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 29 Nov 2021 23:24:10 +0000 Subject: [PATCH 02/79] grammrs/axes: add h / v for horizontal / vertical --- .docs/grammars/axes.bnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/grammars/axes.bnf b/.docs/grammars/axes.bnf index 5150c21..6f5ae77 100644 --- a/.docs/grammars/axes.bnf +++ b/.docs/grammars/axes.bnf @@ -18,7 +18,7 @@ front 3 left 10 y 77 x 30 back 99 ::= | "-" - ::= front | back | left | right | up | down | "?" + ::= front | back | left | right | up | down | h | v | "?" ::= x | y | z From a7412a2c2f704050a0aef1067996b20791309adb Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 30 Nov 2021 19:10:45 +0000 Subject: [PATCH 03/79] bnf/axes: add reverse --- .docs/grammars/axes.bnf | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.docs/grammars/axes.bnf b/.docs/grammars/axes.bnf index 6f5ae77..11be87a 100644 --- a/.docs/grammars/axes.bnf +++ b/.docs/grammars/axes.bnf @@ -1,4 +1,7 @@ {% +# Lists of axes + +In various commands such as `//copy+`, `//move+`, and others lists of axes are used. These are all underpinned by a single grammar and a single parser (located in `worldeditadditions/utils/parse/axes.lua`). While the parser itself requires pre-split tokens (see `split_shell` for that), the grammar which it parses is documnted here. Examples: @@ -11,6 +14,14 @@ front 3 left 10 y 77 x 30 back 99 ::= * ::= + | + + ::= + | + + ::= sym | symmetrical | mirror | mir | rev | reverse | true + + ::= | @@ -22,6 +33,8 @@ front 3 left 10 y 77 x 30 back 99 ::= x | y | z + + ::= * ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 From caa06a5c4cd7cfe2e324da4b76675cf7ae124b93 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Wed, 1 Dec 2021 20:02:53 +0000 Subject: [PATCH 04/79] bnf/axes: make h and v absolute --- .docs/grammars/axes.bnf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docs/grammars/axes.bnf b/.docs/grammars/axes.bnf index 11be87a..a75764c 100644 --- a/.docs/grammars/axes.bnf +++ b/.docs/grammars/axes.bnf @@ -29,9 +29,9 @@ front 3 left 10 y 77 x 30 back 99 ::= | "-" - ::= front | back | left | right | up | down | h | v | "?" + ::= front | back | left | right | up | down | "?" - ::= x | y | z + ::= x | y | z | h | v From 01d6132055ffb7aa2cc670c2827fc7de7b51b81f Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Thu, 16 Dec 2021 21:18:55 +0000 Subject: [PATCH 05/79] docs/Reference: make filter box autofocus --- .docs/Reference.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docs/Reference.html b/.docs/Reference.html index 2cfefcc..48067f1 100644 --- a/.docs/Reference.html +++ b/.docs/Reference.html @@ -14,8 +14,8 @@
- - + +
From 502595579ec8658f077e8a684d627cef5bd03f40 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 26 Dec 2021 22:36:24 +0000 Subject: [PATCH 06/79] Try fixing #72 --- worldeditadditions/utils/bit.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/worldeditadditions/utils/bit.lua b/worldeditadditions/utils/bit.lua index e68f22e..6fb3a89 100644 --- a/worldeditadditions/utils/bit.lua +++ b/worldeditadditions/utils/bit.lua @@ -13,15 +13,11 @@ -- module: bit -local bit -if minetest.global_exists("bit") then - bit = bit -else - bit = {} - - bit.bits = 32 - bit.powtab = { 1 } - +if not minetest.global_exists("bit") then + bit = { + bits = 32, + powtab = { 1 } + } for b = 1, bit.bits - 1 do bit.powtab[#bit.powtab + 1] = math.pow(2, b) end From f7a5e223d4094bcc3e6c3040622fe6eee94a0101 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 26 Dec 2021 22:40:00 +0000 Subject: [PATCH 07/79] really fix #72 --- worldeditadditions/utils/bit.lua | 146 ++++++++++++++++--------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/worldeditadditions/utils/bit.lua b/worldeditadditions/utils/bit.lua index 6fb3a89..aac0427 100644 --- a/worldeditadditions/utils/bit.lua +++ b/worldeditadditions/utils/bit.lua @@ -13,23 +13,27 @@ -- module: bit -if not minetest.global_exists("bit") then - bit = { +local bit_local + +if minetest.global_exists("bit") then + bit_local = bit +else + bit_local = { bits = 32, powtab = { 1 } } - for b = 1, bit.bits - 1 do - bit.powtab[#bit.powtab + 1] = math.pow(2, b) + for b = 1, bit_local.bits - 1 do + bit_local.powtab[#bit_local.powtab + 1] = math.pow(2, b) end end -- Functions --- bit.band -if not bit.band then - bit.band = function(a, b) +-- bit_local.band +if not bit_local.band then + bit_local.band = function(a, b) local result = 0 - for x = 1, bit.bits do + for x = 1, bit_local.bits do result = result + result if (a < 0) then if (b < 0) then @@ -43,11 +47,11 @@ if not bit.band then end end --- bit.bor -if not bit.bor then - bit.bor = function(a, b) +-- bit_local.bor +if not bit_local.bor then + bit_local.bor = function(a, b) local result = 0 - for x = 1, bit.bits do + for x = 1, bit_local.bits do result = result + result if (a < 0) then result = result + 1 @@ -61,47 +65,47 @@ if not bit.bor then end end --- bit.bnot -if not bit.bnot then - bit.bnot = function(x) - return bit.bxor(x, math.pow((bit.bits or math.floor(math.log(x, 2))), 2) - 1) +-- bit_local.bnot +if not bit_local.bnot then + bit_local.bnot = function(x) + return bit_local.bxor(x, math.pow((bit_local.bits or math.floor(math.log(x, 2))), 2) - 1) end end --- bit.lshift -if not bit.lshift then - bit.lshift = function(a, n) - if (n > bit.bits) then +-- bit_local.lshift +if not bit_local.lshift then + bit_local.lshift = function(a, n) + if (n > bit_local.bits) then a = 0 else - a = a * bit.powtab[n] + a = a * bit_local.powtab[n] end return a end end --- bit.rshift -if not bit.rshift then - bit.rshift = function(a, n) - if (n > bit.bits) then +-- bit_local.rshift +if not bit_local.rshift then + bit_local.rshift = function(a, n) + if (n > bit_local.bits) then a = 0 elseif (n > 0) then if (a < 0) then - a = a - bit.powtab[#bit.powtab] - a = a / bit.powtab[n] - a = a + bit.powtab[bit.bits - n] + a = a - bit_local.powtab[#bit_local.powtab] + a = a / bit_local.powtab[n] + a = a + bit_local.powtab[bit_local.bits - n] else - a = a / bit.powtab[n] + a = a / bit_local.powtab[n] end end return a end end --- bit.arshift -if not bit.arshift then - bit.arshift = function(a, n) - if (n >= bit.bits) then +-- bit_local.arshift +if not bit_local.arshift then + bit_local.arshift = function(a, n) + if (n >= bit_local.bits) then if (a < 0) then a = -1 else @@ -109,22 +113,22 @@ if not bit.arshift then end elseif (n > 0) then if (a < 0) then - a = a - bit.powtab[#bit.powtab] - a = a / bit.powtab[n] - a = a - bit.powtab[bit.bits - n] + a = a - bit_local.powtab[#bit_local.powtab] + a = a / bit_local.powtab[n] + a = a - bit_local.powtab[bit_local.bits - n] else - a = a / bit.powtab[n] + a = a / bit_local.powtab[n] end end return a end end --- bit.bxor -if not bit.bxor then - bit.bxor = function(a, b) +-- bit_local.bxor +if not bit_local.bxor then + bit_local.bxor = function(a, b) local result = 0 - for x = 1, bit.bits, 1 do + for x = 1, bit_local.bits, 1 do result = result + result if (a < 0) then if (b >= 0) then @@ -140,40 +144,40 @@ if not bit.bxor then end end --- bit.rol -if not bit.rol then - bit.rol = function(a, b) - local bits = bit.band(b, bit.bits - 1) - a = bit.band(a, 0xffffffff) - a = bit.bor(bit.lshift(a, b), bit.rshift(a, ((bit.bits - 1) - b))) - return bit.band(n, 0xffffffff) +-- bit_local.rol +if not bit_local.rol then + bit_local.rol = function(a, b) + local bits = bit_local.band(b, bit_local.bits - 1) + a = bit_local.band(a, 0xffffffff) + a = bit_local.bor(bit_local.lshift(a, b), bit_local.rshift(a, ((bit_local.bits - 1) - b))) + return bit_local.band(n, 0xffffffff) end end --- bit.ror -if not bit.ror then - bit.ror = function(a, b) - return bit.rol(a, - b) +-- bit_local.ror +if not bit_local.ror then + bit_local.ror = function(a, b) + return bit_local.rol(a, - b) end end --- bit.bswap -if not bit.bswap then - bit.bswap = function(n) - local a = bit.band(n, 0xff) - n = bit.rshift(n, 8) - local b = bit.band(n, 0xff) - n = bit.rshift(n, 8) - local c = bit.band(n, 0xff) - n = bit.rshift(n, 8) - local d = bit.band(n, 0xff) - return bit.lshift(bit.lshift(bit.lshift(a, 8) + b, 8) + c, 8) + d +-- bit_local.bswap +if not bit_local.bswap then + bit_local.bswap = function(n) + local a = bit_local.band(n, 0xff) + n = bit_local.rshift(n, 8) + local b = bit_local.band(n, 0xff) + n = bit_local.rshift(n, 8) + local c = bit_local.band(n, 0xff) + n = bit_local.rshift(n, 8) + local d = bit_local.band(n, 0xff) + return bit_local.lshift(bit_local.lshift(bit_local.lshift(a, 8) + b, 8) + c, 8) + d end end --- bit.tobit -if not bit.tobit then - bit.tobit = function(n) +-- bit_local.tobit +if not bit_local.tobit then + bit_local.tobit = function(n) local MOD = 2^32 n = n % MOD if (n >= 0x80000000) then @@ -183,9 +187,9 @@ if not bit.tobit then end end --- bit.tohex -if not bit.tohex then - bit.tohex = function(x, n) +-- bit_local.tohex +if not bit_local.tohex then + bit_local.tohex = function(x, n) n = n or 8 local up if n <= 0 then @@ -195,9 +199,9 @@ if not bit.tohex then up = true n = -n end - x = bit.band(x, 16^n - 1) + x = bit_local.band(x, 16^n - 1) return ('%0'..n..(up and 'X' or 'x')):format(x) end end -return bit +return bit_local From b962ace8a859af19faed0992cd9f86c93c43c5ce Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 26 Dec 2021 22:45:37 +0000 Subject: [PATCH 08/79] //maze, //maze3d: Fix crash if no arguments are specified --- CHANGELOG.md | 1 + worldeditadditions_commands/commands/maze.lua | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3575fe0..214881a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Note to self: See the bottom of this file for the release template text. - Cloud wand: Improve chat message text - Fix `bonemeal` mod detection to look for the global `bonemeal`, not whether the `bonemeal` mod name has been loaded - `//walls`: Prevent crash if not parameters are specified by defaulting to `dirt` as the replace_node + - `//maze`, `//maze3d`: Fix crash if no arguments are specified ## v1.12: The selection tools update (26th June 2021) diff --git a/worldeditadditions_commands/commands/maze.lua b/worldeditadditions_commands/commands/maze.lua index a02c7de..0d84466 100644 --- a/worldeditadditions_commands/commands/maze.lua +++ b/worldeditadditions_commands/commands/maze.lua @@ -1,7 +1,10 @@ +local wea = worldeditadditions local we_c = worldeditadditions_commands local function parse_params_maze(params_text, is_3d) - if not params_text then + if not params_text then params_text = "" end + params_text = wea.trim(params_text) + if #params_text == 0 then return false, "No arguments specified" end From af0e54cfdf9ac9e6ea21f9c984742bc7f0b10d10 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 26 Dec 2021 22:48:37 +0000 Subject: [PATCH 09/79] //maze: update to use Vector3 --- worldeditadditions_commands/commands/maze.lua | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/worldeditadditions_commands/commands/maze.lua b/worldeditadditions_commands/commands/maze.lua index 0d84466..88c5b7b 100644 --- a/worldeditadditions_commands/commands/maze.lua +++ b/worldeditadditions_commands/commands/maze.lua @@ -1,5 +1,6 @@ local wea = worldeditadditions local we_c = worldeditadditions_commands +local Vector3 = worldeditadditions.Vector3 local function parse_params_maze(params_text, is_3d) if not params_text then params_text = "" end @@ -74,12 +75,20 @@ worldedit.register_command("maze", { return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) end, func = function(name, replace_node, seed, path_length, path_width) - local start_time = worldeditadditions.get_ms_time() - local replaced = worldeditadditions.maze2d(worldedit.pos1[name], worldedit.pos2[name], replace_node, seed, path_length, path_width) - local time_taken = worldeditadditions.get_ms_time() - start_time + local start_time = wea.get_ms_time() - minetest.log("action", name .. " used //maze at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. " - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", replacing " .. replaced .. " nodes in " .. time_taken .. "s") - return true, replaced .. " nodes replaced in " .. worldeditadditions.format.human_time(time_taken) + local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) + local replaced = wea.maze2d( + pos1, pos2, + replace_node, + seed, + path_length, path_width + ) + + local time_taken = wea.get_ms_time() - start_time + + minetest.log("action", name.." used //maze at "..pos1.." - "..pos2..", replacing "..replaced.." nodes in "..time_taken.."s") + return true, replaced.." nodes replaced in "..wea.format.human_time(time_taken) end }) @@ -109,7 +118,7 @@ worldedit.register_command("maze3d", { local time_taken = worldeditadditions.get_ms_time() - start_time - minetest.log("action", name .. " used //maze3d at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. " - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", replacing " .. replaced .. " nodes in " .. time_taken .. "s") - return true, replaced .. " nodes replaced in " .. worldeditadditions.format.human_time(time_taken) + minetest.log("action", name.." used //maze3d at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", replacing "..replaced.." nodes in "..time_taken.."s") + return true, replaced.." nodes replaced in "..worldeditadditions.format.human_time(time_taken) end }) From 6e89fc868f119b23cff395fc61c9cbc9ba092fc4 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 26 Dec 2021 22:57:47 +0000 Subject: [PATCH 10/79] //maze: fix generated maze not reaching to the very edge of the defined region Fixes #60. --- CHANGELOG.md | 4 +++- worldeditadditions/lib/maze2d.lua | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 214881a..1413700 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,9 @@ Note to self: See the bottom of this file for the release template text. - Cloud wand: Improve chat message text - Fix `bonemeal` mod detection to look for the global `bonemeal`, not whether the `bonemeal` mod name has been loaded - `//walls`: Prevent crash if not parameters are specified by defaulting to `dirt` as the replace_node - - `//maze`, `//maze3d`: Fix crash if no arguments are specified + - `//maze`, `//maze3d`: + - Fix generated maze not reaching the very edge of the defined region + - Fix crash if no arguments are specified ## v1.12: The selection tools update (26th June 2021) diff --git a/worldeditadditions/lib/maze2d.lua b/worldeditadditions/lib/maze2d.lua index 8fc95ea..cf4dab8 100644 --- a/worldeditadditions/lib/maze2d.lua +++ b/worldeditadditions/lib/maze2d.lua @@ -27,7 +27,7 @@ local function generate_maze(seed, width, height, path_length, path_width) width = width - 1 height = height - 1 - + local world = {} for y = 0, height, 1 do world[y] = {} @@ -48,13 +48,13 @@ local function generate_maze(seed, width, height, path_length, path_width) if cy - path_length > 0 and world[cy - path_length][cx] == "#" then directions = directions .. "u" end - if cy + path_length < height-path_width and world[cy + path_length][cx] == "#" then + if cy + path_length < height-path_width+1 and world[cy + path_length][cx] == "#" then directions = directions .. "d" end if cx - path_length > 0 and world[cy][cx - path_length] == "#" then directions = directions .. "l" end - if cx + path_length < width-path_width and world[cy][cx + path_length] == "#" then + if cx + path_length < width-path_width+1 and world[cy][cx + path_length] == "#" then directions = directions .. "r" end @@ -148,7 +148,7 @@ function worldeditadditions.maze2d(pos1, pos2, target_node, seed, path_length, p -- print("path_width: "..path_width..", path_length: "..path_length) local maze = generate_maze(seed, extent.z, extent.x, path_length, path_width) -- x & need to be the opposite way around to how we index it - -- printspace(maze, extent.z, extent.x) + printspace(maze, extent.z, extent.x) -- z y x is the preferred loop order, but that isn't really possible here From eab498aac2beb3cbeaab16245cecf777eb73e759 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 27 Dec 2021 01:58:01 +0000 Subject: [PATCH 11/79] comment debug logging --- worldeditadditions/lib/maze2d.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions/lib/maze2d.lua b/worldeditadditions/lib/maze2d.lua index cf4dab8..85a413e 100644 --- a/worldeditadditions/lib/maze2d.lua +++ b/worldeditadditions/lib/maze2d.lua @@ -148,7 +148,7 @@ function worldeditadditions.maze2d(pos1, pos2, target_node, seed, path_length, p -- print("path_width: "..path_width..", path_length: "..path_length) local maze = generate_maze(seed, extent.z, extent.x, path_length, path_width) -- x & need to be the opposite way around to how we index it - printspace(maze, extent.z, extent.x) + -- printspace(maze, extent.z, extent.x) -- z y x is the preferred loop order, but that isn't really possible here From c030acfd7e286d0e35ffd9e2414e305bba6e22ed Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 27 Dec 2021 02:18:56 +0000 Subject: [PATCH 12/79] Vector3: add new .clamp() function --- .tests/Vector3/clamp.test.lua | 100 +++++++++++++++++++++++++++ worldeditadditions/utils/vector3.lua | 11 +++ 2 files changed, 111 insertions(+) create mode 100644 .tests/Vector3/clamp.test.lua diff --git a/.tests/Vector3/clamp.test.lua b/.tests/Vector3/clamp.test.lua new file mode 100644 index 0000000..a866e76 --- /dev/null +++ b/.tests/Vector3/clamp.test.lua @@ -0,0 +1,100 @@ +local Vector3 = require("worldeditadditions.utils.vector3") + +describe("Vector3.clamp", function() + it("should work by not changing values if it's already clamped", function() + local a = Vector3.new(5, 6, 7) + local pos1 = Vector3.new(0, 0, 0) + local pos2 = Vector3.new(10, 10, 10) + + local result = Vector3.clamp(a, pos1, pos2) + + assert.are.same( + Vector3.new(5, 6, 7), + result + ) + + result.z = 999 + + assert.are.same(Vector3.new(5, 6, 7), a) + assert.are.same(Vector3.new(0, 0, 0), pos1) + assert.are.same(Vector3.new(10, 10, 10), pos2) + end) + it("should work with positive vectors", function() + local a = Vector3.new(30, 3, 3) + local pos1 = Vector3.new(0, 0, 0) + local pos2 = Vector3.new(10, 10, 10) + + assert.are.same( + Vector3.new(10, 3, 3), + Vector3.clamp(a, pos1, pos2) + ) + end) + it("should work with multiple positive vectors", function() + local a = Vector3.new(30, 99, 88) + local pos1 = Vector3.new(4, 4, 4) + local pos2 = Vector3.new(13, 13, 13) + + assert.are.same( + Vector3.new(13, 13, 13), + Vector3.clamp(a, pos1, pos2) + ) + end) + it("should work with multiple positive vectors with an irregular defined region", function() + local a = Vector3.new(30, 99, 88) + local pos1 = Vector3.new(1, 5, 3) + local pos2 = Vector3.new(10, 15, 8) + + assert.are.same( + Vector3.new(10, 15, 8), + Vector3.clamp(a, pos1, pos2) + ) + end) + it("should work with multiple negative vectors", function() + local a = Vector3.new(-30, -99, -88) + local pos1 = Vector3.new(4, 4, 4) + local pos2 = Vector3.new(13, 13, 13) + + assert.are.same( + Vector3.new(4, 4, 4), + Vector3.clamp(a, pos1, pos2) + ) + end) + it("should work with multiple negative vectors with an irregular defined region", function() + local a = Vector3.new(-30, -99, -88) + local pos1 = Vector3.new(1, 5, 3) + local pos2 = Vector3.new(10, 15, 8) + + assert.are.same( + Vector3.new(1, 5, 3), + Vector3.clamp(a, pos1, pos2) + ) + end) + it("should work with multiple negative vectors with an irregular defined region with mixed signs", function() + local a = Vector3.new(-30, -99, -88) + local pos1 = Vector3.new(-1, 5, 3) + local pos2 = Vector3.new(10, 15, 8) + + assert.are.same( + Vector3.new(-1, 5, 3), + Vector3.clamp(a, pos1, pos2) + ) + end) + + + + + it("should return new Vector3 instances", function() + local a = Vector3.new(30, 3, 3) + local pos1 = Vector3.new(0, 0, 0) + local pos2 = Vector3.new(10, 10, 10) + + local result = Vector3.clamp(a, pos1, pos2) + assert.are.same(Vector3.new(10, 3, 3), result) + + result.y = 999 + + assert.are.same(Vector3.new(30, 3, 3), a) + assert.are.same(Vector3.new(0, 0, 0), pos1) + assert.are.same(Vector3.new(10, 10, 10), pos2) + end) +end) diff --git a/worldeditadditions/utils/vector3.lua b/worldeditadditions/utils/vector3.lua index c1d1f85..8558453 100644 --- a/worldeditadditions/utils/vector3.lua +++ b/worldeditadditions/utils/vector3.lua @@ -295,6 +295,17 @@ function Vector3.is_contained(target, pos1, pos2) and pos2.z >= target.z end +--- Clamps the given point to fall within the region defined by 2 points. +-- @param a Vector3 The target vector to clamp. +-- @param pos1 Vector3 pos1 of the defined region. +-- @param pos2 Vector3 pos2 of the defined region. +-- @returns Vector3 The target vector, clamped to fall within the defined region. +function Vector3.clamp(a, pos1, pos2) + pos1, pos2 = Vector3.sort(pos1, pos2) + + return Vector3.min(Vector3.max(a, pos1), pos2) +end + --- Expands the defined region to include the given point. -- @param target Vector3 The target vector to include. From 10c9d6f886b9082ebd1665b7e563d8e68fff5fc8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 27 Dec 2021 03:11:52 +0000 Subject: [PATCH 13/79] 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! --- worldeditadditions/init.lua | 1 + worldeditadditions/lib/sculpt/apply.lua | 54 +++++++++++++++++++ .../lib/sculpt/brushes/__smooth.lua | 13 +++++ .../lib/sculpt/brushes/default.lua | 8 +++ .../lib/sculpt/brushes/default_hard.lua | 8 +++ .../lib/sculpt/brushes/default_soft.lua | 8 +++ .../lib/sculpt/brushes/square.lua | 11 ++++ worldeditadditions/lib/sculpt/init.lua | 20 +++++++ worldeditadditions/lib/sculpt/make_brush.lua | 24 +++++++++ .../lib/sculpt/preview_brush.lua | 46 ++++++++++++++++ .../lib/sculpt/read_brush_static.lua | 11 ++++ .../commands/extra/sculptlist.lua | 54 +++++++++++++++++++ worldeditadditions_commands/init.lua | 4 +- 13 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 worldeditadditions/lib/sculpt/apply.lua create mode 100644 worldeditadditions/lib/sculpt/brushes/__smooth.lua create mode 100644 worldeditadditions/lib/sculpt/brushes/default.lua create mode 100644 worldeditadditions/lib/sculpt/brushes/default_hard.lua create mode 100644 worldeditadditions/lib/sculpt/brushes/default_soft.lua create mode 100644 worldeditadditions/lib/sculpt/brushes/square.lua create mode 100644 worldeditadditions/lib/sculpt/init.lua create mode 100644 worldeditadditions/lib/sculpt/make_brush.lua create mode 100644 worldeditadditions/lib/sculpt/preview_brush.lua create mode 100644 worldeditadditions/lib/sculpt/read_brush_static.lua create mode 100644 worldeditadditions_commands/commands/extra/sculptlist.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index f52424c..378f610 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -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") diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua new file mode 100644 index 0000000..6323cb2 --- /dev/null +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/brushes/__smooth.lua b/worldeditadditions/lib/sculpt/brushes/__smooth.lua new file mode 100644 index 0000000..e8647a5 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/__smooth.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/brushes/default.lua b/worldeditadditions/lib/sculpt/brushes/default.lua new file mode 100644 index 0000000..eadf4c6 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/default.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/brushes/default_hard.lua b/worldeditadditions/lib/sculpt/brushes/default_hard.lua new file mode 100644 index 0000000..c4fd325 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/default_hard.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/brushes/default_soft.lua b/worldeditadditions/lib/sculpt/brushes/default_soft.lua new file mode 100644 index 0000000..228e5ec --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/default_soft.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/brushes/square.lua b/worldeditadditions/lib/sculpt/brushes/square.lua new file mode 100644 index 0000000..cd78045 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/square.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua new file mode 100644 index 0000000..723ca3a --- /dev/null +++ b/worldeditadditions/lib/sculpt/init.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/make_brush.lua b/worldeditadditions/lib/sculpt/make_brush.lua new file mode 100644 index 0000000..5dc6d1b --- /dev/null +++ b/worldeditadditions/lib/sculpt/make_brush.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/preview_brush.lua b/worldeditadditions/lib/sculpt/preview_brush.lua new file mode 100644 index 0000000..dd8f05d --- /dev/null +++ b/worldeditadditions/lib/sculpt/preview_brush.lua @@ -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 diff --git a/worldeditadditions/lib/sculpt/read_brush_static.lua b/worldeditadditions/lib/sculpt/read_brush_static.lua new file mode 100644 index 0000000..df87f8c --- /dev/null +++ b/worldeditadditions/lib/sculpt/read_brush_static.lua @@ -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 diff --git a/worldeditadditions_commands/commands/extra/sculptlist.lua b/worldeditadditions_commands/commands/extra/sculptlist.lua new file mode 100644 index 0000000..9317e99 --- /dev/null +++ b/worldeditadditions_commands/commands/extra/sculptlist.lua @@ -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 +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 033d3a3..b6b4244 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -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") From d657ce1abe3bd3aac4cd6d6ef82fc49fc5f4a903 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 27 Dec 2021 19:36:57 +0000 Subject: [PATCH 14/79] //sculptlist: fix bugs. It works! --- worldeditadditions/init.lua | 2 +- worldeditadditions/lib/sculpt/brushes/__smooth.lua | 11 ++++++++++- .../lib/sculpt/brushes/default_hard.lua | 2 +- .../lib/sculpt/brushes/default_soft.lua | 2 +- worldeditadditions/lib/sculpt/init.lua | 2 ++ worldeditadditions/lib/sculpt/preview_brush.lua | 14 ++++++++------ worldeditadditions/utils/numbers.lua | 7 +++++-- .../commands/extra/sculptlist.lua | 9 ++++++--- 8 files changed, 34 insertions(+), 15 deletions(-) diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 378f610..834883c 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -57,7 +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") +wea.sculpt = dofile(wea.modpath.."/lib/sculpt/init.lua") dofile(wea.modpath.."/lib/copy.lua") dofile(wea.modpath.."/lib/move.lua") diff --git a/worldeditadditions/lib/sculpt/brushes/__smooth.lua b/worldeditadditions/lib/sculpt/brushes/__smooth.lua index e8647a5..8654da6 100644 --- a/worldeditadditions/lib/sculpt/brushes/__smooth.lua +++ b/worldeditadditions/lib/sculpt/brushes/__smooth.lua @@ -1,6 +1,7 @@ +local wea = worldeditadditions --- 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 size Vector3 The target size of the brush. Note that the actual size of 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) @@ -9,5 +10,13 @@ return function(size, sigma) return false, "Error: Invalid brush size." end local success, gaussian = worldeditadditions.conv.kernel_gaussian(size, sigma) + + -- Normalise values to fill the range 0 - 1 + -- By default, wea.conv.kernel_gaussian values add up to 1 in total + local max = wea.max(gaussian) + for i=0,size*size-1 do + gaussian[i] = gaussian[i] / max + end + return success, gaussian, { x = size, y = size } end diff --git a/worldeditadditions/lib/sculpt/brushes/default_hard.lua b/worldeditadditions/lib/sculpt/brushes/default_hard.lua index c4fd325..33c1192 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_hard.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_hard.lua @@ -3,6 +3,6 @@ local wea = worldeditadditions local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) - local success, brush, size_actual = __smooth(size, 1) + local success, brush, size_actual = __smooth(size, 1.25) return success, brush, size_actual end diff --git a/worldeditadditions/lib/sculpt/brushes/default_soft.lua b/worldeditadditions/lib/sculpt/brushes/default_soft.lua index 228e5ec..3982ee9 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_soft.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_soft.lua @@ -3,6 +3,6 @@ local wea = worldeditadditions local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) - local success, brush, size_actual = __smooth(size, 5) + local success, brush, size_actual = __smooth(size, 3) return success, brush, size_actual end diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index 723ca3a..6dd75cd 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -13,6 +13,8 @@ local sculpt = { apply = dofile(wea.modpath.."/lib/sculpt/apply.lua") } +return sculpt + -- 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 diff --git a/worldeditadditions/lib/sculpt/preview_brush.lua b/worldeditadditions/lib/sculpt/preview_brush.lua index dd8f05d..c6da56f 100644 --- a/worldeditadditions/lib/sculpt/preview_brush.lua +++ b/worldeditadditions/lib/sculpt/preview_brush.lua @@ -22,15 +22,17 @@ local function preview_brush(brush_name, target_size) values["."] = 1 values[" "] = 0 + print(wea.inspect(brush)) + local result = {} - for z = target_size.z, 0, -1 do + for y = brush_size.y-1, 0, -1 do local row = {} - for x = target_size.x, 0, -1 do - local i = z*brush_size.x + x + for x = brush_size.x-1, 0, -1 do + local i = y*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 + if brush[i] * 10 > threshold and threshold_cur < threshold then pixel = value threshold_cur = threshold end @@ -39,8 +41,8 @@ local function preview_brush(brush_name, target_size) end table.insert(result, table.concat(row)) end - - return true, table.concat(result) + print("RESULT", wea.inspect(result)) + return true, table.concat(result, "\n") end return preview_brush diff --git a/worldeditadditions/utils/numbers.lua b/worldeditadditions/utils/numbers.lua index 0a6f159..f8fb37c 100644 --- a/worldeditadditions/utils/numbers.lua +++ b/worldeditadditions/utils/numbers.lua @@ -33,7 +33,7 @@ end function worldeditadditions.min(list) if #list == 0 then return nil end local min = nil - for i,value in ipairs(list) do + for i,value in pairs(list) do if min == nil or min > value then min = value end @@ -46,8 +46,11 @@ end -- @returns number The maximum value in the given list. function worldeditadditions.max(list) if #list == 0 then return nil end + -- We use pairs() instead of ipairs() here, because then we can support + -- zero-indexed 1D heightmaps too - and we don't care that the order is + -- undefined with pairs(). local max = nil - for i,value in ipairs(list) do + for i,value in pairs(list) do if max == nil or max < value then max = value end diff --git a/worldeditadditions_commands/commands/extra/sculptlist.lua b/worldeditadditions_commands/commands/extra/sculptlist.lua index 9317e99..8506db4 100644 --- a/worldeditadditions_commands/commands/extra/sculptlist.lua +++ b/worldeditadditions_commands/commands/extra/sculptlist.lua @@ -20,13 +20,16 @@ minetest.register_chatcommand("/sculptlist", { 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 success, preview = wea.sculpt.preview_brush(brush_name) local brush_size = "dynamic" - if type(brush_size) ~= "function" then + if type(brush_def) ~= "function" then brush_size = brush_def.size end + print("//sculptlist: preview for "..brush_name..":") + print(preview) + table.insert(msg, brush_name.." ["..brush_size.."]:\n") table.insert(msg, preview.."\n\n") end @@ -34,7 +37,7 @@ minetest.register_chatcommand("/sculptlist", { 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 + if type(brush_def) ~= "function" then brush_size = brush_def.size end From 58d3c1e43b3409eacc7ddd6d8ccde98d3d58bac9 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 27 Dec 2021 19:45:13 +0000 Subject: [PATCH 15/79] Add optional frame to brush previews This way, it's easiere to see where the edges are of the preview --- worldeditadditions/lib/sculpt/preview_brush.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/worldeditadditions/lib/sculpt/preview_brush.lua b/worldeditadditions/lib/sculpt/preview_brush.lua index c6da56f..ede291c 100644 --- a/worldeditadditions/lib/sculpt/preview_brush.lua +++ b/worldeditadditions/lib/sculpt/preview_brush.lua @@ -7,7 +7,8 @@ local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua") -- @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) +local function preview_brush(brush_name, target_size, framed) + if framed == nil then framed = true end if not target_size then target_size = Vector3.new(10, 10, 0) end local success, brush, brush_size = make_brush(brush_name, target_size) @@ -22,11 +23,14 @@ local function preview_brush(brush_name, target_size) values["."] = 1 values[" "] = 0 - print(wea.inspect(brush)) + local frame_vertical = "+"..string.rep("-", math.max(0, brush_size.x)).."+" local result = {} + if framed then table.insert(result, frame_vertical) end + for y = brush_size.y-1, 0, -1 do local row = {} + if framed then table.insert(row, "|") end for x = brush_size.x-1, 0, -1 do local i = y*brush_size.x + x local pixel = " " @@ -39,9 +43,13 @@ local function preview_brush(brush_name, target_size) end table.insert(row, pixel) end + if framed then table.insert(row, "|") end table.insert(result, table.concat(row)) end - print("RESULT", wea.inspect(result)) + + if framed then table.insert(result, frame_vertical) end + + return true, table.concat(result, "\n") end From 462570f2eb3b838f910ef56f405ca7beb1d424e6 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 01:26:53 +0000 Subject: [PATCH 16/79] //bonemeal: bugfix argument parsing --- CHANGELOG.md | 1 + worldeditadditions_commands/commands/bonemeal.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1413700..80f1deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Note to self: See the bottom of this file for the release template text. - `//replacemix`: Improve error handling to avoid crashes (thanks, Jonathon for reporting via Discord!) - Cloud wand: Improve chat message text - Fix `bonemeal` mod detection to look for the global `bonemeal`, not whether the `bonemeal` mod name has been loaded + - `//bonemeal`: Fix argument parsing - `//walls`: Prevent crash if not parameters are specified by defaulting to `dirt` as the replace_node - `//maze`, `//maze3d`: - Fix generated maze not reaching the very edge of the defined region diff --git a/worldeditadditions_commands/commands/bonemeal.lua b/worldeditadditions_commands/commands/bonemeal.lua index 651aaab..cc4ba90 100644 --- a/worldeditadditions_commands/commands/bonemeal.lua +++ b/worldeditadditions_commands/commands/bonemeal.lua @@ -27,7 +27,7 @@ worldedit.register_command("bonemeal", { return false, "Invalid strength value (value must be an integer)" end end - if #parts >= 2 then + if #parts >= 1 then chance = worldeditadditions.parse.chance(table.remove(parts, 1)) if not chance then return false, "Invalid chance value (must be a positive integer)" From f1a433b5e34218401b5276d93d475fae683cb25c Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 01:27:39 +0000 Subject: [PATCH 17/79] split_shell: automatically trim whitespace; ignore args that consist solely of whitespace Hence "//bonemeal 1 4 " now functions correctly --- worldeditadditions/utils/strings/split_shell.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/worldeditadditions/utils/strings/split_shell.lua b/worldeditadditions/utils/strings/split_shell.lua index ca8e1ba..d1211ce 100644 --- a/worldeditadditions/utils/strings/split_shell.lua +++ b/worldeditadditions/utils/strings/split_shell.lua @@ -5,7 +5,7 @@ local function is_whitespace(char) return char:match("%s") end -local function split_shell(text) +local function split_shell(text, autotrim) local text_length = #text local scan_pos = 1 local result = { } @@ -26,7 +26,10 @@ local function split_shell(text) if mode == "NORMAL" then if is_whitespace(curchar) and #acc > 0 then - table.insert(result, table.concat(acc, "")) + local nextval = worldeditadditions.trim(table.concat(acc, "")) + if #nextval > 0 then + table.insert(result, table.concat(acc, "")) + end acc = {} elseif (curchar == "\"" or curchar == "'") and #acc == 0 and prevchar ~= "\\" then if curchar == "\"" then From 75ffa81b7e7ccec34fb3508340de079dc27070e0 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 01:46:12 +0000 Subject: [PATCH 18/79] bonemeal: tidy up split_shell call --- worldeditadditions_commands/commands/bonemeal.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/worldeditadditions_commands/commands/bonemeal.lua b/worldeditadditions_commands/commands/bonemeal.lua index cc4ba90..e4b8f67 100644 --- a/worldeditadditions_commands/commands/bonemeal.lua +++ b/worldeditadditions_commands/commands/bonemeal.lua @@ -1,4 +1,5 @@ local we_c = worldeditadditions_commands +local wea = worldeditadditions -- ██████ ██████ ███ ██ ███████ ███ ███ ███████ █████ ██ -- ██ ██ ██ ██ ████ ██ ██ ████ ████ ██ ██ ██ ██ @@ -15,14 +16,14 @@ worldedit.register_command("bonemeal", { params_text = "1" end - local parts = worldeditadditions.split_shell(params_text, "%s+", false) + local parts = wea.split_shell(params_text) local strength = 1 local chance = 1 local node_names = {} -- An empty table means all nodes if #parts >= 1 then - strength = tonumber(table.remove(parts, 1)) + strength = tonumber(wea.trim(table.remove(parts, 1))) if not strength then return false, "Invalid strength value (value must be an integer)" end From b0c3d34dd0615adc4f5dd1b3b3c35d0cb03d6723 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 02:17:26 +0000 Subject: [PATCH 19/79] Implement apply function for VoxelManipulators --- worldeditadditions/lib/sculpt/apply.lua | 90 +++++++++++-------- .../lib/sculpt/apply_heightmap.lua | 54 +++++++++++ worldeditadditions/lib/sculpt/init.lua | 3 +- .../commands/sculpt.lua | 77 ++++++++++++++++ 4 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 worldeditadditions/lib/sculpt/apply_heightmap.lua create mode 100644 worldeditadditions_commands/commands/sculpt.lua diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index 6323cb2..d53577d 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -1,53 +1,65 @@ 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 +--- Applies the given brush with the given height and size to the given position. +-- @param pos1 Vector3 The position at which to apply the brush. +-- @param brush_name string The name of the brush to apply. +-- @param height number The height of the brush application. +-- @param brush_size Vector3 The size of the brush application. Values are interpreted on the X/Y coordinates, and NOT X/Z! +-- @returns bool, string|{ added: number, removed: number } A bool indicating whether the operation was successful or not, followed by either an error message as a string (if it was not successful) or a table of statistics (if it was successful). +local function apply(pos1, brush_name, height, brush_size) + -- 1: Get & validate brush + local success, brush, brush_size_actual = wea.sculpt.make_brush(brush_name, brush_size) + if not success then return success, brush end - 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 brush_size_terrain = Vector3.new( + brush_size_actual.x, + 0, + brush_size_actual.y + ) + local brush_size_radius = (brush_size_terrain / 2):floor() - local added = 0 - local removed = 0 + local pos1_compute = pos1 - brush_size_radius + local pos2_compute = pos1 + brush_size_radius + Vector3.new(0, height, 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 + + -- 2: Fetch the nodes in the specified area, extract heightmap + local manip, area = worldedit.manip_helpers.init(pos1_compute, pos2_compute) + local data = manip:get_data() + + local heightmap, heightmap_size = wea.make_heightmap( + pos1_compute, pos2_compute, + manip, area, + data + ) + local heightmap_orig = wea.table.shallowcopy(heightmap) + + for z = pos2_compute.z, pos1_compute.z, -1 do + for x = pos2_compute.x, pos1_compute.x, -1 do + local next_index = 1 -- We use table.insert() in make_weighted + local placed_node = false - local adjustment = math.floor(brush[bi]*height) - if adjustment > 0 then - added = added + adjustment - elseif adjustment < 0 then - removed = removed + math.abs(adjustment) - end + local hi = (z-pos1_compute.z)*heightmap_size.x + (x-pos1_compute.x) - heightmap[hi] = heightmap[hi] + adjustment + local offset = brush[hi] * height + if height > 0 then offset = math.floor(offset) + else offset = math.ceil(offset) end + heightmap[hi] = heightmap[hi] + offset end end - return true, added, removed + -- 3: Save back to disk & return + local success2, stats = wea.apply_heightmap_changes( + pos1_compute, pos2_compute, + area, data, + heightmap_orig, heightmap, + heightmap_size + ) + if not success2 then return success2, stats end + + worldedit.manip_helpers.finish(manip, data) + + return true, stats end diff --git a/worldeditadditions/lib/sculpt/apply_heightmap.lua b/worldeditadditions/lib/sculpt/apply_heightmap.lua new file mode 100644 index 0000000..1ad9621 --- /dev/null +++ b/worldeditadditions/lib/sculpt/apply_heightmap.lua @@ -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_heightmap(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_heightmap diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index 6dd75cd..fedb6e7 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -10,6 +10,7 @@ local sculpt = { 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_heightmap = dofile(wea.modpath.."/lib/sculpt/apply_heightmap.lua"), apply = dofile(wea.modpath.."/lib/sculpt/apply.lua") } @@ -17,6 +18,6 @@ return sculpt -- 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 +-- 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 diff --git a/worldeditadditions_commands/commands/sculpt.lua b/worldeditadditions_commands/commands/sculpt.lua new file mode 100644 index 0000000..1d01097 --- /dev/null +++ b/worldeditadditions_commands/commands/sculpt.lua @@ -0,0 +1,77 @@ +local we_c = worldeditadditions_commands +local wea = worldeditadditions + +-- ███████ ██████ ██ ██ ██ ██████ ████████ +-- ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██ ██ ██ ██ ██████ ██ +-- ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██████ ██████ ███████ ██ ██ +worldedit.register_command("sculpt", { + params = "[ [ []]]", + description = "Applies a sculpting brush to the terrain with a given height. See //sculptlist to list all available brushes. Note that while the brush size is configurable, the actual brush size you end up with may be slightly different to that which you request due to brush size restrictions.", + privs = { worldedit = true }, + require_pos = 1, + parse = function(params_text) + if not params_text or params_text == "" then + params_text = "default" + end + + local parts = wea.split_shell(params_text) + + local brush_name = "default" + local height = 5 + local brush_size = 10 + + if #parts >= 1 then + brush_name = table.remove(parts, 1) + if not wea.sculpt.brushes[brush_name] then + return false, "A brush with the name '"..brush_name.."' doesn't exist. Try using //sculptlist to list all available brushes." + end + end + if #parts >= 1 then + height = tonumber(table.remove(parts, 1)) + if not height then + return false, "Invalid height value (must be an integer - negative values lower terrain instead of raising it)" + end + end + if #parts >= 1 then + brush_size = tonumber(table.remove(parts, 1)) + if not brush_size or brush_size < 1 then + return false, "Invalid brush size. Brush sizes must be a positive integer." + end + end + + return true, brush_name, math.floor(height), math.floor(brush_size) + end, + nodes_needed = function(name, brush_name, height, brush_size) + local success, brush, size_actual = wea.sculpt.make_brush(brush_name, brush_size) + if not success then return 0 end + + -- This solution allows for brushes with negative values + -- it also allows for brushes that 'break the rules' and have values + -- that exceed the -1 to 1 range + local brush_min = wea.min(brush) + local brush_max = wea.max(brush) + local range_nodes = (brush_max * height) - (brush_min * height) + print("//sculpt range_nodes", range_nodes) + + return size_actual.x * size_actual.y * range_nodes + end, + func = function(name, brush_name, height, brush_size) + local start_time = wea.get_ms_time() + local pos1, pos2 = wea.Vector3.sort( + worldedit.pos1[name], + worldedit.pos2[name] + ) + local success, stats = wea.sculpt.apply( + pos1, + brush_name, height, brush_size + ) + if not success then return success, stats.added end + + local time_taken = wea.get_ms_time() - start_time + + minetest.log("action", name .. " used //sculpt at "..pos1..", adding " .. stats.added.." nodes and removing "..stats.removed.." nodes in "..time_taken.."s") + return true, stats.added.." nodes added and "..stats.removed.." removed in "..wea.format.human_time(time_taken) + end +}) From bbafca20206b2295f611768457807ee77f404021 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 02:20:47 +0000 Subject: [PATCH 20/79] apply: use wea.sculpt.apply_heightmap() --- worldeditadditions/lib/sculpt/apply.lua | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index d53577d..fcb9c52 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -34,28 +34,22 @@ local function apply(pos1, brush_name, height, brush_size) ) local heightmap_orig = wea.table.shallowcopy(heightmap) - for z = pos2_compute.z, pos1_compute.z, -1 do - for x = pos2_compute.x, pos1_compute.x, -1 do - local next_index = 1 -- We use table.insert() in make_weighted - local placed_node = false - - local hi = (z-pos1_compute.z)*heightmap_size.x + (x-pos1_compute.x) - - local offset = brush[hi] * height - if height > 0 then offset = math.floor(offset) - else offset = math.ceil(offset) end - heightmap[hi] = heightmap[hi] + offset - end - end + local success2, added, removed = wea.sculpt.apply_heightmap( + brush, brush_size_actual, + height, + (heightmap_size / 2):floor(), + heightmap, heightmap_size + ) + if not success2 then return success2, added end -- 3: Save back to disk & return - local success2, stats = wea.apply_heightmap_changes( + local success3, stats = wea.apply_heightmap_changes( pos1_compute, pos2_compute, area, data, heightmap_orig, heightmap, heightmap_size ) - if not success2 then return success2, stats end + if not success3 then return success2, stats end worldedit.manip_helpers.finish(manip, data) From a2a9108d366ff7d8a7681cb3530d5eeb6b7818cf Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 15:22:51 +0000 Subject: [PATCH 21/79] //sculpt: fix a bunch of bugs including, but certainly not limited to, wea.make_heightmap now returns a Vector3 instance for heightmap_size --- worldeditadditions/lib/sculpt/apply_heightmap.lua | 6 +++--- worldeditadditions/lib/sculpt/brushes/__smooth.lua | 3 ++- worldeditadditions/utils/terrain.lua | 10 ++++++---- worldeditadditions_commands/commands/sculpt.lua | 5 ++++- worldeditadditions_commands/init.lua | 1 + 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/worldeditadditions/lib/sculpt/apply_heightmap.lua b/worldeditadditions/lib/sculpt/apply_heightmap.lua index 1ad9621..5af9d63 100644 --- a/worldeditadditions/lib/sculpt/apply_heightmap.lua +++ b/worldeditadditions/lib/sculpt/apply_heightmap.lua @@ -30,12 +30,12 @@ local function apply_heightmap(brush, brush_size, height, position, heightmap, h -- 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 + for z = pos_end.z - 1, pos_start.z, -1 do + for x = pos_end.x - 1, pos_start.x, -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 - + print("hi", hi, "heightmap[hi]", heightmap[hi], "bi", bi, "brush[bi]", brush[bi]) local adjustment = math.floor(brush[bi]*height) if adjustment > 0 then added = added + adjustment diff --git a/worldeditadditions/lib/sculpt/brushes/__smooth.lua b/worldeditadditions/lib/sculpt/brushes/__smooth.lua index 8654da6..7276065 100644 --- a/worldeditadditions/lib/sculpt/brushes/__smooth.lua +++ b/worldeditadditions/lib/sculpt/brushes/__smooth.lua @@ -1,4 +1,5 @@ local wea = worldeditadditions +local Vector3 = wea.Vector3 --- Returns a smooth gaussian brush. -- @param size Vector3 The target size of the brush. Note that the actual size of the brush will be different, as the gaussian function has some limitations. @@ -18,5 +19,5 @@ return function(size, sigma) gaussian[i] = gaussian[i] / max end - return success, gaussian, { x = size, y = size } + return success, gaussian, Vector3.new(size, size, 0) end diff --git a/worldeditadditions/utils/terrain.lua b/worldeditadditions/utils/terrain.lua index 920496f..8649871 100644 --- a/worldeditadditions/utils/terrain.lua +++ b/worldeditadditions/utils/terrain.lua @@ -1,4 +1,5 @@ local wea = worldeditadditions +local Vector3 = wea.Vector3 --- Given a manip object and associates, generates a 2D x/z heightmap. -- Note that pos1 and pos2 should have already been pushed through @@ -35,10 +36,11 @@ function worldeditadditions.make_heightmap(pos1, pos2, manip, area, data) end end - local heightmap_size = { - z = (pos2.z - pos1.z) + 1, - x = (pos2.x - pos1.x) + 1 - } + local heightmap_size = Vector3.new( + (pos2.x - pos1.x) + 1, -- x + 0, -- y + (pos2.z - pos1.z) + 1 -- z + ) return heightmap, heightmap_size end diff --git a/worldeditadditions_commands/commands/sculpt.lua b/worldeditadditions_commands/commands/sculpt.lua index 1d01097..b0ffb98 100644 --- a/worldeditadditions_commands/commands/sculpt.lua +++ b/worldeditadditions_commands/commands/sculpt.lua @@ -1,5 +1,6 @@ local we_c = worldeditadditions_commands local wea = worldeditadditions +local Vector3 = wea.Vector3 -- ███████ ██████ ██ ██ ██ ██████ ████████ -- ██ ██ ██ ██ ██ ██ ██ ██ @@ -41,7 +42,9 @@ worldedit.register_command("sculpt", { end end - return true, brush_name, math.floor(height), math.floor(brush_size) + brush_size = Vector3.new(brush_size, brush_size, 0):floor() + + return true, brush_name, math.floor(height), brush_size end, nodes_needed = function(name, brush_name, height, brush_size) local success, brush, size_actual = wea.sculpt.make_brush(brush_name, brush_size) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index b6b4244..13939df 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -40,6 +40,7 @@ dofile(we_c.modpath.."/commands/copy.lua") dofile(we_c.modpath.."/commands/move.lua") dofile(we_c.modpath.."/commands/count.lua") +dofile(we_c.modpath.."/commands/sculpt.lua") -- Meta Commands dofile(we_c.modpath.."/commands/meta/init.lua") From 2da0f2dcc28eddbbca26a011ecf9078a026fa983 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 17:22:18 +0000 Subject: [PATCH 22/79] Add circle brush, but it isn't working right just yet. --- .../lib/sculpt/apply_heightmap.lua | 2 +- .../lib/sculpt/brushes/circle.lua | 28 +++++++++++++++++++ .../lib/sculpt/brushes/default.lua | 2 +- .../lib/sculpt/brushes/default_hard.lua | 2 +- .../lib/sculpt/brushes/default_soft.lua | 2 +- worldeditadditions/lib/sculpt/init.lua | 3 +- 6 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 worldeditadditions/lib/sculpt/brushes/circle.lua diff --git a/worldeditadditions/lib/sculpt/apply_heightmap.lua b/worldeditadditions/lib/sculpt/apply_heightmap.lua index 5af9d63..dd66446 100644 --- a/worldeditadditions/lib/sculpt/apply_heightmap.lua +++ b/worldeditadditions/lib/sculpt/apply_heightmap.lua @@ -35,7 +35,7 @@ local function apply_heightmap(brush, brush_size, height, position, heightmap, h 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 - print("hi", hi, "heightmap[hi]", heightmap[hi], "bi", bi, "brush[bi]", brush[bi]) + local adjustment = math.floor(brush[bi]*height) if adjustment > 0 then added = added + adjustment diff --git a/worldeditadditions/lib/sculpt/brushes/circle.lua b/worldeditadditions/lib/sculpt/brushes/circle.lua new file mode 100644 index 0000000..4ff6746 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/circle.lua @@ -0,0 +1,28 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + +local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") + +return function(size) + local brush = {} + + local centre = (size / 2):floor() + local minsize = math.floor(math.min(size.x, size.y) / 2) + + print("centre", centre, "minsize", minsize) + + for y = size.y - 1, 0, -1 do + for x = size.x - 1, 0, -1 do + local i = y*size.x + x + + + if math.floor((centre - Vector3.new(x, y, 0)):abs():length()) < minsize - 2 then + brush[i] = 1 + else + brush[i] = 0 + end + end + end + + return true, brush, size +end diff --git a/worldeditadditions/lib/sculpt/brushes/default.lua b/worldeditadditions/lib/sculpt/brushes/default.lua index eadf4c6..3982ee9 100644 --- a/worldeditadditions/lib/sculpt/brushes/default.lua +++ b/worldeditadditions/lib/sculpt/brushes/default.lua @@ -3,6 +3,6 @@ local wea = worldeditadditions local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) - local success, brush, size_actual = __smooth(size, 2) + local success, brush, size_actual = __smooth(size, 3) return success, brush, size_actual end diff --git a/worldeditadditions/lib/sculpt/brushes/default_hard.lua b/worldeditadditions/lib/sculpt/brushes/default_hard.lua index 33c1192..eadf4c6 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_hard.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_hard.lua @@ -3,6 +3,6 @@ local wea = worldeditadditions local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) - local success, brush, size_actual = __smooth(size, 1.25) + local success, brush, size_actual = __smooth(size, 2) return success, brush, size_actual end diff --git a/worldeditadditions/lib/sculpt/brushes/default_soft.lua b/worldeditadditions/lib/sculpt/brushes/default_soft.lua index 3982ee9..228e5ec 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_soft.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_soft.lua @@ -3,6 +3,6 @@ local wea = worldeditadditions local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) - local success, brush, size_actual = __smooth(size, 3) + local success, brush, size_actual = __smooth(size, 5) return success, brush, size_actual end diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index fedb6e7..ad9ffb6 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -5,7 +5,8 @@ local sculpt = { 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") + square = dofile(wea.modpath.."/lib/sculpt/brushes/square.lua"), + circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua") }, make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"), preview_brush = dofile(wea.modpath.."/lib/sculpt/preview_brush.lua"), From 17cc91ba1c6694c3819425663b57796d27650563 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 17:45:20 +0000 Subject: [PATCH 23/79] //sculpt: implement circle brush --- worldeditadditions/lib/sculpt/apply.lua | 2 + .../lib/sculpt/brushes/circle.lua | 5 +- worldeditadditions/lib/sculpt/init.lua | 1 + .../lib/sculpt/make_preview.lua | 54 +++++++++++++++++++ .../lib/sculpt/preview_brush.lua | 43 ++------------- .../commands/sculpt.lua | 1 - 6 files changed, 62 insertions(+), 44 deletions(-) create mode 100644 worldeditadditions/lib/sculpt/make_preview.lua diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index fcb9c52..28307f7 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -12,6 +12,8 @@ local function apply(pos1, brush_name, height, brush_size) local success, brush, brush_size_actual = wea.sculpt.make_brush(brush_name, brush_size) if not success then return success, brush end + print(wea.sculpt.make_preview(brush, brush_size_actual)) + local brush_size_terrain = Vector3.new( brush_size_actual.x, 0, diff --git a/worldeditadditions/lib/sculpt/brushes/circle.lua b/worldeditadditions/lib/sculpt/brushes/circle.lua index 4ff6746..c2cdc0e 100644 --- a/worldeditadditions/lib/sculpt/brushes/circle.lua +++ b/worldeditadditions/lib/sculpt/brushes/circle.lua @@ -9,14 +9,11 @@ return function(size) local centre = (size / 2):floor() local minsize = math.floor(math.min(size.x, size.y) / 2) - print("centre", centre, "minsize", minsize) - for y = size.y - 1, 0, -1 do for x = size.x - 1, 0, -1 do local i = y*size.x + x - - if math.floor((centre - Vector3.new(x, y, 0)):abs():length()) < minsize - 2 then + if math.floor((centre - Vector3.new(x, y, 0)):length()) < minsize then brush[i] = 1 else brush[i] = 0 diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index ad9ffb6..b1ae6d4 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -9,6 +9,7 @@ local sculpt = { circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua") }, make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"), + make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua"), preview_brush = dofile(wea.modpath.."/lib/sculpt/preview_brush.lua"), read_brush_static = dofile(wea.modpath.."/lib/sculpt/read_brush_static.lua"), apply_heightmap = dofile(wea.modpath.."/lib/sculpt/apply_heightmap.lua"), diff --git a/worldeditadditions/lib/sculpt/make_preview.lua b/worldeditadditions/lib/sculpt/make_preview.lua new file mode 100644 index 0000000..2b29d17 --- /dev/null +++ b/worldeditadditions/lib/sculpt/make_preview.lua @@ -0,0 +1,54 @@ +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 table The brush in question to preview. +-- @param size Vector3 The size of the brush. +-- @returns string A preview of the brush as a string. +local function make_preview(brush, size, framed) + if framed == nil then framed = true end + + -- 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 frame_vertical = "+"..string.rep("-", math.max(0, size.x)).."+" + + local result = {} + if framed then table.insert(result, frame_vertical) end + + for y = size.y-1, 0, -1 do + local row = {} + if framed then table.insert(row, "|") end + for x = size.x-1, 0, -1 do + local i = y*size.x + x + local pixel = " " + local threshold_cur = -1 + for value,threshold in pairs(values) do + if brush[i] * 10 > threshold and threshold_cur < threshold then + pixel = value + threshold_cur = threshold + end + end + table.insert(row, pixel) + end + if framed then table.insert(row, "|") end + table.insert(result, table.concat(row)) + end + + if framed then table.insert(result, frame_vertical) end + + + return table.concat(result, "\n") +end + +return make_preview diff --git a/worldeditadditions/lib/sculpt/preview_brush.lua b/worldeditadditions/lib/sculpt/preview_brush.lua index ede291c..13b07df 100644 --- a/worldeditadditions/lib/sculpt/preview_brush.lua +++ b/worldeditadditions/lib/sculpt/preview_brush.lua @@ -2,6 +2,7 @@ local wea = worldeditadditions local Vector3 = wea.Vector3 local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua") +local make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua") --- Generates a textual preview of a given brush. -- @param brush_name string The name of the brush to create a preview for. @@ -10,47 +11,11 @@ local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua") local function preview_brush(brush_name, target_size, framed) if framed == nil then framed = true end if not target_size then target_size = Vector3.new(10, 10, 0) end + local success, brush, brush_size = make_brush(brush_name, target_size) + if not success then return success, brush end - -- 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 frame_vertical = "+"..string.rep("-", math.max(0, brush_size.x)).."+" - - local result = {} - if framed then table.insert(result, frame_vertical) end - - for y = brush_size.y-1, 0, -1 do - local row = {} - if framed then table.insert(row, "|") end - for x = brush_size.x-1, 0, -1 do - local i = y*brush_size.x + x - local pixel = " " - local threshold_cur = -1 - for value,threshold in pairs(values) do - if brush[i] * 10 > threshold and threshold_cur < threshold then - pixel = value - threshold_cur = threshold - end - end - table.insert(row, pixel) - end - if framed then table.insert(row, "|") end - table.insert(result, table.concat(row)) - end - - if framed then table.insert(result, frame_vertical) end - - - return true, table.concat(result, "\n") + return true, make_preview(brush, brush_size, framed) end return preview_brush diff --git a/worldeditadditions_commands/commands/sculpt.lua b/worldeditadditions_commands/commands/sculpt.lua index b0ffb98..a9e949e 100644 --- a/worldeditadditions_commands/commands/sculpt.lua +++ b/worldeditadditions_commands/commands/sculpt.lua @@ -56,7 +56,6 @@ worldedit.register_command("sculpt", { local brush_min = wea.min(brush) local brush_max = wea.max(brush) local range_nodes = (brush_max * height) - (brush_min * height) - print("//sculpt range_nodes", range_nodes) return size_actual.x * size_actual.y * range_nodes end, From db5d25d1deeff630d6efea2932c40ddec2deb173 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:31:46 +0000 Subject: [PATCH 24/79] //convolve: update matrix & kernel to use Vector3 A lot of the maths remains in the old style, but at least it doesn't take a zero-indexed table --- worldeditadditions/lib/conv/conv.lua | 18 +++--- worldeditadditions/lib/conv/convolve.lua | 55 +++++++++++-------- worldeditadditions/lib/erode/snowballs.lua | 3 +- .../commands/convolve.lua | 36 +++++++----- 4 files changed, 68 insertions(+), 44 deletions(-) diff --git a/worldeditadditions/lib/conv/conv.lua b/worldeditadditions/lib/conv/conv.lua index 0330f95..b374fea 100644 --- a/worldeditadditions/lib/conv/conv.lua +++ b/worldeditadditions/lib/conv/conv.lua @@ -1,3 +1,5 @@ +local Vector3 = worldeditadditions.Vector3 + worldeditadditions.conv = {} dofile(worldeditadditions.modpath.."/lib/conv/kernels.lua") @@ -41,14 +43,16 @@ end function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size) pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local border_size = {} - border_size[0] = (kernel_size[0]-1) / 2 -- height - border_size[1] = (kernel_size[1]-1) / 2 -- width + local border_size = Vector3.new( + (kernel_size.x-1) / 2, -- x = height + 0, + (kernel_size.z-1) / 2 -- z = width + ) - pos1.z = pos1.z - border_size[0] - pos2.z = pos2.z + border_size[0] - pos1.x = pos1.x - border_size[1] - pos2.x = pos2.x + border_size[1] + pos1.z = pos1.z - border_size.x + pos2.z = pos2.z + border_size.x + pos1.x = pos1.x - border_size.z + pos2.x = pos2.x + border_size.z local manip, area = worldedit.manip_helpers.init(pos1, pos2) local data = manip:get_data() diff --git a/worldeditadditions/lib/conv/convolve.lua b/worldeditadditions/lib/conv/convolve.lua index bcf3e4b..50b86d0 100644 --- a/worldeditadditions/lib/conv/convolve.lua +++ b/worldeditadditions/lib/conv/convolve.lua @@ -1,31 +1,39 @@ - +local wea = worldeditadditions +local Vector3 = wea.Vector3 --[[ Convolves over a given 2D heightmap with a given matrix. Note that this *mutates* the given heightmap. Note also that the dimensions of the matrix must *only* be odd. -@param {number[]} heightmap The 2D heightmap to convolve over. -@param {[number,number]} heightmap_size The size of the heightmap as [ height, width ] -@param {number[]} matrix The matrix to convolve with. -@param {[number, number]} matrix_size The size of the convolution matrix as [ height, width ] +@param {number[]} heightmap The 2D heightmap to convolve over. +@param Vector3 heightmap_size The size of the heightmap as an X/Z Vector3 instance. +@param {number[]} matrix The matrix to convolve with. +@param Vector3 matrix_size The size of the convolution matrix as an X/Z Vector3 instance. ]]-- function worldeditadditions.conv.convolve(heightmap, heightmap_size, matrix, matrix_size) - if matrix_size[0] % 2 ~= 1 or matrix_size[1] % 2 ~= 1 then + if matrix_size.x % 2 ~= 1 or matrix_size.z % 2 ~= 1 then return false, "Error: The matrix size must contain only odd numbers (even number detected)" end - local border_size = {} - border_size[0] = (matrix_size[0]-1) / 2 -- height - border_size[1] = (matrix_size[1]-1) / 2 -- width - -- print("[convolve] matrix_size", matrix_size[0], matrix_size[1]) - -- print("[convolve] border_size", border_size[0], border_size[1]) + -- We need to reference a *copy* of the heightmap when convolving + -- This is because we need the original values when we perform a + -- convolution on a given pixel + local heightmap_copy = wea.table.shallowcopy(heightmap) + + local border_size = Vector3.new( + (matrix_size.x-1) / 2, -- x = height + 0, + (matrix_size.z-1) / 2 -- z = width + ) + -- print("[convolve] matrix_size", matrix_size.x, matrix_size.z) + -- print("[convolve] border_size", border_size.x, border_size.z) -- print("[convolve] heightmap_size: ", heightmap_size.z, heightmap_size.x) -- - -- print("[convolve] z: from", (heightmap_size.z-border_size[0]) - 1, "to", border_size[0], "step", -1) - -- print("[convolve] x: from", (heightmap_size.x-border_size[1]) - 1, "to", border_size[1], "step", -1) + -- print("[convolve] z: from", (heightmap_size.z-border_size.x) - 1, "to", border_size.x, "step", -1) + -- print("[convolve] x: from", (heightmap_size.x-border_size.z) - 1, "to", border_size.z, "step", -1) -- Convolve over only the bit that allows us to use the full convolution matrix - for z = (heightmap_size.z-border_size[0]) - 1, border_size[0], -1 do - for x = (heightmap_size.x-border_size[1]) - 1, border_size[1], -1 do + for z = (heightmap_size.z-border_size.x) - 1, border_size.x, -1 do + for x = (heightmap_size.x-border_size.z) - 1, border_size.z, -1 do local total = 0 @@ -33,21 +41,22 @@ function worldeditadditions.conv.convolve(heightmap, heightmap_size, matrix, mat -- print("[convolve/internal] z", z, "x", x, "hi", hi) -- No continue statement in Lua :-/ - if heightmap[hi] ~= -1 then - for mz = matrix_size[0]-1, 0, -1 do - for mx = matrix_size[1]-1, 0, -1 do - local mi = (mz * matrix_size[1]) + mx - local cz = z + (mz - border_size[0]) - local cx = x + (mx - border_size[1]) + if heightmap_copy[hi] ~= -1 then + for mz = matrix_size.x-1, 0, -1 do + for mx = matrix_size.z-1, 0, -1 do + local mi = (mz * matrix_size.z) + mx + local cz = z + (mz - border_size.x) + local cx = x + (mx - border_size.z) local i = (cz * heightmap_size.x) + cx -- A value of -1 = nothing in this column (so we should ignore it) - if heightmap[i] ~= -1 then - total = total + (matrix[mi] * heightmap[i]) + if heightmap_copy[i] ~= -1 then + total = total + (matrix[mi] * heightmap_copy[i]) end end end + -- Rounding hack - ref https://stackoverflow.com/a/18313481/1460422 -- heightmap[hi] = math.floor(total + 0.5) heightmap[hi] = math.ceil(total) diff --git a/worldeditadditions/lib/erode/snowballs.lua b/worldeditadditions/lib/erode/snowballs.lua index 5ffc403..29853a5 100644 --- a/worldeditadditions/lib/erode/snowballs.lua +++ b/worldeditadditions/lib/erode/snowballs.lua @@ -1,3 +1,4 @@ +local Vector3 = worldeditadditions.Vector3 -- Test command: //multi //fp set1 1313 6 5540 //fp set2 1338 17 5521 //erode snowballs @@ -132,7 +133,7 @@ function worldeditadditions.erode.snowballs(heightmap_initial, heightmap, height if not params.noconv then local success, matrix = worldeditadditions.get_conv_kernel("gaussian", 3, 3) if not success then return success, matrix end - local matrix_size = {} matrix_size[0] = 3 matrix_size[1] = 3 + local matrix_size = Vector3.new(3, 0, 3) worldeditadditions.conv.convolve( heightmap, heightmap_size, matrix, diff --git a/worldeditadditions_commands/commands/convolve.lua b/worldeditadditions_commands/commands/convolve.lua index 1ad1171..878e7d8 100644 --- a/worldeditadditions_commands/commands/convolve.lua +++ b/worldeditadditions_commands/commands/convolve.lua @@ -1,3 +1,6 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + -- ██████ ██████ ███ ██ ██ ██ ██████ ██ ██ ██ ███████ -- ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █████ @@ -11,8 +14,8 @@ worldedit.register_command("convolve", { parse = function(params_text) if not params_text then params_text = "" end - -- local parts = worldeditadditions.split(params_text, "%s+", false) - local parts = worldeditadditions.split_shell(params_text) + -- local parts = wea.split(params_text, "%s+", false) + local parts = wea.split_shell(params_text) local kernel_name = "gaussian" local width = 5 @@ -23,7 +26,7 @@ worldedit.register_command("convolve", { kernel_name = parts[1] end if #parts >= 2 then - local parts_dimension = worldeditadditions.split(parts[2], ",%s*", false) + local parts_dimension = wea.split(parts[2], ",%s*", false) width = tonumber(parts_dimension[1]) if not width then return false, "Error: Invalid width (it must be a positive odd integer)." @@ -50,26 +53,33 @@ worldedit.register_command("convolve", { return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) end, func = function(name, kernel_name, kernel_width, kernel_height, sigma) - local start_time = worldeditadditions.get_ms_time() + local start_time = wea.get_ms_time() - local success, kernel = worldeditadditions.get_conv_kernel(kernel_name, kernel_width, kernel_height, sigma) + local success, kernel = wea.get_conv_kernel(kernel_name, kernel_width, kernel_height, sigma) if not success then return success, kernel end - local kernel_size = {} - kernel_size[0] = kernel_height - kernel_size[1] = kernel_width + local kernel_size = Vector3.new( + kernel_height, + 0, + kernel_width + ) + + local pos1, pos2 = Vector3.sort( + worldedit.pos1[name], + worldedit.pos2[name] + ) local stats - success, stats = worldeditadditions.convolve( - worldedit.pos1[name], worldedit.pos2[name], + success, stats = wea.convolve( + pos1, pos2, kernel, kernel_size ) if not success then return success, stats end - local time_taken = worldeditadditions.get_ms_time() - start_time + local time_taken = wea.get_ms_time() - start_time - minetest.log("action", name.." used //convolve at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", adding "..stats.added.." nodes and removing "..stats.removed.." nodes in "..time_taken.."s") - return true, "Added "..stats.added.." and removed "..stats.removed.." nodes in " .. worldeditadditions.format.human_time(time_taken) + minetest.log("action", name.." used //convolve at "..pos1.." - "..pos2..", adding "..stats.added.." nodes and removing "..stats.removed.." nodes in "..time_taken.."s") + return true, "Added "..stats.added.." and removed "..stats.removed.." nodes in " .. wea.format.human_time(time_taken) end }) From 1cad9f4064876ebb05a101142284e2fe0cfa4a09 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:32:10 +0000 Subject: [PATCH 25/79] Minor layout tweaks --- worldeditadditions/lib/sculpt/brushes/__smooth.lua | 2 +- worldeditadditions/lib/sculpt/brushes/circle.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/worldeditadditions/lib/sculpt/brushes/__smooth.lua b/worldeditadditions/lib/sculpt/brushes/__smooth.lua index 7276065..32ec007 100644 --- a/worldeditadditions/lib/sculpt/brushes/__smooth.lua +++ b/worldeditadditions/lib/sculpt/brushes/__smooth.lua @@ -10,7 +10,7 @@ return function(size, sigma) if size < 1 then return false, "Error: Invalid brush size." end - local success, gaussian = worldeditadditions.conv.kernel_gaussian(size, sigma) + local success, gaussian = wea.conv.kernel_gaussian(size, sigma) -- Normalise values to fill the range 0 - 1 -- By default, wea.conv.kernel_gaussian values add up to 1 in total diff --git a/worldeditadditions/lib/sculpt/brushes/circle.lua b/worldeditadditions/lib/sculpt/brushes/circle.lua index c2cdc0e..2d7de2f 100644 --- a/worldeditadditions/lib/sculpt/brushes/circle.lua +++ b/worldeditadditions/lib/sculpt/brushes/circle.lua @@ -1,7 +1,6 @@ local wea = worldeditadditions local Vector3 = wea.Vector3 -local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") return function(size) local brush = {} From 3e2687f82d3e42a01629f8939caf3bc5cb369797 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:32:40 +0000 Subject: [PATCH 26/79] Add soft circle brush --- .../lib/sculpt/brushes/circle_soft1.lua | 56 +++++++++++++++++++ worldeditadditions/lib/sculpt/init.lua | 3 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 worldeditadditions/lib/sculpt/brushes/circle_soft1.lua diff --git a/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua new file mode 100644 index 0000000..dac76b0 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua @@ -0,0 +1,56 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + + +return function(size) + local brush = {} + + local centre = (size / 2):floor() + local minsize = math.floor(math.min(size.x, size.y) / 2) + + local border = 1 + local kernel_size = 3 + local kernel_size_vec = {} + kernel_size_vec[0] = kernel_size + kernel_size_vec[1] = kernel_size + + -- Make the circle + -- We don't use 0 to 1 here, because we have to blur it and the existing convolutional + -- system rounds values. + for y = size.y - 1, 0, -1 do + for x = size.x - 1, 0, -1 do + local i = y*size.x + x + + if math.floor((centre - Vector3.new(x, y, 0)):length()) < minsize - border then + brush[i] = 100000 + else + brush[i] = 0 + end + end + end + + print("BEFORE_BLUR") + print(wea.sculpt.make_preview(brush, size)) + + -- Make the kernel & blur it + local success, kernel = wea.conv.kernel_gaussian(kernel_size, 2) + if not success then return success, kernel end + + local success2, msg = worldeditadditions.conv.convolve( + brush, Vector3.new(size.x, 0, size.y), + kernel, kernel_size_vec + ) + if not success2 then return success2, msg end + + -- Rescale to be between 0 and 1 + local max_value = wea.max(brush) + for i,value in pairs(brush) do + brush[i] = brush[i] / max_value + end + + print("AFTER_BLUR") + print(wea.sculpt.make_preview(brush, size)) + + + return true, brush, size +end diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index b1ae6d4..1791d35 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -6,7 +6,8 @@ local sculpt = { 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"), - circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua") + circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua"), + circle_soft1 = dofile(wea.modpath.."/lib/sculpt/brushes/circle_soft1.lua") }, make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"), make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua"), From 1de037c341bc90952a67fde2d877b0bac94f708a Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:35:16 +0000 Subject: [PATCH 27/79] =?UTF-8?q?//sculpt=20brushes:=20=5F=5Fsmooth=20?= =?UTF-8?q?=E2=86=92=20=5F=5Fgaussian?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/sculpt/brushes/{__smooth.lua => __gaussian.lua} | 0 worldeditadditions/lib/sculpt/brushes/circle_soft1.lua | 7 ------- worldeditadditions/lib/sculpt/brushes/default.lua | 2 +- worldeditadditions/lib/sculpt/brushes/default_hard.lua | 2 +- worldeditadditions/lib/sculpt/brushes/default_soft.lua | 2 +- 5 files changed, 3 insertions(+), 10 deletions(-) rename worldeditadditions/lib/sculpt/brushes/{__smooth.lua => __gaussian.lua} (100%) diff --git a/worldeditadditions/lib/sculpt/brushes/__smooth.lua b/worldeditadditions/lib/sculpt/brushes/__gaussian.lua similarity index 100% rename from worldeditadditions/lib/sculpt/brushes/__smooth.lua rename to worldeditadditions/lib/sculpt/brushes/__gaussian.lua diff --git a/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua index dac76b0..64db19b 100644 --- a/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua +++ b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua @@ -29,9 +29,6 @@ return function(size) end end - print("BEFORE_BLUR") - print(wea.sculpt.make_preview(brush, size)) - -- Make the kernel & blur it local success, kernel = wea.conv.kernel_gaussian(kernel_size, 2) if not success then return success, kernel end @@ -48,9 +45,5 @@ return function(size) brush[i] = brush[i] / max_value end - print("AFTER_BLUR") - print(wea.sculpt.make_preview(brush, size)) - - return true, brush, size end diff --git a/worldeditadditions/lib/sculpt/brushes/default.lua b/worldeditadditions/lib/sculpt/brushes/default.lua index 3982ee9..638b9b1 100644 --- a/worldeditadditions/lib/sculpt/brushes/default.lua +++ b/worldeditadditions/lib/sculpt/brushes/default.lua @@ -1,6 +1,6 @@ local wea = worldeditadditions -local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") +local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") return function(size) local success, brush, size_actual = __smooth(size, 3) diff --git a/worldeditadditions/lib/sculpt/brushes/default_hard.lua b/worldeditadditions/lib/sculpt/brushes/default_hard.lua index eadf4c6..30b5f56 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_hard.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_hard.lua @@ -1,6 +1,6 @@ local wea = worldeditadditions -local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") +local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") return function(size) local success, brush, size_actual = __smooth(size, 2) diff --git a/worldeditadditions/lib/sculpt/brushes/default_soft.lua b/worldeditadditions/lib/sculpt/brushes/default_soft.lua index 228e5ec..5680f4d 100644 --- a/worldeditadditions/lib/sculpt/brushes/default_soft.lua +++ b/worldeditadditions/lib/sculpt/brushes/default_soft.lua @@ -1,6 +1,6 @@ local wea = worldeditadditions -local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__smooth.lua") +local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") return function(size) local success, brush, size_actual = __smooth(size, 5) From 732010a8ee925b792b7e0a88e3231943d2957b15 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:37:54 +0000 Subject: [PATCH 28/79] //layers: comment debug logging --- worldeditadditions_commands/commands/layers.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worldeditadditions_commands/commands/layers.lua b/worldeditadditions_commands/commands/layers.lua index 6423af2..4c84075 100644 --- a/worldeditadditions_commands/commands/layers.lua +++ b/worldeditadditions_commands/commands/layers.lua @@ -68,8 +68,8 @@ worldedit.register_command("layers", { ) local time_taken = worldeditadditions.get_ms_time() - start_time - print("DEBUG min_slope", min_slope, "max_slope", max_slope) - print("DEBUG min_slope", math.deg(min_slope), "max_slope", math.deg(max_slope)) + -- print("DEBUG min_slope", min_slope, "max_slope", max_slope) + -- print("DEBUG min_slope", math.deg(min_slope), "max_slope", math.deg(max_slope)) minetest.log("action", name .. " used //layers at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns ("..changes.skipped_columns_slope.." due to slope constraints) in " .. time_taken .. "s") return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped ("..changes.skipped_columns_slope.." due to slope constraints) in " .. worldeditadditions.format.human_time(time_taken) From ca6f1e7cea31ef954550c8e848539142a20699c0 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:38:10 +0000 Subject: [PATCH 29/79] //sculpt: sort pos{1,2}_compute --- worldeditadditions/lib/sculpt/apply.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index 28307f7..d759ead 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -24,6 +24,10 @@ local function apply(pos1, brush_name, height, brush_size) local pos1_compute = pos1 - brush_size_radius local pos2_compute = pos1 + brush_size_radius + Vector3.new(0, height, 0) + pos1_compute, pos2_compute = Vector3.sort( + pos1_compute, + pos2_compute + ) -- 2: Fetch the nodes in the specified area, extract heightmap local manip, area = worldedit.manip_helpers.init(pos1_compute, pos2_compute) From be7d46740a1fbc02322c82534097e7a68b2925ba Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 18:38:23 +0000 Subject: [PATCH 30/79] Update changelog (//convolve) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f1deb..26932d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Note to self: See the bottom of this file for the release template text. - `//maze`, `//maze3d`: - Fix generated maze not reaching the very edge of the defined region - Fix crash if no arguments are specified + - `//convolve`: Fix those super tall pillars appearing randomly ## v1.12: The selection tools update (26th June 2021) From b0e0b83c0d36fa05a9cd289a5dcdaeefe3dfab74 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 19:04:13 +0000 Subject: [PATCH 31/79] //sculpt: Add buffer to catch height variations in terrain --- worldeditadditions/lib/sculpt/apply.lua | 7 +++++-- worldeditadditions/lib/sculpt/brushes/circle_soft1.lua | 5 +---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index d759ead..6887c0f 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -21,8 +21,11 @@ local function apply(pos1, brush_name, height, brush_size) ) local brush_size_radius = (brush_size_terrain / 2):floor() - local pos1_compute = pos1 - brush_size_radius - local pos2_compute = pos1 + brush_size_radius + Vector3.new(0, height, 0) + -- To try and make sure we catch height variations + local buffer = Vector3.new(0, math.min(height*2, 100), 0) + + local pos1_compute = pos1 - brush_size_radius - buffer + local pos2_compute = pos1 + brush_size_radius + Vector3.new(0, height, 0) + buffer pos1_compute, pos2_compute = Vector3.sort( pos1_compute, diff --git a/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua index 64db19b..704b077 100644 --- a/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua +++ b/worldeditadditions/lib/sculpt/brushes/circle_soft1.lua @@ -10,9 +10,6 @@ return function(size) local border = 1 local kernel_size = 3 - local kernel_size_vec = {} - kernel_size_vec[0] = kernel_size - kernel_size_vec[1] = kernel_size -- Make the circle -- We don't use 0 to 1 here, because we have to blur it and the existing convolutional @@ -35,7 +32,7 @@ return function(size) local success2, msg = worldeditadditions.conv.convolve( brush, Vector3.new(size.x, 0, size.y), - kernel, kernel_size_vec + kernel, Vector3.new(kernel_size, 0, kernel_size) ) if not success2 then return success2, msg end From ef2dbc1806f21cae26450eb82402f60e443d0c3f Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 19:07:02 +0000 Subject: [PATCH 32/79] Reorder brushes circle_soft1 is now the default brush, as it's awesome --- .../lib/sculpt/brushes/{default.lua => gaussian.lua} | 0 .../brushes/{default_hard.lua => gaussian_hard.lua} | 0 .../brushes/{default_soft.lua => gaussian_soft.lua} | 0 worldeditadditions/lib/sculpt/init.lua | 10 +++++----- worldeditadditions_commands/commands/sculpt.lua | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) rename worldeditadditions/lib/sculpt/brushes/{default.lua => gaussian.lua} (100%) rename worldeditadditions/lib/sculpt/brushes/{default_hard.lua => gaussian_hard.lua} (100%) rename worldeditadditions/lib/sculpt/brushes/{default_soft.lua => gaussian_soft.lua} (100%) diff --git a/worldeditadditions/lib/sculpt/brushes/default.lua b/worldeditadditions/lib/sculpt/brushes/gaussian.lua similarity index 100% rename from worldeditadditions/lib/sculpt/brushes/default.lua rename to worldeditadditions/lib/sculpt/brushes/gaussian.lua diff --git a/worldeditadditions/lib/sculpt/brushes/default_hard.lua b/worldeditadditions/lib/sculpt/brushes/gaussian_hard.lua similarity index 100% rename from worldeditadditions/lib/sculpt/brushes/default_hard.lua rename to worldeditadditions/lib/sculpt/brushes/gaussian_hard.lua diff --git a/worldeditadditions/lib/sculpt/brushes/default_soft.lua b/worldeditadditions/lib/sculpt/brushes/gaussian_soft.lua similarity index 100% rename from worldeditadditions/lib/sculpt/brushes/default_soft.lua rename to worldeditadditions/lib/sculpt/brushes/gaussian_soft.lua diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index 1791d35..98cde48 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -2,12 +2,12 @@ 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"), + circle_soft1 = dofile(wea.modpath.."/lib/sculpt/brushes/circle_soft1.lua"), circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua"), - circle_soft1 = dofile(wea.modpath.."/lib/sculpt/brushes/circle_soft1.lua") + square = dofile(wea.modpath.."/lib/sculpt/brushes/square.lua"), + gaussian_hard = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian_hard.lua"), + gaussian = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian.lua"), + gaussian_soft = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian_soft.lua"), }, make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"), make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua"), diff --git a/worldeditadditions_commands/commands/sculpt.lua b/worldeditadditions_commands/commands/sculpt.lua index a9e949e..938b03c 100644 --- a/worldeditadditions_commands/commands/sculpt.lua +++ b/worldeditadditions_commands/commands/sculpt.lua @@ -14,12 +14,12 @@ worldedit.register_command("sculpt", { require_pos = 1, parse = function(params_text) if not params_text or params_text == "" then - params_text = "default" + params_text = "circle_soft1" end local parts = wea.split_shell(params_text) - local brush_name = "default" + local brush_name = "circle_soft1" local height = 5 local brush_size = 10 From 691b71adfec28ced6986b87c244058e0a30f8df0 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 22:13:38 +0000 Subject: [PATCH 33/79] Add img2brush tool to website Pending insertion of a link into the reference docs --- .docs/.eleventy.js | 2 +- .docs/css/theme.css | 27 ++++++++++ .docs/img2brush/img2brush.js | 97 ++++++++++++++++++++++++++++++++++++ .docs/img2brush/index.html | 34 +++++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 .docs/img2brush/img2brush.js create mode 100644 .docs/img2brush/index.html diff --git a/.docs/.eleventy.js b/.docs/.eleventy.js index cddd9b5..f410716 100644 --- a/.docs/.eleventy.js +++ b/.docs/.eleventy.js @@ -75,7 +75,7 @@ async function fetch(url) { } module.exports = function(eleventyConfig) { - + eleventyConfig.addPassthroughCopy("img2brush/img2brush.js"); eleventyConfig.addAsyncShortcode("fetch", fetch); // eleventyConfig.addPassthroughCopy("images"); diff --git a/.docs/css/theme.css b/.docs/css/theme.css index 22658f3..01a98e6 100644 --- a/.docs/css/theme.css +++ b/.docs/css/theme.css @@ -411,3 +411,30 @@ footer { flex-direction: column; align-items: center; } + +@keyframes move-diagonal { + from { + background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; + } + to { + background-position: -52px -52px, -52px -52px, -51px -51px, -51px -51px; + } +} + + +#dropzone { + border: 0.3em dashed #aaaaaa; + transition: border 0.2s; + justify-content: flex-start; +} +#dropzone.dropzone-active { + border: 0.3em dashed hsl(203, 79%, 55%); + + /* Ref https://www.magicpattern.design/tools/css-backgrounds */ + background-image: linear-gradient(var(--bg-bright) 2px, transparent 2px), linear-gradient(90deg, var(--bg-bright) 2px, transparent 2px), linear-gradient(var(--bg-bright) 1px, transparent 1px), linear-gradient(90deg, var(--bg-bright) 1px, var(--bg-transcluscent) 1px); + background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px; + background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; + + animation: move-diagonal 5s linear infinite; +} +#brushimg-preview { flex: 1; } diff --git a/.docs/img2brush/img2brush.js b/.docs/img2brush/img2brush.js new file mode 100644 index 0000000..0ab1c30 --- /dev/null +++ b/.docs/img2brush/img2brush.js @@ -0,0 +1,97 @@ +window.addEventListener("load", () => { + let dropzone = document.querySelector("#dropzone"); + dropzone.addEventListener("dragenter", handle_drag_enter); + dropzone.addEventListener("dragleave", handle_drag_leave); + dropzone.addEventListener("dragover", handle_drag_over); + dropzone.addEventListener("drop", handle_drop); + + document.querySelector("#brushimg-tsv").addEventListener("click", select_output); + let button_copy = document.querySelector("#brushimg-copy") + button_copy.addEventListener("click", () => { + select_output(); + button_copy.innerHTML = document.execCommand("copy") ? "Copied!" : "Failed to copy :-("; + }) +}); + +function select_output() { + let output = document.querySelector("#brushimg-tsv"); + + let selection = window.getSelection(); + + if (selection.rangeCount > 0) + selection.removeAllRanges(); + + let range = document.createRange(); + range.selectNode(output); + selection.addRange(range); +} + + +function handle_drag_enter(event) { + event.target.classList.add("dropzone-active"); +} +function handle_drag_leave(event) { + event.target.classList.remove("dropzone-active"); +} + +function handle_drag_over(event) { + event.preventDefault(); +} + +function handle_drop(event) { + event.stopPropagation(); + event.preventDefault(); + event.target.classList.remove("dropzone-active"); + + let image_file = null; + + image_file = event.dataTransfer.files[0]; + + let reader = new FileReader(); + reader.addEventListener("load", function(_event) { + let image = new Image(); + image.src = reader.result; + image.addEventListener("load", () => handle_new_image(image)); + + + document.querySelector("#brushimg-preview").src = image.src; + }); + reader.readAsDataURL(image_file); + + return false; +} + +function image2pixels(image) { + let canvas = document.createElement("canvas"), + ctx = canvas.getContext("2d"); + + canvas.width = image.width; + canvas.height = image.height; + + ctx.drawImage(image, 0, 0); + + return ctx.getImageData(0, 0, image.width, image.height); +} + +function handle_new_image(image) { + let tsv = pixels2tsv(image2pixels(image)); + document.querySelector("#brushimg-stats").value = `${image.width} x ${image.height} | ${image.width * image.height} pixels`; + document.querySelector("#brushimg-tsv").value = tsv; +} + +function round(number, decimal_places = 0) { + let multiplier = Math.pow(10, decimal_places); + return Math.round(number * multiplier) / multiplier; +} + +function pixels2tsv(pixels) { + let result = ""; + for(let y = 0; y < pixels.height; y++) { + let row = []; + for(let x = 0; x < pixels.width; x++) { + row.push(round(pixels.data[((y*pixels.width + x) * 4) + 3], 3)); + } + result += row.join(`\t`) + `\n`; + } + return result; +} diff --git a/.docs/img2brush/index.html b/.docs/img2brush/index.html new file mode 100644 index 0000000..4c4b594 --- /dev/null +++ b/.docs/img2brush/index.html @@ -0,0 +1,34 @@ +--- +layout: theme.njk +title: Image to brush converter +--- + +
+

Image to sculpting brush converter

+ +

Convert any image to a sculpting brush here! The alpha (opacity) channel is used to determine the weight of the brush - all colour is ignored.

+
+ +
+

Input

+

Drop your image here.

+ + + + +
+ + +
+

Output

+ +

Paste the output below into a text file in worldeditadditions/lib/sculpt/brushes with the file extension .txt and restart your Minetest server for the brush to be recognised.

+ +

+ +

+ +
(your output will appear here as soon as you drop an image above)
+
+ + From 3c279559f5cbacad46e5f4d4da9489b7bcaa89be Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 22:16:45 +0000 Subject: [PATCH 34/79] img2brush: rescale pixel values --- .docs/img2brush/img2brush.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.docs/img2brush/img2brush.js b/.docs/img2brush/img2brush.js index 0ab1c30..b1fe3d0 100644 --- a/.docs/img2brush/img2brush.js +++ b/.docs/img2brush/img2brush.js @@ -79,17 +79,12 @@ function handle_new_image(image) { document.querySelector("#brushimg-tsv").value = tsv; } -function round(number, decimal_places = 0) { - let multiplier = Math.pow(10, decimal_places); - return Math.round(number * multiplier) / multiplier; -} - function pixels2tsv(pixels) { let result = ""; for(let y = 0; y < pixels.height; y++) { let row = []; for(let x = 0; x < pixels.width; x++) { - row.push(round(pixels.data[((y*pixels.width + x) * 4) + 3], 3)); + row.push((pixels.data[((y*pixels.width + x) * 4) + 3] / 255).toFixed(3)); } result += row.join(`\t`) + `\n`; } From d60d3073bdee55c83f21c4848056df3156e2d3db Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 28 Dec 2021 22:18:40 +0000 Subject: [PATCH 35/79] fixup --- .docs/img2brush/img2brush.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.docs/img2brush/img2brush.js b/.docs/img2brush/img2brush.js index b1fe3d0..ccbea47 100644 --- a/.docs/img2brush/img2brush.js +++ b/.docs/img2brush/img2brush.js @@ -84,7 +84,8 @@ function pixels2tsv(pixels) { for(let y = 0; y < pixels.height; y++) { let row = []; for(let x = 0; x < pixels.width; x++) { - row.push((pixels.data[((y*pixels.width + x) * 4) + 3] / 255).toFixed(3)); + // No need to rescale here - this is done automagically by WorldEditAdditions. + row.push(pixels.data[((y*pixels.width + x) * 4) + 3] / 255); } result += row.join(`\t`) + `\n`; } From 1e9a65153719c632d96392cbbb72592516401e94 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 00:55:46 +0000 Subject: [PATCH 36/79] stings/polyfill: add str_ends ....because seriously, Lua *really* should have this. --- worldeditadditions/utils/strings/polyfill.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/worldeditadditions/utils/strings/polyfill.lua b/worldeditadditions/utils/strings/polyfill.lua index d1599ed..8c52b38 100644 --- a/worldeditadditions/utils/strings/polyfill.lua +++ b/worldeditadditions/utils/strings/polyfill.lua @@ -26,6 +26,14 @@ local function str_starts(str, start) return string.sub(str, 1, string.len(start)) == start end +--- Equivalent to string.endsWith in JS +-- @param str string The string to operate on +-- @param substr_end number The ending string to look for +-- @returns bool Whether substr_end is present at the end of str +local function str_ends(str, substr_end) + return string.sub(str, -string.len(substr_end)) == substr_end +end + --- Trims whitespace from a string from the beginning and the end. -- From http://lua-users.org/wiki/StringTrim -- @param str string The string to trim the whitespace from. @@ -39,12 +47,14 @@ if worldeditadditions then worldeditadditions.str_padend = str_padend worldeditadditions.str_padstart = str_padstart worldeditadditions.str_starts = str_starts + worldeditadditions.str_ends = str_starts worldeditadditions.trim = trim else return { str_padend = str_padend, str_padstart = str_padstart, str_starts = str_starts, + str_ends = str_starts, trim = trim } end From f259f85771cb872f7bd000b689d73a166e39f4b4 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 00:57:17 +0000 Subject: [PATCH 37/79] Add io compatibility layer ....I can't believe that Lua doesn't have an inbuilt scandir equivalent?! --- worldeditadditions/init.lua | 3 +++ worldeditadditions/utils/io.lua | 14 ++++++++++++++ worldeditadditions/utils/strings/split.lua | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 worldeditadditions/utils/io.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 834883c..fbc9a8c 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -18,6 +18,9 @@ wea.Queue = dofile(wea.modpath.."/utils/queue.lua") wea.LRU = dofile(wea.modpath.."/utils/lru.lua") wea.inspect = dofile(wea.modpath.."/utils/inspect.lua") +-- I/O compatibility layer +wea.io = dofile(wea.modpath.."/utils/io.lua") + wea.bit = dofile(wea.modpath.."/utils/bit.lua") diff --git a/worldeditadditions/utils/io.lua b/worldeditadditions/utils/io.lua new file mode 100644 index 0000000..4d83589 --- /dev/null +++ b/worldeditadditions/utils/io.lua @@ -0,0 +1,14 @@ +local io = { + -- Ref https://minetest.gitlab.io/minetest/minetest-namespace-reference/#utilities + scandir = function(dirpath) + return minetest.get_dir_list(dirpath, nil) + end, + scandir_files = function(dirpath) + return minetest.get_dir_list(dirpath, false) + end, + scandir_dirs = function(dirpath) + return minetest.get_dir_list(dirpath, true) + end, +} + +return io diff --git a/worldeditadditions/utils/strings/split.lua b/worldeditadditions/utils/strings/split.lua index 738b56f..541b86b 100644 --- a/worldeditadditions/utils/strings/split.lua +++ b/worldeditadditions/utils/strings/split.lua @@ -62,7 +62,7 @@ end -- @param plain boolean If true (or truthy), pattern is interpreted as a -- plain string, not a Lua pattern -- @returns table A sequence table containing the substrings -function worldeditadditions.split (str,dlm,plain) +function worldeditadditions.split(str,dlm,plain) local pos, ret = 0, {} local ins, i = str:find(dlm,pos,plain) -- "if plain" shaves off some time in the while statement From 3f1e01a282a6828de1d46a7ca94d587124e2a3a3 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 00:59:44 +0000 Subject: [PATCH 38/79] docs/img2brush: tweak file extension in instructions --- .docs/img2brush/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/img2brush/index.html b/.docs/img2brush/index.html index 4c4b594..4e68b2e 100644 --- a/.docs/img2brush/index.html +++ b/.docs/img2brush/index.html @@ -22,7 +22,7 @@ title: Image to brush converter

Output

-

Paste the output below into a text file in worldeditadditions/lib/sculpt/brushes with the file extension .txt and restart your Minetest server for the brush to be recognised.

+

Paste the output below into a text file in worldeditadditions/lib/sculpt/brushes with the file extension .brush.tsv and restart your Minetest server for the brush to be recognised.

From ec04cc58be195b091b1c75dc312777b3fb534507 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:03:56 +0000 Subject: [PATCH 39/79] Add support for autoloading static brushes on server start --- .../lib/sculpt/import_static.lua | 23 +++++++++ worldeditadditions/lib/sculpt/init.lua | 7 ++- .../lib/sculpt/parse_static.lua | 48 +++++++++++++++++++ worldeditadditions/lib/sculpt/scan_static.lua | 48 +++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 worldeditadditions/lib/sculpt/import_static.lua create mode 100644 worldeditadditions/lib/sculpt/parse_static.lua create mode 100644 worldeditadditions/lib/sculpt/scan_static.lua diff --git a/worldeditadditions/lib/sculpt/import_static.lua b/worldeditadditions/lib/sculpt/import_static.lua new file mode 100644 index 0000000..92f628a --- /dev/null +++ b/worldeditadditions/lib/sculpt/import_static.lua @@ -0,0 +1,23 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + +local parse_static = dofile(wea.modpath.."/lib/sculpt/parse_static.lua") + +--- Reads and parses the brush stored in the specified file. +-- @param filepath string The path to file that contains the static brush to read in. +-- @returns true,table,Vector3|false,string A success boolean, followed either by an error message as a string or the brush (as a table) and it's size (as an X/Y Vector3) +return function(filepath) + local handle = io.open(filepath) + if not handle then + handle:close() + return false, "Error: Failed to open the static brush file at '"..filepath.."'." + end + + local data = handle:read("*all") + handle:close() + + local success, brush, brush_size = parse_static(data) + if not success then return success, brush end + + return true, brush, brush_size +end diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index 98cde48..89f928a 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -14,9 +14,14 @@ local sculpt = { preview_brush = dofile(wea.modpath.."/lib/sculpt/preview_brush.lua"), read_brush_static = dofile(wea.modpath.."/lib/sculpt/read_brush_static.lua"), apply_heightmap = dofile(wea.modpath.."/lib/sculpt/apply_heightmap.lua"), - apply = dofile(wea.modpath.."/lib/sculpt/apply.lua") + apply = dofile(wea.modpath.."/lib/sculpt/apply.lua"), + scan_static = dofile(wea.modpath.."/lib/sculpt/scan_static.lua"), + import_static = dofile(wea.modpath.."/lib/sculpt/import_static.lua"), + parse_static = dofile(wea.modpath.."/lib/sculpt/parse_static.lua") } +sculpt.scan_static(wea.modpath.."/lib/sculpt/brushes") + return sculpt -- TODO: Automatically find & register all text file based brushes in the brushes directory diff --git a/worldeditadditions/lib/sculpt/parse_static.lua b/worldeditadditions/lib/sculpt/parse_static.lua new file mode 100644 index 0000000..4570ea1 --- /dev/null +++ b/worldeditadditions/lib/sculpt/parse_static.lua @@ -0,0 +1,48 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + +--- Parses a static brush definition. +-- @param source string The source string that contains the static brush, formatted as TSV. +-- @returns true,table,Vector3|false,string A success boolean, followed either by an error message as a string or the brush (as a table) and it's size (as an X/Y Vector3) +return function(source) + local width = -1 + local height + local maxvalue, minvalue, range + + -- Parse out the TSV into a table of tables, while also parsing values as numbers + -- Also keeps track of the maximum/minimum values found for rescaling later. + local values = wea.table.map( + wea.split(source, "\n", false), + function(line) + local row = wea.split(line, "%s+", false) + width = math.max(width, #row) + return wea.table.map( + row, + function(pixel) + local value = tonumber(pixel) + if not value then value = 0 end + if maxvalue == nil or value > maxvalue then + maxvalue = value + end + if minvalue == nil or value < minvalue then + minvalue = value + end + return value + end + ) + end + ) + + height = #values + range = maxvalue - minvalue + + local brush = {} + for y,row in ipairs(values) do + for x,value in ipairs(row) do + local i = (y-1)*width + (x-1) + brush[i] = (value - minvalue) / range + end + end + + return true, brush, Vector3.new(width, height, 0) +end diff --git a/worldeditadditions/lib/sculpt/scan_static.lua b/worldeditadditions/lib/sculpt/scan_static.lua new file mode 100644 index 0000000..caacf37 --- /dev/null +++ b/worldeditadditions/lib/sculpt/scan_static.lua @@ -0,0 +1,48 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + +local import_static = dofile(wea.modpath.."/lib/sculpt/import_static.lua") + +local function import_filepath(filepath, name, overwrite_existing) + if overwrite_existing and wea.sculpt.brushes[name] ~= nil then + return false, "Error: A brush with the name '"..name.."' already exists." + end + + local success, brush, brush_size = import_static(filepath) + if not success then return success, "Error while reading from '"..filepath.."': "..brush end + + wea.sculpt.brushes[name] = { + brush = brush, + size = brush_size + } + + return true +end + +--- Scans the given directory and imports all static brushes found. +-- Static brushes have the file extension ".brush.tsv" (without quotes). +-- @param dirpath string The path to directory that contains the static brushs to import. +-- @returns bool,loaded,errors A success boolean, followed by the number of brushes loaded, followed by the number of errors encountered while loading brushes (errors are logged as warnings with Minetest) +return function(dirpath, overwrite_existing) + if overwrite_existing == nil then overwrite_existing = false end + local files = wea.io.scandir_files(dirpath) + + local brushes_loaded = 0 + local errors = 0 + + + for filename in pairs(files) do + if wea.str_ends(filename, ".brush.tsv") then + local filepath = dirpath.."/"..filename + local name = filepath:gsub(".brush.tsv", "") + + local success, msg = import_filepath(filepath, name, overwrite_existing) + if not success then + minetest.log("warning", "[WorldEditAdditions:sculpt] Encountered error when loading brush from '"..filepath.."':"..msg) + end + brushes_loaded = brushes_loaded + 1 + end + end + + return true, brushes_loaded, errors +end From f19b22e9069bed5a287308adb5b810e4dd9df267 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:04:27 +0000 Subject: [PATCH 40/79] //sculpt: add simple test ellipse static brush --- .../lib/sculpt/brushes/ellipse.brush.tsv | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 worldeditadditions/lib/sculpt/brushes/ellipse.brush.tsv diff --git a/worldeditadditions/lib/sculpt/brushes/ellipse.brush.tsv b/worldeditadditions/lib/sculpt/brushes/ellipse.brush.tsv new file mode 100644 index 0000000..fef25d8 --- /dev/null +++ b/worldeditadditions/lib/sculpt/brushes/ellipse.brush.tsv @@ -0,0 +1,27 @@ +0.000 0.000 0.000 0.000 0.000 0.004 0.008 0.004 0.004 0.000 0.000 0.000 0.000 +0.000 0.000 0.000 0.004 0.012 0.031 0.043 0.031 0.012 0.004 0.000 0.000 0.000 +0.000 0.000 0.000 0.012 0.047 0.110 0.149 0.118 0.051 0.012 0.000 0.000 0.000 +0.000 0.000 0.004 0.031 0.118 0.259 0.345 0.271 0.125 0.035 0.004 0.000 0.000 +0.000 0.000 0.012 0.063 0.212 0.435 0.561 0.455 0.231 0.075 0.012 0.000 0.000 +0.000 0.000 0.024 0.106 0.318 0.588 0.729 0.612 0.341 0.122 0.027 0.004 0.000 +0.000 0.004 0.039 0.161 0.416 0.702 0.835 0.725 0.447 0.180 0.047 0.008 0.000 +0.000 0.008 0.055 0.220 0.506 0.780 0.894 0.800 0.537 0.239 0.067 0.012 0.000 +0.000 0.012 0.075 0.271 0.580 0.835 0.929 0.855 0.608 0.294 0.086 0.016 0.000 +0.000 0.016 0.094 0.318 0.635 0.875 0.953 0.886 0.659 0.341 0.110 0.020 0.000 +0.004 0.024 0.118 0.357 0.678 0.902 0.965 0.906 0.698 0.376 0.129 0.027 0.004 +0.004 0.031 0.137 0.392 0.710 0.914 0.973 0.918 0.718 0.404 0.145 0.031 0.004 +0.004 0.035 0.153 0.412 0.725 0.922 0.976 0.922 0.729 0.416 0.157 0.035 0.004 +0.004 0.035 0.161 0.424 0.733 0.922 0.976 0.922 0.729 0.420 0.157 0.035 0.004 +0.004 0.035 0.161 0.424 0.733 0.922 0.976 0.922 0.722 0.408 0.149 0.035 0.004 +0.004 0.035 0.149 0.412 0.722 0.922 0.973 0.914 0.706 0.388 0.137 0.031 0.004 +0.004 0.027 0.137 0.388 0.706 0.910 0.969 0.902 0.678 0.361 0.118 0.024 0.004 +0.004 0.020 0.114 0.349 0.671 0.894 0.957 0.875 0.639 0.318 0.094 0.016 0.000 +0.000 0.016 0.090 0.302 0.620 0.859 0.933 0.835 0.580 0.271 0.075 0.012 0.000 +0.000 0.012 0.071 0.251 0.549 0.812 0.898 0.780 0.506 0.220 0.059 0.008 0.000 +0.000 0.008 0.047 0.192 0.459 0.733 0.839 0.702 0.420 0.165 0.039 0.004 0.000 +0.000 0.004 0.027 0.129 0.349 0.624 0.733 0.592 0.318 0.110 0.024 0.000 0.000 +0.000 0.000 0.016 0.075 0.235 0.463 0.565 0.439 0.212 0.063 0.012 0.000 0.000 +0.000 0.000 0.008 0.035 0.129 0.275 0.345 0.263 0.118 0.031 0.004 0.000 0.000 +0.000 0.000 0.004 0.012 0.051 0.118 0.153 0.114 0.047 0.012 0.000 0.000 0.000 +0.000 0.000 0.000 0.004 0.012 0.031 0.043 0.031 0.012 0.004 0.000 0.000 0.000 +0.000 0.000 0.000 0.000 0.004 0.004 0.008 0.004 0.000 0.000 0.000 0.000 0.000 From 08cff1c96750625ef8cdbc2cd00d4fadf8ab62f3 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:14:08 +0000 Subject: [PATCH 41/79] strings/polyfill/str_ends: actually export it correctly --- worldeditadditions/utils/strings/polyfill.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions/utils/strings/polyfill.lua b/worldeditadditions/utils/strings/polyfill.lua index 8c52b38..4fc0497 100644 --- a/worldeditadditions/utils/strings/polyfill.lua +++ b/worldeditadditions/utils/strings/polyfill.lua @@ -47,7 +47,7 @@ if worldeditadditions then worldeditadditions.str_padend = str_padend worldeditadditions.str_padstart = str_padstart worldeditadditions.str_starts = str_starts - worldeditadditions.str_ends = str_starts + worldeditadditions.str_ends = str_ends worldeditadditions.trim = trim else return { From 4597edcf1e6d576ccff7ee2ba031ad56fa4f29a8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:33:44 +0000 Subject: [PATCH 42/79] str_ends: write tests --- .tests/strings/str_ends.test.lua | 40 +++++++++++++++++++ worldeditadditions/utils/strings/polyfill.lua | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .tests/strings/str_ends.test.lua diff --git a/.tests/strings/str_ends.test.lua b/.tests/strings/str_ends.test.lua new file mode 100644 index 0000000..b17665c --- /dev/null +++ b/.tests/strings/str_ends.test.lua @@ -0,0 +1,40 @@ +local polyfill = require("worldeditadditions.utils.strings.polyfill") + +describe("str_ends", function() + it("should return true for a single character", function() + assert.are.equal( + true, + polyfill.str_ends("test", "t") + ) + end) + it("should return true for a multiple characters", function() + assert.are.equal( + true, + polyfill.str_ends("test", "st") + ) + end) + it("should return true for identical strings", function() + assert.are.equal( + true, + polyfill.str_ends("test", "test") + ) + end) + it("should return false for a single character ", function() + assert.are.equal( + false, + polyfill.str_ends("test", "y") + ) + end) + it("should return false for a character present elsewherer", function() + assert.are.equal( + false, + polyfill.str_ends("test", "e") + ) + end) + it("should return false for another substring", function() + assert.are.equal( + false, + polyfill.str_ends("test", "tes") + ) + end) +end) diff --git a/worldeditadditions/utils/strings/polyfill.lua b/worldeditadditions/utils/strings/polyfill.lua index 4fc0497..1348da2 100644 --- a/worldeditadditions/utils/strings/polyfill.lua +++ b/worldeditadditions/utils/strings/polyfill.lua @@ -54,7 +54,7 @@ else str_padend = str_padend, str_padstart = str_padstart, str_starts = str_starts, - str_ends = str_starts, + str_ends = str_ends, trim = trim } end From 70d6f364445a7721532c71a2a88aed8ee4cb0de1 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:34:41 +0000 Subject: [PATCH 43/79] tests/str_starts: fix assert calls --- .tests/strings/str_starts.test.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.tests/strings/str_starts.test.lua b/.tests/strings/str_starts.test.lua index 606a311..747b3eb 100644 --- a/.tests/strings/str_starts.test.lua +++ b/.tests/strings/str_starts.test.lua @@ -3,38 +3,38 @@ local polyfill = require("worldeditadditions.utils.strings.polyfill") describe("str_starts", function() it("should return true for a single character", function() assert.are.equal( - polyfill.str_starts("test", "t"), - true + true, + polyfill.str_starts("test", "t") ) end) it("should return true for a multiple characters", function() assert.are.equal( - polyfill.str_starts("test", "te"), - true + true, + polyfill.str_starts("test", "te") ) end) it("should return true for identical strings", function() assert.are.equal( - polyfill.str_starts("test", "test"), - true + true, + polyfill.str_starts("test", "test") ) end) it("should return false for a single character ", function() assert.are.equal( - polyfill.str_starts("test", "y"), - false + false, + polyfill.str_starts("test", "y") ) end) it("should return false for a character present elsewherer", function() assert.are.equal( - polyfill.str_starts("test", "e"), - false + false, + polyfill.str_starts("test", "e") ) end) it("should return false for another substring", function() assert.are.equal( - polyfill.str_starts("test", "est"), - false + false, + polyfill.str_starts("test", "est") ) end) end) From dd8cd78d6bd4cd7671583f3802865d7a5acb2f28 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:38:24 +0000 Subject: [PATCH 44/79] wea.inspect: pull metatable names from item.__name this is completely arbitrary, but will assist in discovering types of tables etc. --- worldeditadditions/utils/inspect.lua | 13 ++++++++++++- worldeditadditions/utils/vector3.lua | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/worldeditadditions/utils/inspect.lua b/worldeditadditions/utils/inspect.lua index f8072c9..d5ed030 100644 --- a/worldeditadditions/utils/inspect.lua +++ b/worldeditadditions/utils/inspect.lua @@ -1,5 +1,10 @@ --- Serialises an arbitrary value to a string. -- Note that although the resulting table *looks* like valid Lua, it isn't. +-- Completely arbitrarily, if a table (or it's associated metatable) has the +-- key __name then it is conidered the name of the parent metatable. This can +-- be useful for identifying custom table-based types. +-- Should anyone come across a 'proper' way to obtain the name of a metatable +-- in pure vanilla Lua, I will update this to follow that standard instead. -- @param item any Input item to serialise. -- @param sep string key value seperator -- @param new_line string key value pair delimiter @@ -12,7 +17,13 @@ local function inspect(item, maxdepth) end if maxdepth < 1 then return "[truncated]" end - local result = { "{\n" } + local result = { } + -- Consider our (arbitrarily decided) property __name to the type of this item + -- Remember that this implicitly checks the metatable so long as __index is set. + if type(item.__name) == "string" then + table.insert(result, "("..item.__name..") ") + end + table.insert(result, "{\n") for key,value in pairs(item) do local value_text = inspect(value, maxdepth - 1) :gsub("\n", "\n\t") diff --git a/worldeditadditions/utils/vector3.lua b/worldeditadditions/utils/vector3.lua index 8558453..efff729 100644 --- a/worldeditadditions/utils/vector3.lua +++ b/worldeditadditions/utils/vector3.lua @@ -2,6 +2,7 @@ -- @class local Vector3 = {} Vector3.__index = Vector3 +Vector3.__name = "Vector3" -- A hack to allow identification in wea.inspect --- Creates a new Vector3 instance. -- @param x number The x co-ordinate value. From 227265db720369e06ff6cd39a29a514d491bd6b8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:39:04 +0000 Subject: [PATCH 45/79] //sculpt: fix importing static brushes --- worldeditadditions/init.lua | 11 +++++++++++ worldeditadditions/lib/sculpt/init.lua | 2 +- worldeditadditions/lib/sculpt/scan_static.lua | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index fbc9a8c..d391094 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -81,3 +81,14 @@ dofile(wea.modpath.."/lib/selection/init.lua") -- Helpers for selections dofile(wea.modpath.."/lib/wireframe/corner_set.lua") dofile(wea.modpath.."/lib/wireframe/make_compass.lua") dofile(wea.modpath.."/lib/wireframe/wire_box.lua") + + + +--- +-- Post-setup tasks +--- + +--- 1: Scan for an import static brushes +-- Static brushes live in lib/sculpt/brushes (relative to this file), and have +-- the file extension ".brush.tsv" (without quotes, of course). +wea.sculpt.scan_static(wea.modpath.."/lib/sculpt/brushes") diff --git a/worldeditadditions/lib/sculpt/init.lua b/worldeditadditions/lib/sculpt/init.lua index 89f928a..5dfffbb 100644 --- a/worldeditadditions/lib/sculpt/init.lua +++ b/worldeditadditions/lib/sculpt/init.lua @@ -20,7 +20,7 @@ local sculpt = { parse_static = dofile(wea.modpath.."/lib/sculpt/parse_static.lua") } -sculpt.scan_static(wea.modpath.."/lib/sculpt/brushes") +-- scan_sculpt is called after everything is loaded in the main init file return sculpt diff --git a/worldeditadditions/lib/sculpt/scan_static.lua b/worldeditadditions/lib/sculpt/scan_static.lua index caacf37..3535469 100644 --- a/worldeditadditions/lib/sculpt/scan_static.lua +++ b/worldeditadditions/lib/sculpt/scan_static.lua @@ -31,7 +31,7 @@ return function(dirpath, overwrite_existing) local errors = 0 - for filename in pairs(files) do + for i, filename in pairs(files) do if wea.str_ends(filename, ".brush.tsv") then local filepath = dirpath.."/"..filename local name = filepath:gsub(".brush.tsv", "") From 9df5ba6fe593c24c5287277c1b4e528d3e890bf8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 01:43:53 +0000 Subject: [PATCH 46/79] //sculpt, //sculptlist: fix naming of static brushes; prettify output --- worldeditadditions/lib/sculpt/scan_static.lua | 2 +- worldeditadditions_commands/commands/extra/sculptlist.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worldeditadditions/lib/sculpt/scan_static.lua b/worldeditadditions/lib/sculpt/scan_static.lua index 3535469..a300ee6 100644 --- a/worldeditadditions/lib/sculpt/scan_static.lua +++ b/worldeditadditions/lib/sculpt/scan_static.lua @@ -34,7 +34,7 @@ return function(dirpath, overwrite_existing) for i, filename in pairs(files) do if wea.str_ends(filename, ".brush.tsv") then local filepath = dirpath.."/"..filename - local name = filepath:gsub(".brush.tsv", "") + local name = filename:gsub(".brush.tsv", "") local success, msg = import_filepath(filepath, name, overwrite_existing) if not success then diff --git a/worldeditadditions_commands/commands/extra/sculptlist.lua b/worldeditadditions_commands/commands/extra/sculptlist.lua index 8506db4..690aeb7 100644 --- a/worldeditadditions_commands/commands/extra/sculptlist.lua +++ b/worldeditadditions_commands/commands/extra/sculptlist.lua @@ -24,7 +24,7 @@ minetest.register_chatcommand("/sculptlist", { local brush_size = "dynamic" if type(brush_def) ~= "function" then - brush_size = brush_def.size + brush_size = brush_def.size.x.."x"..brush_def.size.y end print("//sculptlist: preview for "..brush_name..":") From 1310dae884cbd54693745b74b884baa2b30564bf Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 02:42:32 +0000 Subject: [PATCH 47/79] Refactor: Split up terrain.lua, make subtable wea.terrain This is just the start, if my plans work out. The eventual aim here is to implement a generic Heightmap2D class, just like Vector3. This will make interacting with heightmaps much easier. --- worldeditadditions/init.lua | 3 +- worldeditadditions/lib/conv/conv.lua | 33 ++-- worldeditadditions/lib/erode/erode.lua | 23 +-- worldeditadditions/lib/erode/snowballs.lua | 27 +-- worldeditadditions/lib/layers.lua | 4 +- worldeditadditions/lib/noise/apply_2d.lua | 14 +- worldeditadditions/lib/noise/run2d.lua | 10 +- worldeditadditions/lib/sculpt/apply.lua | 4 +- worldeditadditions/utils/terrain.lua | 180 ------------------ .../utils/terrain/apply_heightmap_changes.lua | 71 +++++++ .../utils/terrain/calculate_normals.lua | 47 +++++ .../utils/terrain/calculate_slopes.lua | 30 +++ worldeditadditions/utils/terrain/init.lua | 12 ++ .../utils/terrain/make_heightmap.lua | 50 +++++ 14 files changed, 271 insertions(+), 237 deletions(-) delete mode 100644 worldeditadditions/utils/terrain.lua create mode 100644 worldeditadditions/utils/terrain/apply_heightmap_changes.lua create mode 100644 worldeditadditions/utils/terrain/calculate_normals.lua create mode 100644 worldeditadditions/utils/terrain/calculate_slopes.lua create mode 100644 worldeditadditions/utils/terrain/init.lua create mode 100644 worldeditadditions/utils/terrain/make_heightmap.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index d391094..ab628ec 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -23,6 +23,7 @@ wea.io = dofile(wea.modpath.."/utils/io.lua") wea.bit = dofile(wea.modpath.."/utils/bit.lua") +wea.terrain = dofile(wea.modpath.."/utils/terrain.lua") dofile(wea.modpath.."/utils/vector.lua") dofile(wea.modpath.."/utils/strings/init.lua") @@ -33,7 +34,7 @@ dofile(wea.modpath.."/utils/tables/init.lua") dofile(wea.modpath.."/utils/numbers.lua") dofile(wea.modpath.."/utils/nodes.lua") dofile(wea.modpath.."/utils/node_identification.lua") -dofile(wea.modpath.."/utils/terrain.lua") + dofile(wea.modpath.."/utils/raycast_adv.lua") -- For the farwand dofile(wea.modpath.."/utils/player.lua") -- Player info functions diff --git a/worldeditadditions/lib/conv/conv.lua b/worldeditadditions/lib/conv/conv.lua index b374fea..eec69b3 100644 --- a/worldeditadditions/lib/conv/conv.lua +++ b/worldeditadditions/lib/conv/conv.lua @@ -1,11 +1,12 @@ -local Vector3 = worldeditadditions.Vector3 +local wea = worldeditadditions +local Vector3 = wea.Vector3 -worldeditadditions.conv = {} +wea.conv = {} -dofile(worldeditadditions.modpath.."/lib/conv/kernels.lua") -dofile(worldeditadditions.modpath.."/lib/conv/kernel_gaussian.lua") +dofile(wea.modpath.."/lib/conv/kernels.lua") +dofile(wea.modpath.."/lib/conv/kernel_gaussian.lua") -dofile(worldeditadditions.modpath.."/lib/conv/convolve.lua") +dofile(wea.modpath.."/lib/conv/convolve.lua") --- Creates a new kernel. -- Note that the gaussian kernel only allows for creating a square kernel. @@ -14,7 +15,7 @@ dofile(worldeditadditions.modpath.."/lib/conv/convolve.lua") -- @param width number The width of the kernel to create (must be an odd integer). -- @param height number The height of the kernel to create (must be an odd integer). -- @param arg number The argument to pass when creating the kernel. Currently only used by gaussian kernel as the sigma value. -function worldeditadditions.get_conv_kernel(name, width, height, arg) +function wea.get_conv_kernel(name, width, height, arg) if width % 2 ~= 1 then return false, "Error: The width must be an odd integer."; end @@ -23,16 +24,16 @@ function worldeditadditions.get_conv_kernel(name, width, height, arg) end if name == "box" then - return true, worldeditadditions.conv.kernel_box(width, height) + return true, wea.conv.kernel_box(width, height) elseif name == "pascal" then - return true, worldeditadditions.conv.kernel_pascal(width, height, true) + return true, wea.conv.kernel_pascal(width, height, true) elseif name == "gaussian" then if width ~= height then return false, "Error: When using a gaussian kernel the width and height must be identical." end -- Default to sigma = 2 if arg == nil then arg = 2 end - local success, result = worldeditadditions.conv.kernel_gaussian(width, arg) + local success, result = wea.conv.kernel_gaussian(width, arg) return success, result end @@ -40,7 +41,7 @@ function worldeditadditions.get_conv_kernel(name, width, height, arg) end -function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size) +function wea.convolve(pos1, pos2, kernel, kernel_size) pos1, pos2 = worldedit.sort_pos(pos1, pos2) local border_size = Vector3.new( @@ -61,10 +62,10 @@ function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size) local node_id_air = minetest.get_content_id("air") - local heightmap, heightmap_size = worldeditadditions.make_heightmap(pos1, pos2, manip, area, data) - local heightmap_conv = worldeditadditions.table.shallowcopy(heightmap) + local heightmap, heightmap_size = wea.terrain.make_heightmap(pos1, pos2, manip, area, data) + local heightmap_conv = wea.table.shallowcopy(heightmap) - worldeditadditions.conv.convolve( + wea.conv.convolve( heightmap_conv, heightmap_size, kernel, @@ -72,11 +73,11 @@ function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size) ) -- print("original") - -- worldeditadditions.format.array_2d(heightmap, (pos2.z - pos1.z) + 1) + -- wea.format.array_2d(heightmap, (pos2.z - pos1.z) + 1) -- print("transformed") - -- worldeditadditions.format.array_2d(heightmap_conv, (pos2.z - pos1.z) + 1) + -- wea.format.array_2d(heightmap_conv, (pos2.z - pos1.z) + 1) - worldeditadditions.apply_heightmap_changes( + wea.terrain.apply_heightmap_changes( pos1, pos2, area, data, heightmap, heightmap_conv, heightmap_size ) diff --git a/worldeditadditions/lib/erode/erode.lua b/worldeditadditions/lib/erode/erode.lua index 7c263b6..be16a9a 100644 --- a/worldeditadditions/lib/erode/erode.lua +++ b/worldeditadditions/lib/erode/erode.lua @@ -1,10 +1,11 @@ -worldeditadditions.erode = {} +local wea = worldeditadditions +wea.erode = {} -dofile(worldeditadditions.modpath.."/lib/erode/snowballs.lua") -dofile(worldeditadditions.modpath.."/lib/erode/river.lua") +dofile(wea.modpath.."/lib/erode/snowballs.lua") +dofile(wea.modpath.."/lib/erode/river.lua") -function worldeditadditions.erode.run(pos1, pos2, algorithm, params) +function wea.erode.run(pos1, pos2, algorithm, params) pos1, pos2 = worldedit.sort_pos(pos1, pos2) local manip, area = worldedit.manip_helpers.init(pos1, pos2) @@ -17,15 +18,15 @@ function worldeditadditions.erode.run(pos1, pos2, algorithm, params) local region_height = (pos2.y - pos1.y) + 1 - local heightmap = worldeditadditions.make_heightmap(pos1, pos2, manip, area, data) - local heightmap_eroded = worldeditadditions.table.shallowcopy(heightmap) + local heightmap = wea.terrain.make_heightmap(pos1, pos2, manip, area, data) + local heightmap_eroded = wea.table.shallowcopy(heightmap) -- print("[erode.run] algorithm: "..algorithm..", params:"); - -- print(worldeditadditions.format.map(params)) - -- worldeditadditions.format.array_2d(heightmap, heightmap_size.x) + -- print(wea.format.map(params)) + -- wea.format.array_2d(heightmap, heightmap_size.x) local success, msg, stats if algorithm == "snowballs" then - success, msg = worldeditadditions.erode.snowballs( + success, msg = wea.erode.snowballs( heightmap, heightmap_eroded, heightmap_size, region_height, @@ -33,7 +34,7 @@ function worldeditadditions.erode.run(pos1, pos2, algorithm, params) ) if not success then return success, msg end elseif algorithm == "river" then - success, msg = worldeditadditions.erode.river( + success, msg = wea.erode.river( heightmap, heightmap_eroded, heightmap_size, region_height, @@ -48,7 +49,7 @@ function worldeditadditions.erode.run(pos1, pos2, algorithm, params) return false, "Error: Unknown algorithm '"..algorithm.."'. Currently implemented algorithms: snowballs (2d; hydraulic-like), river (2d; cellular automata-like; fills potholes and lowers towers). Ideas for algorithms to implement are welcome!" end - success, stats = worldeditadditions.apply_heightmap_changes( + success, stats = wea.terrain.apply_heightmap_changes( pos1, pos2, area, data, heightmap, heightmap_eroded, heightmap_size ) diff --git a/worldeditadditions/lib/erode/snowballs.lua b/worldeditadditions/lib/erode/snowballs.lua index 29853a5..cc10ba5 100644 --- a/worldeditadditions/lib/erode/snowballs.lua +++ b/worldeditadditions/lib/erode/snowballs.lua @@ -1,4 +1,5 @@ -local Vector3 = worldeditadditions.Vector3 +local wea = worldeditadditions +local Vector3 = wea.Vector3 -- Test command: //multi //fp set1 1313 6 5540 //fp set2 1338 17 5521 //erode snowballs @@ -28,7 +29,7 @@ local function snowball(heightmap, normalmap, heightmap_size, startpos, params) end if #hist_velocity > 0 and i > 5 - and worldeditadditions.average(hist_velocity) < 0.03 then + and wea.average(hist_velocity) < 0.03 then -- print("[snowball] It looks like we've stopped") return true, i end @@ -51,11 +52,11 @@ local function snowball(heightmap, normalmap, heightmap_size, startpos, params) velocity.x = params.friction * velocity.x + normalmap[hi].x * params.speed velocity.z = params.friction * velocity.z + normalmap[hi].y * params.speed - -- print("[snowball] now at ("..x..", "..z..") velocity "..worldeditadditions.vector.lengthsquared(velocity)..", sediment "..sediment) - local new_vel_sq = worldeditadditions.vector.lengthsquared(velocity) + -- print("[snowball] now at ("..x..", "..z..") velocity "..wea.vector.lengthsquared(velocity)..", sediment "..sediment) + local new_vel_sq = wea.vector.lengthsquared(velocity) if new_vel_sq > 1 then -- print("[snowball] velocity squared over 1, normalising") - velocity = worldeditadditions.vector.normalize(velocity) + velocity = wea.vector.normalize(velocity) end table.insert(hist_velocity, new_vel_sq) if #hist_velocity > params.velocity_hist_count then table.remove(hist_velocity, 1) end @@ -74,7 +75,7 @@ Note that this *mutates* the given heightmap. @source https://jobtalle.com/simulating_hydraulic_erosion.html ]]-- -function worldeditadditions.erode.snowballs(heightmap_initial, heightmap, heightmap_size, region_height, params_custom) +function wea.erode.snowballs(heightmap_initial, heightmap, heightmap_size, region_height, params_custom) local params = { rate_deposit = 0.03, -- 0.03 rate_erosion = 0.04, -- 0.04 @@ -88,12 +89,12 @@ function worldeditadditions.erode.snowballs(heightmap_initial, heightmap, height count = 25000 } -- Apply the custom settings - worldeditadditions.table.apply(params_custom, params) + wea.table.apply(params_custom, params) -- print("[erode/snowballs] params: ") - -- print(worldeditadditions.format.map(params)) + -- print(wea.format.map(params)) - local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size) + local normals = wea.terrain.calculate_normals(heightmap, heightmap_size) local stats_steps = {} for i = 1, params.count do @@ -111,7 +112,7 @@ function worldeditadditions.erode.snowballs(heightmap_initial, heightmap, height if not success then return false, "Error: Failed at snowball "..i..":"..steps end end - -- print("[snowballs] "..#stats_steps.." snowballs simulated, max "..params.max_steps.." steps, averaged ~"..worldeditadditions.average(stats_steps).."") + -- print("[snowballs] "..#stats_steps.." snowballs simulated, max "..params.max_steps.." steps, averaged ~"..wea.average(stats_steps).."") -- Round everything to the nearest int, since you can't really have -- something like .141592671 of a node @@ -131,15 +132,15 @@ function worldeditadditions.erode.snowballs(heightmap_initial, heightmap, height end if not params.noconv then - local success, matrix = worldeditadditions.get_conv_kernel("gaussian", 3, 3) + local success, matrix = wea.get_conv_kernel("gaussian", 3, 3) if not success then return success, matrix end local matrix_size = Vector3.new(3, 0, 3) - worldeditadditions.conv.convolve( + wea.conv.convolve( heightmap, heightmap_size, matrix, matrix_size ) end - return true, ""..#stats_steps.." snowballs simulated, max "..params.max_steps.." steps (averaged ~"..worldeditadditions.average(stats_steps).." steps)" + return true, ""..#stats_steps.." snowballs simulated, max "..params.max_steps.." steps (averaged ~"..wea.average(stats_steps).." steps)" end diff --git a/worldeditadditions/lib/layers.lua b/worldeditadditions/lib/layers.lua index 80fef4e..8ace75f 100644 --- a/worldeditadditions/lib/layers.lua +++ b/worldeditadditions/lib/layers.lua @@ -35,11 +35,11 @@ function worldeditadditions.layers(pos1, pos2, node_weights, min_slope, max_slop local node_ids, node_ids_count = wea.unwind_node_list(node_weights) - local heightmap, heightmap_size = wea.make_heightmap( + local heightmap, heightmap_size = wea.terrain.make_heightmap( pos1, pos2, manip, area, data ) - local slopemap = wea.calculate_slopes(heightmap, heightmap_size) + local slopemap = wea.terrain.calculate_slopes(heightmap, heightmap_size) -- worldeditadditions.format.array_2d(heightmap, heightmap_size.x) -- print_slopes(slopemap, heightmap_size.x) --luacheck:ignore 311 diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua index d0cd7eb..1885871 100644 --- a/worldeditadditions/lib/noise/apply_2d.lua +++ b/worldeditadditions/lib/noise/apply_2d.lua @@ -3,13 +3,13 @@ local wea = worldeditadditions --- Applies the given noise field to the given heightmap. -- Mutates the given heightmap. --- @param heightmap number[] A table of ZERO indexed numbers representing the heghtmap - see worldeditadditions.make_heightmap(). +-- @param heightmap number[] A table of ZERO indexed numbers representing the heghtmap - see worldeditadditions.terrain.make_heightmap(). -- @param noise number[] An table identical in structure to the heightmap containing the noise values to apply. -- @param heightmap_size {x:number,z:number} A 2d vector representing the size of the heightmap. -- @param region_height number The height of the defined region. -- @param apply_mode string The apply mode to use to apply the noise to the heightmap. -- @returns bool[,string] A boolean value representing whether the application was successful or not. If false, then an error message as a string is also returned describing the error that occurred. -function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos1, pos2, apply_mode) +function wea.noise.apply_2d(heightmap, noise, heightmap_size, pos1, pos2, apply_mode) if type(apply_mode) ~= "string" and type(apply_mode) ~= "number" then return false, "Error: Expected value of type string or number for apply_mode, but received value of type "..type(apply_mode) end @@ -17,12 +17,12 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos local region_height = pos2.y - pos1.y - print("NOISE APPLY_2D\n") - worldeditadditions.format.array_2d(noise, heightmap_size.x) + -- print("NOISE APPLY_2D\n") + wea.format.array_2d(noise, heightmap_size.x) local height = tonumber(apply_mode) - print("DEBUG apply_mode", apply_mode, "as height", height) + -- print("DEBUG apply_mode", apply_mode, "as height", height) for z = heightmap_size.z - 1, 0, -1 do for x = heightmap_size.x - 1, 0, -1 do @@ -53,8 +53,8 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos -- heightmap[(z * heightmap_size.x) + x] = z -- end - print("HEIGHTMAP\n") - worldeditadditions.format.array_2d(heightmap, heightmap_size.x) + -- print("HEIGHTMAP\n") + -- worldeditadditions.format.array_2d(heightmap, heightmap_size.x) return true diff --git a/worldeditadditions/lib/noise/run2d.lua b/worldeditadditions/lib/noise/run2d.lua index 001f8ce..c08657a 100644 --- a/worldeditadditions/lib/noise/run2d.lua +++ b/worldeditadditions/lib/noise/run2d.lua @@ -12,25 +12,25 @@ local wea = worldeditadditions -- @param pos1 Vector pos1 of the defined region -- @param pos2 Vector pos2 of the defined region -- @param noise_params table A noise parameters table. -function worldeditadditions.noise.run2d(pos1, pos2, noise_params) +function wea.noise.run2d(pos1, pos2, noise_params) pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- pos2 will always have the highest co-ordinates now -- Fill in the default params -- print("DEBUG noise_params_custom ", wea.format.map(noise_params)) - noise_params = worldeditadditions.noise.params_apply_default(noise_params) + noise_params = wea.noise.params_apply_default(noise_params) -- print("DEBUG noise_params[1] ", wea.format.map(noise_params[1])) -- Fetch the nodes in the specified area local manip, area = worldedit.manip_helpers.init(pos1, pos2) local data = manip:get_data() - local heightmap_old, heightmap_size = worldeditadditions.make_heightmap( + local heightmap_old, heightmap_size = wea.terrain.make_heightmap( pos1, pos2, manip, area, data ) - local heightmap_new = worldeditadditions.table.shallowcopy(heightmap_old) + local heightmap_new = wea.table.shallowcopy(heightmap_old) local success, noisemap = wea.noise.make_2d( heightmap_size, @@ -49,7 +49,7 @@ function worldeditadditions.noise.run2d(pos1, pos2, noise_params) if not success then return success, message end local stats - success, stats = wea.apply_heightmap_changes( + success, stats = wea.terrain.apply_heightmap_changes( pos1, pos2, area, data, heightmap_old, heightmap_new, diff --git a/worldeditadditions/lib/sculpt/apply.lua b/worldeditadditions/lib/sculpt/apply.lua index 6887c0f..c2da299 100644 --- a/worldeditadditions/lib/sculpt/apply.lua +++ b/worldeditadditions/lib/sculpt/apply.lua @@ -36,7 +36,7 @@ local function apply(pos1, brush_name, height, brush_size) local manip, area = worldedit.manip_helpers.init(pos1_compute, pos2_compute) local data = manip:get_data() - local heightmap, heightmap_size = wea.make_heightmap( + local heightmap, heightmap_size = wea.terrain.make_heightmap( pos1_compute, pos2_compute, manip, area, data @@ -52,7 +52,7 @@ local function apply(pos1, brush_name, height, brush_size) if not success2 then return success2, added end -- 3: Save back to disk & return - local success3, stats = wea.apply_heightmap_changes( + local success3, stats = wea.terrain.apply_heightmap_changes( pos1_compute, pos2_compute, area, data, heightmap_orig, heightmap, diff --git a/worldeditadditions/utils/terrain.lua b/worldeditadditions/utils/terrain.lua deleted file mode 100644 index 8649871..0000000 --- a/worldeditadditions/utils/terrain.lua +++ /dev/null @@ -1,180 +0,0 @@ -local wea = worldeditadditions -local Vector3 = wea.Vector3 - ---- Given a manip object and associates, generates a 2D x/z heightmap. --- Note that pos1 and pos2 should have already been pushed through --- worldedit.sort_pos(pos1, pos2) before passing them to this function. --- @param pos1 Vector Position 1 of the region to operate on --- @param pos2 Vector Position 2 of the region to operate on --- @param manip VoxelManip The VoxelManip object. --- @param area area The associated area object. --- @param data table The associated data object. --- @return table,table The ZERO-indexed heightmap data (as 1 single flat array), followed by the size of the heightmap in the form { z = size_z, x = size_x }. -function worldeditadditions.make_heightmap(pos1, pos2, manip, area, data) - -- z y x (in reverse for little-endian machines) is the preferred loop order, but that isn't really possible here - - local heightmap = {} - local hi = 0 - local changes = { updated = 0, skipped_columns = 0 } - for z = pos1.z, pos2.z, 1 do - for x = pos1.x, pos2.x, 1 do - local found_node = false - -- Scan each column top to bottom - for y = pos2.y+1, pos1.y, -1 do - local i = area:index(x, y, z) - if not (wea.is_airlike(data[i]) or wea.is_liquidlike(data[i])) then - -- It's the first non-airlike node in this column - -- Start heightmap values from 1 (i.e. there's at least 1 node in the column) - heightmap[hi] = (y - pos1.y) + 1 - found_node = true - break - end - end - - if not found_node then heightmap[hi] = -1 end - hi = hi + 1 - end - end - - local heightmap_size = Vector3.new( - (pos2.x - pos1.x) + 1, -- x - 0, -- y - (pos2.z - pos1.z) + 1 -- z - ) - - return heightmap, heightmap_size -end - ---- Calculates a normal map for the given heightmap. --- Caution: This method (like worldeditadditions.make_heightmap) works on --- X AND Z, and NOT x and y. This means that the resulting 3d normal vectors --- will have the z and y values swapped. --- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap(). --- @param heightmap_size int[] The size of the heightmap in the form [ z, x ] --- @return Vector[] The calculated normal map, in the same form as the input heightmap. Each element of the array is a Vector3 instance representing a normal. -function worldeditadditions.calculate_normals(heightmap, heightmap_size) - -- print("heightmap_size: "..heightmap_size.x.."x"..heightmap_size.z) - local result = {} - for z = heightmap_size.z-1, 0, -1 do - for x = heightmap_size.x-1, 0, -1 do - -- Algorithm ref https://stackoverflow.com/a/13983431/1460422 - -- Also ref Vector.mjs, which I implemented myself (available upon request) - local hi = z*heightmap_size.x + x - -- Default to this pixel's height - local up = heightmap[hi] - local down = heightmap[hi] - local left = heightmap[hi] - local right = heightmap[hi] - if z - 1 > 0 then up = heightmap[(z-1)*heightmap_size.x + x] end - if z + 1 < heightmap_size.z-1 then down = heightmap[(z+1)*heightmap_size.x + x] end - if x - 1 > 0 then left = heightmap[z*heightmap_size.x + (x-1)] end - if x + 1 < heightmap_size.x-1 then right = heightmap[z*heightmap_size.x + (x+1)] end - - -- print("[normals] UP | index", (z-1)*heightmap_size.x + x, "z", z, "z-1", z - 1, "up", up, "limit", 0) - -- print("[normals] DOWN | index", (z+1)*heightmap_size.x + x, "z", z, "z+1", z + 1, "down", down, "limit", heightmap_size.x-1) - -- print("[normals] LEFT | index", z*heightmap_size.x + (x-1), "x", x, "x-1", x - 1, "left", left, "limit", 0) - -- print("[normals] RIGHT | index", z*heightmap_size.x + (x+1), "x", x, "x+1", x + 1, "right", right, "limit", heightmap_size.x-1) - - result[hi] = wea.Vector3.new( - left - right, -- x - 2, -- y - Z & Y are flipped - down - up -- z - ):normalise() - - -- print("[normals] at "..hi.." ("..x..", "..z..") normal "..result[hi]) - end - end - return result -end - ---- Converts a 2d heightmap into slope values in radians. --- Convert a radians to degrees by doing (radians*math.pi) / 180 for display, --- but it is STRONGLY recommended to keep all internal calculations in radians. --- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap(). --- @param heightmap_size int[] The size of the heightmap in the form [ z, x ] --- @return Vector[] The calculated slope map, in the same form as the input heightmap. Each element of the array is a (floating-point) number representing the slope in that cell in radians. -function worldeditadditions.calculate_slopes(heightmap, heightmap_size) - local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size) - local slopes = { } - - local up = wea.Vector3.new(0, 1, 0) -- Z & Y are flipped - - for z = heightmap_size.z-1, 0, -1 do - for x = heightmap_size.x-1, 0, -1 do - local hi = z*heightmap_size.x + x - - -- Ref https://stackoverflow.com/a/16669463/1460422 - -- slopes[hi] = wea.Vector3.dot_product(normals[hi], up) - slopes[hi] = math.acos(normals[hi].y) - end - end - - return slopes -end - ---- Applies changes to a heightmap to a Voxel Manipulator data block. --- @param pos1 vector Position 1 of the defined region --- @param pos2 vector Position 2 of the defined region --- @param area VoxelArea The VoxelArea object (see worldedit.manip_helpers.init) --- @param data number[] The node ids data array containing the slice of the Minetest world extracted using the Voxel Manipulator. --- @param heightmap_old number[] The original heightmap from worldeditadditions.make_heightmap. --- @param heightmap_new number[] The new heightmap containing the altered updated values. It is expected that worldeditadditions.table.shallowcopy be used to make a COPY of the data worldeditadditions.make_heightmap for this purpose. Both heightmap_old AND heightmap_new are REQUIRED in order for this function to work. --- @param heightmap_size vector The x / z size of the heightmap. Any y value set in the vector is ignored. --- @returns bool, string|{ added: number, removed: number } A bool indicating whether the operation was successful or not, followed by either an error message as a string (if it was not successful) or a table of statistics (if it was successful). -function worldeditadditions.apply_heightmap_changes(pos1, pos2, area, data, heightmap_old, heightmap_new, heightmap_size) - local stats = { added = 0, removed = 0 } - local node_id_air = minetest.get_content_id("air") - local node_id_ignore = minetest.get_content_id("ignore") - - for z = heightmap_size.z - 1, 0, -1 do - for x = heightmap_size.x - 1, 0, -1 do - local hi = z*heightmap_size.x + x - - local height_old = heightmap_old[hi] - local height_new = heightmap_new[hi] - -- print("[conv/save] hi", hi, "height_old", heightmap_old[hi], "height_new", heightmap_new[hi], "z", z, "x", x, "pos1.y", pos1.y) - - -- Lua doesn't have a continue statement :-/ - if height_old == height_new then - -- noop - elseif height_new < height_old then - local node_id_replace = data[area:index( - pos1.x + x, - pos1.y + height_old + 1, - pos1.z + z - )] - -- Unlikely, but if it can happen, it *will* happen..... - if node_id_replace == node_id_ignore then - node_id_replace = node_id_air - end - stats.removed = stats.removed + (height_old - height_new) - local y = height_new - while y < height_old do - local ci = area:index(pos1.x + x, pos1.y + y, pos1.z + z) - -- print("[conv/save] remove at y", y, "→", pos1.y + y, "current:", minetest.get_name_from_content_id(data[ci])) - if data[ci] ~= node_id_ignore then - data[ci] = node_id_replace - end - y = y + 1 - end - else -- height_new > height_old - -- We subtract one because the heightmap starts at 1 (i.e. 1 = 1 node in the column), but the selected region is inclusive - local node_id = data[area:index(pos1.x + x, pos1.y + (height_old - 1), pos1.z + z)] - -- print("[conv/save] filling with ", node_id, "→", minetest.get_name_from_content_id(node_id)) - - stats.added = stats.added + (height_new - height_old) - local y = height_old - while y < height_new do - local ci = area:index(pos1.x + x, pos1.y + y, pos1.z + z) - -- print("[conv/save] add at y", y, "→", pos1.y + y, "current:", minetest.get_name_from_content_id(data[ci])) - if data[ci] ~= node_id_ignore then - data[ci] = node_id - end - y = y + 1 - end - end - end - end - - return true, stats -end diff --git a/worldeditadditions/utils/terrain/apply_heightmap_changes.lua b/worldeditadditions/utils/terrain/apply_heightmap_changes.lua new file mode 100644 index 0000000..fc1e1d1 --- /dev/null +++ b/worldeditadditions/utils/terrain/apply_heightmap_changes.lua @@ -0,0 +1,71 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + +--- Applies changes to a heightmap to a Voxel Manipulator data block. +-- @param pos1 vector Position 1 of the defined region +-- @param pos2 vector Position 2 of the defined region +-- @param area VoxelArea The VoxelArea object (see worldedit.manip_helpers.init) +-- @param data number[] The node ids data array containing the slice of the Minetest world extracted using the Voxel Manipulator. +-- @param heightmap_old number[] The original heightmap from worldeditadditions.make_heightmap. +-- @param heightmap_new number[] The new heightmap containing the altered updated values. It is expected that worldeditadditions.table.shallowcopy be used to make a COPY of the data worldeditadditions.make_heightmap for this purpose. Both heightmap_old AND heightmap_new are REQUIRED in order for this function to work. +-- @param heightmap_size vector The x / z size of the heightmap. Any y value set in the vector is ignored. +-- @returns bool, string|{ added: number, removed: number } A bool indicating whether the operation was successful or not, followed by either an error message as a string (if it was not successful) or a table of statistics (if it was successful). +local function apply_heightmap_changes(pos1, pos2, area, data, heightmap_old, heightmap_new, heightmap_size) + local stats = { added = 0, removed = 0 } + local node_id_air = minetest.get_content_id("air") + local node_id_ignore = minetest.get_content_id("ignore") + + for z = heightmap_size.z - 1, 0, -1 do + for x = heightmap_size.x - 1, 0, -1 do + local hi = z*heightmap_size.x + x + + local height_old = heightmap_old[hi] + local height_new = heightmap_new[hi] + -- print("[conv/save] hi", hi, "height_old", heightmap_old[hi], "height_new", heightmap_new[hi], "z", z, "x", x, "pos1.y", pos1.y) + + -- Lua doesn't have a continue statement :-/ + if height_old == height_new then + -- noop + elseif height_new < height_old then + local node_id_replace = data[area:index( + pos1.x + x, + pos1.y + height_old + 1, + pos1.z + z + )] + -- Unlikely, but if it can happen, it *will* happen..... + if node_id_replace == node_id_ignore then + node_id_replace = node_id_air + end + stats.removed = stats.removed + (height_old - height_new) + local y = height_new + while y < height_old do + local ci = area:index(pos1.x + x, pos1.y + y, pos1.z + z) + -- print("[conv/save] remove at y", y, "→", pos1.y + y, "current:", minetest.get_name_from_content_id(data[ci])) + if data[ci] ~= node_id_ignore then + data[ci] = node_id_replace + end + y = y + 1 + end + else -- height_new > height_old + -- We subtract one because the heightmap starts at 1 (i.e. 1 = 1 node in the column), but the selected region is inclusive + local node_id = data[area:index(pos1.x + x, pos1.y + (height_old - 1), pos1.z + z)] + -- print("[conv/save] filling with ", node_id, "→", minetest.get_name_from_content_id(node_id)) + + stats.added = stats.added + (height_new - height_old) + local y = height_old + while y < height_new do + local ci = area:index(pos1.x + x, pos1.y + y, pos1.z + z) + -- print("[conv/save] add at y", y, "→", pos1.y + y, "current:", minetest.get_name_from_content_id(data[ci])) + if data[ci] ~= node_id_ignore then + data[ci] = node_id + end + y = y + 1 + end + end + end + end + + return true, stats +end + +return apply_heightmap_changes diff --git a/worldeditadditions/utils/terrain/calculate_normals.lua b/worldeditadditions/utils/terrain/calculate_normals.lua new file mode 100644 index 0000000..fbadf11 --- /dev/null +++ b/worldeditadditions/utils/terrain/calculate_normals.lua @@ -0,0 +1,47 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + + +--- Calculates a normal map for the given heightmap. +-- Caution: This method (like worldeditadditions.make_heightmap) works on +-- X AND Z, and NOT x and y. This means that the resulting 3d normal vectors +-- will have the z and y values swapped. +-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.terrain.make_heightmap(). +-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ] +-- @return Vector[] The calculated normal map, in the same form as the input heightmap. Each element of the array is a Vector3 instance representing a normal. +local function calculate_normals(heightmap, heightmap_size) + -- print("heightmap_size: "..heightmap_size.x.."x"..heightmap_size.z) + local result = {} + for z = heightmap_size.z-1, 0, -1 do + for x = heightmap_size.x-1, 0, -1 do + -- Algorithm ref https://stackoverflow.com/a/13983431/1460422 + -- Also ref Vector.mjs, which I implemented myself (available upon request) + local hi = z*heightmap_size.x + x + -- Default to this pixel's height + local up = heightmap[hi] + local down = heightmap[hi] + local left = heightmap[hi] + local right = heightmap[hi] + if z - 1 > 0 then up = heightmap[(z-1)*heightmap_size.x + x] end + if z + 1 < heightmap_size.z-1 then down = heightmap[(z+1)*heightmap_size.x + x] end + if x - 1 > 0 then left = heightmap[z*heightmap_size.x + (x-1)] end + if x + 1 < heightmap_size.x-1 then right = heightmap[z*heightmap_size.x + (x+1)] end + + -- print("[normals] UP | index", (z-1)*heightmap_size.x + x, "z", z, "z-1", z - 1, "up", up, "limit", 0) + -- print("[normals] DOWN | index", (z+1)*heightmap_size.x + x, "z", z, "z+1", z + 1, "down", down, "limit", heightmap_size.x-1) + -- print("[normals] LEFT | index", z*heightmap_size.x + (x-1), "x", x, "x-1", x - 1, "left", left, "limit", 0) + -- print("[normals] RIGHT | index", z*heightmap_size.x + (x+1), "x", x, "x+1", x + 1, "right", right, "limit", heightmap_size.x-1) + + result[hi] = Vector3.new( + left - right, -- x + 2, -- y - Z & Y are flipped + down - up -- z + ):normalise() + + -- print("[normals] at "..hi.." ("..x..", "..z..") normal "..result[hi]) + end + end + return result +end + +return calculate_normals diff --git a/worldeditadditions/utils/terrain/calculate_slopes.lua b/worldeditadditions/utils/terrain/calculate_slopes.lua new file mode 100644 index 0000000..8da99eb --- /dev/null +++ b/worldeditadditions/utils/terrain/calculate_slopes.lua @@ -0,0 +1,30 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + + +--- Converts a 2d heightmap into slope values in radians. +-- Convert a radians to degrees by doing (radians*math.pi) / 180 for display, +-- but it is STRONGLY recommended to keep all internal calculations in radians. +-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.terrain.make_heightmap(). +-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ] +-- @return Vector[] The calculated slope map, in the same form as the input heightmap. Each element of the array is a (floating-point) number representing the slope in that cell in radians. +local function calculate_slopes(heightmap, heightmap_size) + local normals = wea.terrain.calculate_normals(heightmap, heightmap_size) + local slopes = { } + + local up = wea.Vector3.new(0, 1, 0) -- Z & Y are flipped + + for z = heightmap_size.z-1, 0, -1 do + for x = heightmap_size.x-1, 0, -1 do + local hi = z*heightmap_size.x + x + + -- Ref https://stackoverflow.com/a/16669463/1460422 + -- slopes[hi] = wea.Vector3.dot_product(normals[hi], up) + slopes[hi] = math.acos(normals[hi].y) + end + end + + return slopes +end + +return calculate_slopes diff --git a/worldeditadditions/utils/terrain/init.lua b/worldeditadditions/utils/terrain/init.lua new file mode 100644 index 0000000..c83577e --- /dev/null +++ b/worldeditadditions/utils/terrain/init.lua @@ -0,0 +1,12 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + + +local terrain = { + make_heightmap = dofile(wea.modpath.."/utils/terrain/make_heightmap.lua"), + calculate_normals = dofile(wea.modpath.."/utils/terrain/calculate_normals.lua"), + calculate_slopes = dofile(wea.modpath.."/utils/terrain/calculate_slopes.lua"), + apply_heightmap_changes = dofile(wea.modpath.."/utils/terrain/apply_heightmap_changes.lua") +} + +return terrain diff --git a/worldeditadditions/utils/terrain/make_heightmap.lua b/worldeditadditions/utils/terrain/make_heightmap.lua new file mode 100644 index 0000000..1b3c67b --- /dev/null +++ b/worldeditadditions/utils/terrain/make_heightmap.lua @@ -0,0 +1,50 @@ +local wea = worldeditadditions +local Vector3 = wea.Vector3 + + +--- Given a manip object and associates, generates a 2D x/z heightmap. +-- Note that pos1 and pos2 should have already been pushed through +-- worldedit.sort_pos(pos1, pos2) before passing them to this function. +-- @param pos1 Vector Position 1 of the region to operate on +-- @param pos2 Vector Position 2 of the region to operate on +-- @param manip VoxelManip The VoxelManip object. +-- @param area area The associated area object. +-- @param data table The associated data object. +-- @return table,table The ZERO-indexed heightmap data (as 1 single flat array), followed by the size of the heightmap in the form { z = size_z, x = size_x }. +local function make_heightmap(pos1, pos2, manip, area, data) + -- z y x (in reverse for little-endian machines) is the preferred loop order, but that isn't really possible here + + local heightmap = {} + local hi = 0 + local changes = { updated = 0, skipped_columns = 0 } + for z = pos1.z, pos2.z, 1 do + for x = pos1.x, pos2.x, 1 do + local found_node = false + -- Scan each column top to bottom + for y = pos2.y+1, pos1.y, -1 do + local i = area:index(x, y, z) + if not (wea.is_airlike(data[i]) or wea.is_liquidlike(data[i])) then + -- It's the first non-airlike node in this column + -- Start heightmap values from 1 (i.e. there's at least 1 node in the column) + heightmap[hi] = (y - pos1.y) + 1 + found_node = true + break + end + end + + if not found_node then heightmap[hi] = -1 end + hi = hi + 1 + end + end + + local heightmap_size = Vector3.new( + (pos2.x - pos1.x) + 1, -- x + 0, -- y + (pos2.z - pos1.z) + 1 -- z + ) + + return heightmap, heightmap_size +end + + +return make_heightmap From 56a83572e7eb8a836838181d2c7465a9a935846e Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 02:46:15 +0000 Subject: [PATCH 48/79] fixup --- worldeditadditions/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index ab628ec..229b4a0 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -23,7 +23,7 @@ wea.io = dofile(wea.modpath.."/utils/io.lua") wea.bit = dofile(wea.modpath.."/utils/bit.lua") -wea.terrain = dofile(wea.modpath.."/utils/terrain.lua") +wea.terrain = dofile(wea.modpath.."/utils/terrain/init.lua") dofile(wea.modpath.."/utils/vector.lua") dofile(wea.modpath.."/utils/strings/init.lua") From 934007c07bfb62bb5abe81279f514fa2fce0f9ff Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 03:01:51 +0000 Subject: [PATCH 49/79] //move+ add comment about bug --- worldeditadditions/lib/move.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/worldeditadditions/lib/move.lua b/worldeditadditions/lib/move.lua index 63586f1..076287f 100644 --- a/worldeditadditions/lib/move.lua +++ b/worldeditadditions/lib/move.lua @@ -28,7 +28,6 @@ function worldeditadditions.move(source_pos1, source_pos2, target_pos1, target_p local data_target = manip_target:get_data() -- z y x is the preferred loop order (because CPU cache, since then we're iterating linearly through the data array backwards. This only holds true for little-endian machines however) - for z = source_pos2.z, source_pos1.z, -1 do for y = source_pos2.y, source_pos1.y, -1 do for x = source_pos2.x, source_pos1.x, -1 do @@ -37,14 +36,19 @@ function worldeditadditions.move(source_pos1, source_pos2, target_pos1, target_p local target = source:subtract(offset) local target_i = area_target:index(target.x, target.y, target.z) + data_target[target_i] = data_source[source_i] data_source[source_i] = node_id_air + end end end -- Save the modified nodes back to disk & return -- Note that we save the source region *first* to avoid issues with overlap + + -- BUG: Voxel Manipulators cover a larger area than the source area if the target position is too close the to the source, then the source will be overwritten by the target by accident. + worldedit.manip_helpers.finish(manip_source, data_source) worldedit.manip_helpers.finish(manip_target, data_target) From d252ec46756c5d547b658c7daa2d0fa88f73cf7c Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 03:05:02 +0000 Subject: [PATCH 50/79] wea.move: add potential solution as a comment --- worldeditadditions/lib/move.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/worldeditadditions/lib/move.lua b/worldeditadditions/lib/move.lua index 076287f..ff881e7 100644 --- a/worldeditadditions/lib/move.lua +++ b/worldeditadditions/lib/move.lua @@ -49,6 +49,13 @@ function worldeditadditions.move(source_pos1, source_pos2, target_pos1, target_p -- BUG: Voxel Manipulators cover a larger area than the source area if the target position is too close the to the source, then the source will be overwritten by the target by accident. + -- Potential solution: + -- 1. Grab source & target + -- 2. Perform copy + -- 3. Save move + -- 4. Re-grab source + -- 5. Zero out source, but avoiding touching any nodes that fall within target + worldedit.manip_helpers.finish(manip_source, data_source) worldedit.manip_helpers.finish(manip_target, data_target) From db7602a8b310d2b28425874eb8aeabdb70a0efe9 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 31 Dec 2021 12:44:15 +0000 Subject: [PATCH 51/79] //sculpt, //sculptlist: add to documentation --- Chat-Command-Reference.md | 53 +++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 55 insertions(+) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 402364b..9270172 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -616,6 +616,59 @@ Example invocations: ``` +## `//sculptlist [preview]` +Lists all the available sculpting brushes for use with `//sculpt`. If the `preview` keyword is specified as an argument, then the brushes are also rendered in ASCII as a preview. See [`//sculpt`](#sculpt). + +``` +//sculptlist +//sculptlist preview +``` + + +## `//sculpt [ [ []]]` +Applies a specified brush to the terrain at position 1 with a given height and a given size. Multiple brushes exist (see [`//sculptlist`](#sculptlist)) - and are represented as a 2D grid of values between 0 and 1, which are then scaled to the specified height. The terrain around position 1 is first converted to a 2D heightmap (as in [`//convolve`](#convolve) before the brush "heightmap" is applied to it. + +Similar to [`//sphere`](https://github.com/Uberi/Minetest-WorldEdit/blob/master/ChatCommands.md#sphere-radius-node), [`//cubeapply 10 set`](https://github.com/Uberi/Minetest-WorldEdit/blob/master/ChatCommands.md#cubeapply-sizesizex-sizey-sizez-command-parameters), or [`//cylinder y 5 10 10 dirt`](https://github.com/Uberi/Minetest-WorldEdit/blob/master/ChatCommands.md#cylinder-xyz-length-radius1-radius2-node) (all from [WorldEdit](https://content.minetest.net/packages/sfan5/worldedit/)), but has a number of added advantages: + + - No accidental overhangs + - Multiple custom brushes (see below on how you can add your own!) + +While sculpting brushes cannot yet be rotated, this is a known issue. Rotating sculpting brushes will be implemented in a future version of WorldEditAdditions. + +``` +//sculpt +//sculpt default 10 25 +//sculpt ellipse +//sculpt circle 5 50 +``` + +### Create your own brushes +2 types of brush exist: + +1. Dynamic (lua-generated) brushes +2. Static brushes + +All brushes are located in `worldeditadditions/lib/sculpt/brushes` (relative to the root of WorldEditAdditions' installation directory). + +Lua-generated brushes are not the focus here, but are a file with the extension `.lua` and return a function that returns a brush - see other existing Lua-generated brushes for examples (and don't forget to update `worldeditadditions/lib/sculpt/.init.lua`). + +Static brushes on the other hand are simply a list of tab-separated values arranged in a grid. For example, here is a simple brush: + +```tsv +0 1 0 +1 2 1 +0 1 0 +``` + +Values are automatically rescaled to be between 0 and 1 based on the minimum and maximum values, so don't worry about which numbers to use. Static brushes are saved with the file extension `.brush.tsv` in the aforementioned directory, and are automatically rescanned when your Minetest server starts. While they can't be rescaled automatically to fix a target size (without creating multiple variants of a brush manually of course, though this may be implemented in the future), static brushes are much easier to create than dynamic brushes. + +To assist with the creation of static brushes, a tool exists to convert any image to a static brush: + + + +The tool operates on the **alpha channel only**, so it's recommended to use an image format that supports transparency. All colours in the R, G, and B channels are ignored. + + ## Flora