From acb288b984699f6d404a49675bc14e415be7419d Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Fri, 21 Aug 2020 13:27:40 +0100 Subject: [PATCH] Fix a ton of bugs but there are lots more to go..... --- worldeditadditions/init.lua | 3 +- worldeditadditions/lib/conv/conv.lua | 42 ++----------- worldeditadditions/lib/erode/erode.lua | 36 +++++++++++ worldeditadditions/lib/erode/snowballs.lua | 59 ++++++++++--------- worldeditadditions/utils/strings.lua | 27 +++++++++ worldeditadditions/utils/terrain.lua | 58 ++++++++++++++++-- .../{utils.lua => utils/vector.lua} | 10 ++-- .../commands/erode.lua | 43 ++++++++++++++ worldeditadditions_commands/init.lua | 1 + 9 files changed, 202 insertions(+), 77 deletions(-) rename worldeditadditions/{utils.lua => utils/vector.lua} (84%) create mode 100644 worldeditadditions_commands/commands/erode.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 571d68d..a7c10f8 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -7,6 +7,7 @@ worldeditadditions = {} worldeditadditions.modpath = minetest.get_modpath("worldeditadditions") +dofile(worldeditadditions.modpath.."/utils/vector.lua") dofile(worldeditadditions.modpath.."/utils/strings.lua") dofile(worldeditadditions.modpath.."/utils/numbers.lua") dofile(worldeditadditions.modpath.."/utils/nodes.lua") @@ -14,7 +15,6 @@ dofile(worldeditadditions.modpath.."/utils/tables.lua") dofile(worldeditadditions.modpath.."/utils/terrain.lua") dofile(worldeditadditions.modpath.."/utils/raycast_adv.lua") -- For the farwand -dofile(worldeditadditions.modpath.."/utils.lua") dofile(worldeditadditions.modpath.."/lib/floodfill.lua") dofile(worldeditadditions.modpath.."/lib/overlay.lua") dofile(worldeditadditions.modpath.."/lib/layers.lua") @@ -25,6 +25,7 @@ dofile(worldeditadditions.modpath.."/lib/replacemix.lua") dofile(worldeditadditions.modpath.."/lib/maze2d.lua") dofile(worldeditadditions.modpath.."/lib/maze3d.lua") dofile(worldeditadditions.modpath.."/lib/conv/conv.lua") +dofile(worldeditadditions.modpath.."/lib/erode/erode.lua") dofile(worldeditadditions.modpath.."/lib/count.lua") diff --git a/worldeditadditions/lib/conv/conv.lua b/worldeditadditions/lib/conv/conv.lua index eff91ab..93b0dbc 100644 --- a/worldeditadditions/lib/conv/conv.lua +++ b/worldeditadditions/lib/conv/conv.lua @@ -75,45 +75,11 @@ function worldeditadditions.convolve(pos1, pos2, kernel, kernel_size) -- worldeditadditions.print_2d(heightmap, (pos2.z - pos1.z) + 1) -- print("transformed") -- worldeditadditions.print_2d(heightmap_conv, (pos2.z - pos1.z) + 1) - -- It seems to be convolving as intended, but something's probably getting lost in translation below - for z = heightmap_size[0], 0, -1 do - for x = heightmap_size[1], 0, -1 do - local hi = z*heightmap_size[1] + x - - - local height_old = heightmap[hi] - local height_new = heightmap_conv[hi] - -- print("[conv/save] hi", hi, "height_old", heightmap[hi], "height_new", heightmap_conv[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 - 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])) - data[ci] = node_id_air - 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])) - data[ci] = node_id - y = y + 1 - end - end - end - end + worldeditadditions.apply_heightmap_changes( + pos1, pos2, area, data, + heightmap, heightmap_conv, heightmap_size + ) worldedit.manip_helpers.finish(manip, data) diff --git a/worldeditadditions/lib/erode/erode.lua b/worldeditadditions/lib/erode/erode.lua index d30118d..4e87ffe 100644 --- a/worldeditadditions/lib/erode/erode.lua +++ b/worldeditadditions/lib/erode/erode.lua @@ -1,3 +1,39 @@ worldeditadditions.erode = {} dofile(worldeditadditions.modpath.."/lib/erode/snowballs.lua") + + +function worldeditadditions.erode.run(pos1, pos2, algorithm, params) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local manip, area = worldedit.manip_helpers.init(pos1, pos2) + local data = manip:get_data() + + local heightmap_size = {} + heightmap_size[0] = (pos2.z - pos1.z) + 1 + heightmap_size[1] = (pos2.x - pos1.x) + 1 + + local heightmap = worldeditadditions.make_heightmap(pos1, pos2, manip, area, data) + local heightmap_eroded = worldeditadditions.shallowcopy(heightmap) + + print("[erode.run] algorithm: "..algorithm..", params:"); + print(worldeditadditions.map_stringify(params)) + worldeditadditions.print_2d(heightmap, heightmap_size[1]) + + if algorithm == "snowballs" then + local success, msg = worldeditadditions.erode.snowballs(heightmap_eroded, heightmap_size, params) + if not success then return success, msg end + else + return false, "Error: Unknown algorithm '"..algorithm.."'. Currently implemented algorithms: snowballs (2d; hydraulic-like). Ideas for algorithms to implement are welcome!" + end + + local success, msg = worldeditadditions.apply_heightmap_changes( + pos1, pos2, area, data, + heightmap, heightmap_eroded, heightmap_size + ) + if not success then return success, msg end + + worldedit.manip_helpers.finish(manip, data) + + return true, stats +end diff --git a/worldeditadditions/lib/erode/snowballs.lua b/worldeditadditions/lib/erode/snowballs.lua index f9fc82e..0cf2b7c 100644 --- a/worldeditadditions/lib/erode/snowballs.lua +++ b/worldeditadditions/lib/erode/snowballs.lua @@ -1,32 +1,3 @@ ---[[ -2D erosion algorithm based on snowballs -Note that this *mutates* the given heightmap. -@source https://jobtalle.com/simulating_hydraulic_erosion.html - -]]-- -function worldeditadditions.erode.snowballs(heightmap, heightmap_size, params) - -- Apply the default settings - worldeditadditions.table_apply({ - rate_deposit = 0.03, - rate_erosion = 0.04, - friction = 0.07, - speed = 0.15, - radius = 0.8, - snowball_max_steps = 80, - scale_iterations = 0.04, - drops_per_cell = 0.4, - snowball_count = 50000 - }, params) - - local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size) - - for i = 1, params.snowball_count do - snowball( - heightmap, normals, heightmap_size, - { x = math.random() } - ) - end -end local function snowball(heightmap, normalmap, heightmap_size, startpos, params) local offset = { -- Random jitter - apparently helps to avoid snowballs from entrenching too much @@ -72,3 +43,33 @@ local function snowball(heightmap, normalmap, heightmap_size, startpos, params) heightmap[i] = math.floor(heightmap[i] + 0.5) end end + +--[[ +2D erosion algorithm based on snowballs +Note that this *mutates* the given heightmap. +@source https://jobtalle.com/simulating_hydraulic_erosion.html + +]]-- +function worldeditadditions.erode.snowballs(heightmap, heightmap_size, params) + -- Apply the default settings + worldeditadditions.table_apply({ + rate_deposit = 0.03, + rate_erosion = 0.04, + friction = 0.07, + speed = 0.15, + radius = 0.8, + snowball_max_steps = 80, + scale_iterations = 0.04, + drops_per_cell = 0.4, + snowball_count = 50000 + }, params) + + local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size) + + for i = 1, params.snowball_count do + snowball( + heightmap, normals, heightmap_size, + { x = math.random() } + ) + end +end diff --git a/worldeditadditions/utils/strings.lua b/worldeditadditions/utils/strings.lua index 52a0b2b..f3e75fa 100644 --- a/worldeditadditions/utils/strings.lua +++ b/worldeditadditions/utils/strings.lua @@ -86,6 +86,7 @@ end -- @param tbl number[] The ZERO-indexed list of numbers -- @param width number The width of 2D array. function worldeditadditions.print_2d(tbl, width) + print("==== count: "..#tbl..", width:"..width.." ====") local display_width = 1 for _i,value in pairs(tbl) do display_width = math.max(display_width, #tostring(value)) @@ -209,6 +210,32 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list) return true, result end +function worldeditadditions.parse_map(params_text) + local result = {} + local parts = worldeditadditions.split(params_text, "%s+", false) + + local last_key = nil + for i, part in ipairs(parts) do + if i % 2 == 1 then + -- Try converting to a number to see if it works + local part_converted = tonumber(part) + if as_number == nil then part_converted = part end + result[last_key] = part + else + last_key = part + end + end + return true, result +end + +function worldeditadditions.map_stringify(map) + local result = {} + for key, value in pairs(map) do + table.insert(key.."\t"..value) + end + return table.concat(result, "\n") +end + --- Converts a float milliseconds into a human-readable string. -- Ported from PHP human_time from Pepperminty Wiki: https://github.com/sbrl/Pepperminty-Wiki/blob/fa81f0d/core/05-functions.php#L82-L104 -- @param ms float The number of milliseconds to convert. diff --git a/worldeditadditions/utils/terrain.lua b/worldeditadditions/utils/terrain.lua index 2148f39..c941608 100644 --- a/worldeditadditions/utils/terrain.lua +++ b/worldeditadditions/utils/terrain.lua @@ -45,9 +45,10 @@ end -- @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 3D Vector (i.e. { x, y, z }) representing a normal. function worldeditadditions.calculate_normals(heightmap, heightmap_size) + print("heightmap_size: "..heightmap_size[1].."x"..heightmap_size[0]) local result = {} - for z = heightmap_size[0], 0, -1 do - for x = heightmap_size[1], 0, -1 do + for z = heightmap_size[0]-1, 0, -1 do + for x = heightmap_size[1]-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[1] + x @@ -57,9 +58,14 @@ function worldeditadditions.calculate_normals(heightmap, heightmap_size) local left = heightmap[hi] local right = heightmap[hi] if z - 1 > 0 then up = heightmap[(z-1)*heightmap_size[1] + x] end - if z + 1 < heightmap_size[1] then down = heightmap[(z+1)*heightmap_size[1] + x] end + if z + 1 < heightmap_size[0]-1 then down = heightmap[(z+1)*heightmap_size[1] + x] end if x - 1 > 0 then left = heightmap[z*heightmap_size[1] + (x-1)] end - if x + 1 < heightmap_size[0] then right = heightmap[z*heightmap_size[1] + (x+1)] end + if x + 1 < heightmap_size[1]-1 then right = heightmap[z*heightmap_size[1] + (x+1)] end + + print("[normals] UP | index", (z-1)*heightmap_size[1] + x, "z", z, "z-1", z - 1, "up", up, "limit", 0) + print("[normals] DOWN | index", (z+1)*heightmap_size[1] + x, "z", z, "z+1", z + 1, "down", down, "limit", heightmap_size[1]-1) + print("[normals] LEFT | index", z*heightmap_size[1] + (x-1), "x", x, "x-1", x - 1, "left", left, "limit", 0) + print("[normals] RIGHT | index", z*heightmap_size[1] + (x+1), "x", x, "x+1", x + 1, "right", right, "limit", heightmap_size[1]-1) result[hi] = worldeditadditions.vector.normalize({ x = left - right, @@ -70,3 +76,47 @@ function worldeditadditions.calculate_normals(heightmap, heightmap_size) end return result end + +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") + + for z = heightmap_size[0], 0, -1 do + for x = heightmap_size[1], 0, -1 do + local hi = z*heightmap_size[1] + x + + local height_old = heightmap[hi] + local height_new = heightmap_new[hi] + -- print("[conv/save] hi", hi, "height_old", heightmap[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 + 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])) + data[ci] = node_id_air + 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])) + data[ci] = node_id + y = y + 1 + end + end + end + end + + return true, stats +end diff --git a/worldeditadditions/utils.lua b/worldeditadditions/utils/vector.lua similarity index 84% rename from worldeditadditions/utils.lua rename to worldeditadditions/utils/vector.lua index 3ab8504..73e86f1 100644 --- a/worldeditadditions/utils.lua +++ b/worldeditadditions/utils/vector.lua @@ -16,12 +16,12 @@ end -- This method does *not* mutate. -- @param v Vector The vector to calculate from. -- @return Vector A new normalised vector. -function worldeditadditions.vector.normalise(v) - local length = math.sqrt(worldeditadditions.lengthsquared(v)) +function worldeditadditions.vector.normalize(v) + local length = math.sqrt(worldeditadditions.vector.lengthsquared(v)) return { - x = x / length, - y = y / length, - z = z / length + x = v.x / length, + y = v.y / length, + z = v.z / length } end diff --git a/worldeditadditions_commands/commands/erode.lua b/worldeditadditions_commands/commands/erode.lua new file mode 100644 index 0000000..81b98e1 --- /dev/null +++ b/worldeditadditions_commands/commands/erode.lua @@ -0,0 +1,43 @@ +-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ +worldedit.register_command("erode", { + params = "[ [ []] [ []] ...]", + description = "Runs the specified erosion algorithm over the given defined region. This may occur in 2d or 3d. Currently implemented algorithms: snowballs (default;2d hydraulic-like). Also optionally takes an arbitrary set of key - value pairs representing parameters to pass to the algorithm. See the full documentation for details.", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + if not params_text or params_text == "" then + return true, "snowballs", {} + end + + if params_text:find("%s") == nil then + return true, params_text, {} + end + + local algorithm, params = params_text:match("([^%s]+)%s(.+)") + if algorithm == nil then + return false, "Failed to split params_text into 2 parts (this is probably a bug)" + end + + local success, map = worldeditadditions.parse_map(params) + if not success then return success, map end + return true, algorithm, map + end, + nodes_needed = function(name) + return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) + end, + func = function(name, algorithm, params) + local start_time = worldeditadditions.get_ms_time() + local success, stats = worldeditadditions.erode.run( + worldedit.pos1[name], worldedit.pos2[name], + algorithm, params + ) + local time_taken = worldeditadditions.get_ms_time() - start_time + + minetest.log("action", name .. " used //erode "..algorithm.." at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s") + return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. worldeditadditions.human_time(time_taken) + end +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 91c7366..229f460 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -30,6 +30,7 @@ dofile(we_c.modpath.."/commands/walls.lua") dofile(we_c.modpath.."/commands/maze.lua") dofile(we_c.modpath.."/commands/replacemix.lua") dofile(we_c.modpath.."/commands/convolve.lua") +dofile(we_c.modpath.."/commands/erode.lua") dofile(we_c.modpath.."/commands/count.lua")