diff --git a/worldeditadditions/lib/erode/erode.lua b/worldeditadditions/lib/erode/erode.lua index 2eb54eb..6184e6a 100644 --- a/worldeditadditions/lib/erode/erode.lua +++ b/worldeditadditions/lib/erode/erode.lua @@ -24,14 +24,27 @@ function worldeditadditions.erode.run(pos1, pos2, algorithm, params) -- worldeditadditions.format.array_2d(heightmap, heightmap_size.x) local success, msg, stats if algorithm == "snowballs" then - success, msg = worldeditadditions.erode.snowballs(heightmap, heightmap_eroded, heightmap_size, region_height, params) + success, msg = worldeditadditions.erode.snowballs( + heightmap, heightmap_eroded, + heightmap_size, + region_height, + params + ) + if not success then return success, msg end + elseif algorithm == "river" then + success, msg = worldeditadditions.erode.river( + heightmap, heightmap_eroded, + heightmap_size, + region_height, + params + ) if not success then return success, msg end else -- FUTURE: Add a new "river" algorithm here that: -- * Fills in blocks that are surrounded on more than 3 horizontal sides -- * Destroys blocks that have no horizontal neighbours -- A bit like cellular automata actually. - return false, "Error: Unknown algorithm '"..algorithm.."'. Currently implemented algorithms: snowballs (2d; hydraulic-like). Ideas for algorithms to implement are welcome!" + 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( diff --git a/worldeditadditions/lib/erode/river.lua b/worldeditadditions/lib/erode/river.lua new file mode 100644 index 0000000..aa788fb --- /dev/null +++ b/worldeditadditions/lib/erode/river.lua @@ -0,0 +1,100 @@ +local wea = worldeditadditions + +--- Parses a comma-separated side numbers list out into a list of numbers. +-- @param list string The command separated list to parse. +-- @returns number[] A list of side numbers. +local function parse_sides_list(list) + list = list:gsub("%s", "") -- Spaces are not permitted + return wea.table_unique(wea.table_map( + wea.split(list, ","), + function(value) return tonumber(value) end + )) +end + +function worldeditadditions.erode.river(heightmap_initial, heightmap, heightmap_size, region_height, params_custom) + local params = { + steps = 1, -- Number of rounds/passes of the algorithm to run + remove_sides = "0,1", -- Cells with this many adjacent horizontal neighbours will be removed + fill_sides = "4,3" -- Cells with this many adjaect horizontal neighbours will be filled in + } + -- Apply the custom settings + wea.table_apply(params_custom, params) + + params.remove_sides = parse_sides_list(params.remove_sides) + params.fill_sides = parse_sides_list(params.fill_sides) + + local timings = {} + local filled = 0 + local removed = 0 + for i=1,params.steps do + local time_start = wea.get_ms_time() + + 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 thisheight = heightmap[hi] + + local sides = 0 + local adjacent_heights = { } + if x > 0 then + table.insert(adjacent_heights, heightmap[z*heightmap_size.x + x-1]) + if heightmap[z*heightmap_size.x + x-1] >= thisheight then + sides = sides + 1 + end + end + if x < heightmap_size.x - 1 then + table.insert(adjacent_heights, heightmap[z*heightmap_size.x + x+1]) + if heightmap[z*heightmap_size.x + x+1] >= thisheight then + sides = sides + 1 + end + end + if z > 0 then + table.insert(adjacent_heights, heightmap[(z-1)*heightmap_size.x + x]) + if heightmap[(z-1)*heightmap_size.x + x] >= thisheight then + sides = sides + 1 + end + end + if z < heightmap_size.z - 1 then + table.insert(adjacent_heights, heightmap[(z+1)*heightmap_size.x + x]) + if heightmap[(z+1)*heightmap_size.x + x] >= thisheight then + sides = sides + 1 + end + end + + local action = "none" + for i,sidecount in ipairs(params.fill_sides) do + if sidecount == sides then + action = "fill" + break + end + end + for i,sidecount in ipairs(params.remove_sides) do + if sidecount == sides then + action = "remove" + break + end + end + + if action == "fill" then + local above_us = wea.table_filter(adjacent_heights, + function(height) return height > thisheight end + ) + local above_us_next = wea.min(above_us) + heightmap[hi] = above_us_next + filled = filled + 1 + elseif action == "remove" then + local below_us = wea.table_filter(adjacent_heights, + function(height) return height < thisheight end + ) + local below_us_next = wea.max(below_us) + heightmap[hi] = below_us_next + removed = removed + 1 + end + end + end + + table.insert(timings, wea.get_ms_time() - time_start) + end + + return true, params.steps.." steps made, raising "..filled.." and lowering "..removed.." columns in "..wea.format.human_time(wea.sum(timings)).." (~"..wea.format.human_time(wea.average(timings)).." per step)" +end