Compare commits

...

5 Commits

Author SHA1 Message Date
sfan5 d55b4ad71e Add mod.conf 2021-02-24 11:36:55 +01:00
sfan5 4807907db9 Fix broken //ores depth parameter 2020-04-07 13:29:46 +02:00
sfan5 9311367e97 Update for recent WorldEdit changes 2019-12-19 16:18:43 +01:00
sfan5 29b80bdef3 smooth: add iterations parameter 2018-05-29 12:05:46 +02:00
sfan5 7949d8591c smooth: move algorithm to separate function 2018-05-29 11:52:48 +02:00
4 changed files with 120 additions and 91 deletions

View File

@ -24,10 +24,10 @@ Meaning: Add grass on top and change deeper nodes to stone.
Let the map generator generate ores in the selected region, this works with whatever ores mods have registered.
The optional argument specifies which height is used for deciding the ores to generate and defaults to sea level (0).
* `//smooth`
* `//smooth [iterations]`
Smooth terrain in the current selection. When adding nodes, the type of the top-most node in the column is used.
This uses an exponentially weighted moving average (EWMA) and should be ran 1 or 2 times to produce good results.
This uses an exponentially weighted moving average (EWMA) and should be ran with 1 (the default) or 2 iterations to produce good results.
* `//smoothbrush`

View File

@ -1,3 +0,0 @@
worldedit
worldedit_commands
worldedit_brush?

200
init.lua
View File

