we_env/init.lua

517 lines
14 KiB
Lua

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
---------------------------------------------
local function fall(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
if dim.y == 1 then return 0 end
local manip, area = mh.init(pos1, pos2)
local data = manip:get_data()
local count = 0
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")
for x = 0, dim.x-1 do
local index_x = offset.x + x + 1 -- +1 for 1-based indexing
for z = 0, dim.z do
local index_z = index_x + (offset.z + z) * stride.z
local y = 0
local fall_height = 0
while y < dim.y do
local index = index_z + (offset.y + y) * stride.y
local ndef = minetest.registered_nodes[minetest.get_name_from_content_id(data[index])]
local did_fall = false
if ndef ~= nil then
if ndef.groups.falling_node ~= nil and fall_height > 0 then
-- move all nodes above down by `fall_height`
-- FIXME: move meta & param2 too
for y2 = y, dim.y-1 do
local index2 = index_z + (offset.y + y2) * stride.y
data[index2 - stride.y * fall_height] = data[index2]
data[index2] = c_air
end
count = count + (dim.y - y)
did_fall = true
elseif not ndef.walkable then
fall_height = fall_height + 1
else -- walkable and won't fall
fall_height = 0
end
end
if did_fall then
-- restart processing from node above the one that fell
y = y - fall_height + 1
fall_height = 0
else
y = y + 1
end
end
end
end
mh.finish(manip, data)
return count
end
local function populate(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
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 count = 0
local c_air = minetest.get_content_id("air")
local c_dirt = minetest.get_content_id("default:dirt")
local c_grass = minetest.get_content_id("default:dirt_with_grass")
local c_stone = minetest.get_content_id("default:stone")
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
local last_was_air = false
local depth = 0
while y >= 0 do
local index = index_z + (offset.y + y) * stride.y
if data[index] == c_dirt then
depth = depth + 1
if last_was_air then
data[index] = c_grass
count = count + 1
elseif depth > 3 then
data[index] = c_stone
count = count + 1
end
else
depth = 0
end
last_was_air = data[index] == c_air
y = y - 1
end
end
end
mh.finish(manip, data)
return count
end
local function ores(pos1, pos2, pretend_y, try)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
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)
-- find out which ores we want
local disallow_oretypes = {"sheet", "blob", "stratum"}
local oreids = {}
for _, def in pairs(minetest.registered_ores) do
if table.indexof(disallow_oretypes, def.ore_type) == -1 then
oreids[minetest.get_content_id(def.ore)] = true
end
end
-- create a second manip
local voff = vector.new(0, 0, 0)
voff.x = area.MinEdge.x - math.random(-512, 512)*16
voff.y = area.MinEdge.y - math.floor(pretend_y/16)*16 -- ensure same alignment(!)
voff.z = area.MinEdge.z - math.random(-512, 512)*16
local manip2 = VoxelManip(vector.subtract(area.MinEdge, voff), vector.subtract(area.MaxEdge, voff))
-- copy data & generate ores inside that
local data2 = manip2:get_data()
for i = 1, area:getVolume() do
data2[i] = data[i]
end
manip2:set_data(data2)
local tmp1, tmp2 = manip2:get_emerged_area()
minetest.generate_ores(manip2, tmp1, tmp2) -- nothing works if you omit the last two params
-- apply the changes we want
local count = 0
manip2:get_data(data2)
for x = 0, dim.x-1 do
local index_x = offset.x + x + 1 -- +1 for 1-based indexing
for y = 0, dim.y-1 do
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
local cid = data2[index_z]
if oreids[cid] then
data[index_z] = cid
count = count + 1
end
end
end
end
-- looks like we hit some biome that didn't have ores
try = try or 1
if count == 0 and try < 4 then
return ores(pos1, pos2, pretend_y, try + 1) -- try again
end
mh.finish(manip, data)
return count
end
local print2d = function(name, w, h, max, index) -- for debugging
local s = "##" .. name .. "\n" .. w .. "," .. h .. ":"
for y = 0, h-1 do
for x = 0, w-1 do
local n = (1 - index(x, y) / max) * 255
assert(n >= 0 and n <= 255)
s = s .. string.format("%02x", math.floor(n))
end
end
print(s)
end
local EWMA_alpha = 0.45
local WEIGHT = {orig=0.2, x=0.4, z=0.4}
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+
local res = {}
local last = heightmap[x + 1]
res[1] = last
for z = 1, dim.z-1 do
local h = heightmap[x + (z * hstride.z) + 1]
last = EWMA_alpha * h + (1 - EWMA_alpha) * last
res[z+1] = last
end
slice_x[x+1] = res
end
for x = 0, dim.x-1 do -- x- & averaging
local res = slice_x[x+1]
local last = heightmap[x + ((dim.z-1) * hstride.z) + 1]
res[dim.z] = (res[dim.z] + last) / 2
for z = dim.z-2, 0, -1 do
local h = heightmap[x + (z * hstride.z) + 1]
last = EWMA_alpha * h + (1 - EWMA_alpha) * last
res[z+1] = (res[z+1] + last) / 2
end
end
for z = 0, dim.z-1 do -- z+
local res = {}
local last = heightmap[(z * hstride.z) + 1]
res[1] = last
for x = 1, dim.x-1 do
local h = heightmap[x + (z * hstride.z) + 1]
last = EWMA_alpha * h + (1 - EWMA_alpha) * last
res[x+1] = last
end
slice_z[z+1] = res
end
for z = 0, dim.z-1 do -- z- & averaging
local res = slice_z[z+1]
local last = heightmap[dim.x-1 + (z * hstride.z) + 1]
res[dim.x] = (res[dim.x] + last) / 2
for x = dim.x-2, 0, -1 do
local h = heightmap[x + (z * hstride.z) + 1]
last = EWMA_alpha * h + (1 - EWMA_alpha) * last
res[x+1] = (res[x+1] + last) / 2
end
end
--[[print2d("heightmap", dim.x, dim.z, dim.y, function(x, z)
return heightmap[x + (z * hstride.z) + 1]
end)
print2d("ewma_x", dim.x, dim.z, dim.y, function(x, z)
return slice_x[x+1][z+1]
end)
print2d("ewma_z", dim.x, dim.z, dim.y, function(x, z)
return slice_z[z+1][x+1]
end)--]]
-- 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
for z = 0, dim.z-1 do
local index_z = index_x + (offset.z + z) * stride.z
local noop = false
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 hindex = x + (z * hstride.z) + 1
local old_height = heightmap[hindex]
local new_height = heightmap_new[hindex]
if noop then
-- do nothing (deadzone)
elseif old_height > new_height then
-- need to delete nodes
local y = old_height-1
while y >= new_height do
local index = index_z + (offset.y + y) * stride.y
if data[index] ~= c_air then data[index] = c_air end
count = count + 1
y = y - 1
end
elseif old_height < new_height then
-- need to add nodes
local c_top = c_dirt
if old_height ~= 0 then
c_top = data[index_z + (offset.y + old_height - 1) * stride.y]
end
local y = old_height
while y <= new_height-1 do
local index = index_z + (offset.y + y) * stride.y
if data[index] == c_air then data[index] = c_top end
count = count + 1
y = y + 1
end
end
end
end
mh.finish(manip, data)
return count
end
---------------------------------------------
-- chat commands
---------------------------------------------
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 selected region",
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = fall(pos1, pos2)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
worldedit.register_command("populate", {
params = "",
description = "Populate dirt in selected region",
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = populate(pos1, pos2)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
worldedit.register_command("ores", {
params = "",
description = "Generate ores in selected region",
privs = {worldedit=true},
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]
local count = ores(pos1, pos2, depth)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
worldedit.register_command("smooth", {
params = "[iterations]",
description = "Smooth terrain in selected region",
privs = {worldedit=true},
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]
local count = smooth(pos1, pos2, {x=0, z=0}, iterations)
worldedit.player_notify(name, count .. " nodes updated")
end,
})
---------------------------------------------
-- //smooth brush
---------------------------------------------
if minetest.registered_items["worldedit:brush"] == nil then
minetest.after(0, function()
minetest.log("error", "we_env: "..
"worldedit_brush not installed or enabled, "..
"brush functionality will be unavailable")
end)
return
end
local internal_name = "_smooth_brush_internal_do_not_use"
worldedit.register_command(internal_name, {
params = "",
privs = {worldedit=true},
require_pos = 1,
func = function(name)
local pos = worldedit.pos1[name]
-- Only modify an 10*10 area but take heights from 14*14 into consideration
local dist, dead = 10, 4
dist = dist + dead
local pos1 = vector.apply(vector.subtract(pos, dist/2), math.floor)
local pos2 = vector.add(pos1, dist)
-- Expand region vertically to include lowest & highest nodes
local max_height = 48
max_height = math.floor(max_height / 2)
pos1.y = pos.y - max_height -- defaults
pos2.y = pos.y + max_height
for y = pos.y, pos.y - max_height, -1 do
local all_solid = true
for x = pos1.x, pos2.x do
for z = pos1.z, pos2.z do
if minetest.get_node({x=x, y=y, z=z}).name == "air" then
all_solid = false
break
end
end
end
if all_solid then
pos1.y = y
break
end
end
for y = pos.y, pos.y + max_height do
local all_nonsolid = true
for x = pos1.x, pos2.x do
for z = pos1.z, pos2.z do
if minetest.get_node({x=x, y=y, z=z}).name ~= "air" then
all_nonsolid = false
break
end
end
end
if all_nonsolid then
pos2.y = y
break
end
end
smooth(pos1, pos2, {x=dead, z=dead}, 1)
--[[worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)--]]
end,
})
worldedit.register_command("smoothbrush", {
privs = {worldedit=true},
params = "",
description = "Assign smoothing action to WorldEdit brush item",
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.")
return
end
local meta = itemstack:get_meta()
meta:set_string("command", internal_name)
meta:set_string("params", "")
meta:set_string("description",
minetest.registered_tools["worldedit:brush"].description .. ": Smooth")
worldedit.player_notify(name, "Smoothing action assigned to brush.")
minetest.get_player_by_name(name):set_wielded_item(itemstack)
end,
})