@ -1,5 +1,10 @@
local mh = worldedit.manip_helpers
assert(worldedit.register_command,
"Your WorldEdit installation is out of date, "..
"please update to the latest version from git"..
"to run we_env.")
---------------------------------------------
-- manipulations
---------------------------------------------
@ -123,7 +128,7 @@ local function ores(pos1, pos2, pretend_y, try)
local oreids = {}
for _, def in pairs(minetest.registered_ores) do
if table.indexof(disallow_oretypes, def.ore_type) == -1 then
oreids[#oreids + 1] = minetest.get_content_id(def.ore)
oreids[minetest.get_content_id(def.ore)] = true
end
end
@ -153,8 +158,9 @@ local function ores(pos1, pos2, pretend_y, try)
local index_y = index_x + (offset.y + y) * stride.y
for z = 0, dim.z-1 do
local index_z = index_y + (offset.z + z) * stride.z
if table.indexof(oreids, data2[index_z]) ~= -1 then
data[index_z] = data2[index_z]
local cid = data2[index_z]
if oreids[cid] then
data[index_z] = cid
count = count + 1
end
end
@ -185,43 +191,7 @@ end
local EWMA_alpha = 0.45
local WEIGHT = {orig=0.2, x=0.4, z=0.4}
local function smooth(pos1, pos2, deadzone)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
if dim.x < 2 or dim.y < 2 or dim.z < 2 then return 0 end
local manip, area = mh.init(pos1, pos2)
local data = manip:get_data()
local stride = {x=1, y=area.ystride, z=area.zstride}
local offset = vector.subtract(pos1, area.MinEdge)
local c_air = minetest.get_content_id("air")
local c_dirt = minetest.get_content_id("default:dirt")
-- read heightmap from data
local heightmap = {}
local hstride = {x=1, z=dim.x}
for x = 0, dim.x-1 do
for z = 0, dim.z-1 do
heightmap[x + (z * hstride.z) + 1] = 0
end
end
for x = 0, dim.x-1 do
local index_x = offset.x + x + 1 -- +1 for 1-based indexing
for z = 0, dim.z-1 do
local index_z = index_x + (offset.z + z) * stride.z
local y = dim.y-1
while y >= 0 do
if data[index_z + (offset.y + y) * stride.y] ~= c_air then
heightmap[x + (z * hstride.z) + 1] = y + 1
break
end
y = y - 1
end
end
end
local ewma = function(heightmap, hstride, dim, out)
-- calculate EWMA for each x/z slice
local slice_x, slice_z = {}, {}
for x = 0, dim.x-1 do -- x+
@ -277,7 +247,68 @@ local function smooth(pos1, pos2, deadzone)
return slice_z[z+1][x+1]
end)--]]
-- adjust actual heights based on results
-- calculate actual heights
for x = 0, dim.x-1 do
for z = 0, dim.z-1 do
local hindex = x + (z * hstride.z) + 1
local old_height = heightmap[hindex]
local new_height = math.floor(
old_height * WEIGHT.orig +
slice_x[x+1][z+1] * WEIGHT.x +
slice_z[z+1][x+1] * WEIGHT.z +
0.5
)
out[hindex] = new_height
end
end
end
local function smooth(pos1, pos2, deadzone, iterations)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
if dim.x < 2 or dim.y < 2 or dim.z < 2 then return 0 end
local manip, area = mh.init(pos1, pos2)
local data = manip:get_data()
local stride = {x=1, y=area.ystride, z=area.zstride}
local offset = vector.subtract(pos1, area.MinEdge)
local c_air = minetest.get_content_id("air")
local c_dirt = minetest.get_content_id("default:dirt")
-- read heightmap from data
local heightmap = {}
local hstride = {x=1, z=dim.x}
for x = 0, dim.x-1 do
for z = 0, dim.z-1 do
heightmap[x + (z * hstride.z) + 1] = 0
end
end
for x = 0, dim.x-1 do
local index_x = offset.x + x + 1 -- +1 for 1-based indexing
for z = 0, dim.z-1 do
local index_z = index_x + (offset.z + z) * stride.z
local y = dim.y-1
while y >= 0 do
if data[index_z + (offset.y + y) * stride.y] ~= c_air then
heightmap[x + (z * hstride.z) + 1] = y + 1
break
end
y = y - 1
end
end
end
-- apply algorithm
local heightmap_new = {}
ewma(heightmap, hstride, dim, heightmap_new)
for i = 2, iterations do
ewma(heightmap_new, hstride, dim, heightmap_new)
end
-- adjust actual heights
local count = 0
for x = 0, dim.x-1 do
local index_x = offset.x + x + 1 -- +1 for 1-based indexing
@ -288,13 +319,9 @@ local function smooth(pos1, pos2, deadzone)
if x < deadzone.x or x > dim.x-1 - deadzone.x then noop = true end
if z < deadzone.z or z > dim.z-1 - deadzone.z then noop = true end
local old_height = heightmap[x + (z * hstride.z) + 1]
local new_height = math.floor(
old_height * WEIGHT.orig +
slice_x[x+1][z+1] * WEIGHT.x +
slice_z[z+1][x+1] * WEIGHT.z +
0.5
)
local hindex = x + (z * hstride.z) + 1
local old_height = heightmap[hindex]
local new_height = heightmap_new[hindex]
if noop then
-- do nothing (deadzone)
@ -335,63 +362,64 @@ end
-- chat commands
---------------------------------------------
minetest.register_chatcommand("/fall", {
local check_region = function(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
worldedit.register_command("fall", {
params = "",
description = "Apply gravity to all falling nodes in current WorldEdit region",
description = "Apply gravity to all falling nodes in selected region",
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
local count = fall(pos1, pos2)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
minetest.register_chatcommand("/populate", {
worldedit.register_command("populate", {
params = "",
description = "Populate dirt in current WorldEdit region",
description = "Populate dirt in selected region",
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
local count = populate(pos1, pos2)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
minetest.register_chatcommand("/ores", {
worldedit.register_command("ores", {
params = "",
description = "Generate ores in current WorldEdit region",
description = "Generate ores in selected region",
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
nodes_needed = check_region,
parse = function(param)
return true, tonumber(param) or 0
end,
func = function(name, depth)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
local depth = param ~= "" and tonumber(param) or 0
local count = ores(pos1, pos2, depth)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
minetest.register_chatcommand("/smooth", {
params = "",
description = "Smooth terrain in current WorldEdit region",
worldedit.register_command("smooth", {
params = "[iterations]",
description = "Smooth terrain in selected region",
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
nodes_needed = check_region,
parse = function(param)
return true, tonumber(param) or 1
end,
func = function(name, iterations)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
local count = smooth(pos1, pos2, {x=0, z=0})
local count = smooth(pos1, pos2, {x=0, z=0}, iterations)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
@ -410,12 +438,12 @@ if minetest.registered_items["worldedit:brush"] == nil then
end
local internal_name = "_smooth_brush_internal_do_not_use"
minetest.register_chatcommand("/" .. internal_name, {
worldedit.register_command(internal_name, {
params = "",
privs = {worldedit=true},
func = function(name, param)
require_pos = 1,
func = function(name)
local pos = worldedit.pos1[name]
assert(pos ~= nil)
-- Only modify an 10*10 area but take heights from 14*14 into consideration
local dist, dead = 10, 4
@ -459,18 +487,18 @@ minetest.register_chatcommand("/" .. internal_name, {
end
end
smooth(pos1, pos2, {x=dead, z=dead})
smooth(pos1, pos2, {x=dead, z=dead}, 1)
--[[worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.mark_region(name)--]]
worldedit.marker_update(name)--]]
end,
})
minetest.register_chatcommand("/smoothbrush", {
worldedit.register_command("smoothbrush", {
privs = {worldedit=true},
params = "",
description = "Assign smoothing action to WorldEdit brush item",
func = function(name, param)
func = function(name)
local itemstack = minetest.get_player_by_name(name):get_wielded_item()
if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
worldedit.player_notify(name, "Not holding brush item.")

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = we_env
description = Tools to aid the forming of "natural" terrain by hand using WorldEdit
depends = worldedit, worldedit_commands
optional_depends = worldedit_brush