peoplecantlua/peoplecantblur/init.lua

236 lines
5.9 KiB
Lua

-- peoplecantblur - Generate flat areas
local HEIGHT_CHECK = 30
local BLUR_ITERATIONS = 8
local c_air = minetest.get_content_id("air")
-- Function to get the node definition groups to check for a surface node
local function check_surface_content(c_id, cache_c)
if cache_c[c_id] ~= nil then
return cache_c[c_id]
end
-- Not contain in the table yet
local name = minetest.get_name_from_content_id(c_id)
local def = minetest.registered_nodes[name]
if not (def and def.groups) then
return false -- Unknown node
end
local is_surface_content = def.groups.soil or def.groups.sand or def.groups.stone
if not is_surface_content then
for k, v in pairs(minetest.registered_biomes) do
if v.node_top == name then
is_surface_content = true
break
end
end
end
cache_c[c_id] = (is_surface_content ~= nil)
return is_surface_content
end
-- Find the ground of a coordinate
local function get_ground(data, area, pos, max_height, cache_c, get_contents)
local id_cache = {}
local rel_surface -- Relative height of surface
-- Find the ground height (check downwards)
for y = 0, -max_height + 4, -1 do
local c_id = data[area:index(pos.x, pos.y + y, pos.z)]
local is_surface_content = check_surface_content(c_id, cache_c)
id_cache[y] = { c_id, is_surface_content }
if is_surface_content then
if y ~= 0 then
rel_surface = y
end
break
end
end
if not rel_surface then
-- Check upper area
for y = max_height - 1, 0, -1 do
local c_id = data[area:index(pos.x, pos.y + y, pos.z)]
local is_surface_content = check_surface_content(c_id, cache_c)
id_cache[y] = { c_id, is_surface_content }
if is_surface_content then
if y ~= max_height - 1 then
rel_surface = y
end
break
end
end
end
if not rel_surface then
-- Can not find ground in the air
return {}
end
if not get_contents then
return { rel_surface = rel_surface }
end
-- Get the ground contents
local c_contents = {}
local c_last_good = id_cache[rel_surface][1]
for y = rel_surface, rel_surface - 4, -1 do
local c_data = id_cache[y] -- { c_id, is_surface_content }
if not c_data then
local c_id = data[area:index(pos.x, pos.y + y, pos.z)]
local is_surface_content = check_surface_content(c_id, cache_c)
c_data = { c_id, is_surface_content }
end
-- insert: (is_surface_content) ? c_id : c_last_good
table.insert(c_contents, c_data[2] and c_data[1] or c_last_good)
if c_data[2] then
c_last_good = c_data[1]
end
end
-- Stretch the node above if it's air, a liquid, tree etc.
local c_above
local c_id = id_cache[rel_surface + 1][1]
local name = minetest.get_name_from_content_id(c_id)
local def = minetest.registered_nodes[name]
if def and (def.drawtype == "normal"
or def.drawtype == "airlike"
or def.drawtype == "liquid") then
c_above = c_id
end
return {
rel_surface = rel_surface,
c_contents = c_contents,
c_above = (c_above or c_air)
}
end
local function flatten(ppos, radius)
-- Flattened area is within radius, we need one more to use the blur function
local minp = vector.add(ppos, -radius - 1)
local maxp = vector.add(ppos, radius + 1)
-- Required to check the ground properly
minp.y = minp.y - HEIGHT_CHECK
maxp.y = maxp.y + HEIGHT_CHECK
local max_height = radius + HEIGHT_CHECK
local heightmap = {}
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(minp, maxp)
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
local data = vm:get_data()
local sidelen = maxp.x - minp.x + 1
-- Lookup table for content ID groups
local cache_c = {}
for z = minp.z, maxp.z do
for x = minp.x, maxp.x do
local ground = get_ground(
data,
area,
vector.new(x, ppos.y, z),
max_height,
cache_c,
math.abs(x - ppos.x) <= radius and math.abs(z - ppos.z) <= radius
)
heightmap[(z - minp.z) * sidelen + (x - minp.x)] = ground
end
end
-- Get the relative height from the heightmap with relative coordinates
local get_height = function(map, x, z, fallback)
local info = map[(z - minp.z) * sidelen + (x - minp.x)]
if info and info.rel_surface then
return info.rel_surface
end
return fallback
end
local _dirty_ = false
-- Apply blur filter on each position and update the nodes
for n = 0, BLUR_ITERATIONS do
for z = minp.z + 1, maxp.z - 1 do
for x = minp.x + 1, maxp.x - 1 do
local p_info = heightmap[(z - minp.z) * sidelen + (x - minp.x)]
local nodes = p_info.c_contents
local old_h = p_info.rel_surface
local above = p_info.c_above
if nodes and #nodes > 0 and old_h then
--[[
+----+----+----+
| 1 | 2 | 1 | 4
| 2 | 1 | 2 | 5
| 1 | 2 | 1 | 4
+----+----+----+ -> 13
]]
local h = old_h + (
get_height(heightmap, x , z - 1, old_h)
+ get_height(heightmap, x - 1, z , old_h)
+ get_height(heightmap, x + 1, z , old_h)
+ get_height(heightmap, x , z + 1, old_h)
) * 2 + (
get_height(heightmap, x - 1, z - 1, old_h)
+ get_height(heightmap, x + 1, z - 1, old_h)
+ get_height(heightmap, x - 1, z + 1, old_h)
+ get_height(heightmap, x + 1, z + 1, old_h)
)
h = math.floor(h / 13 + 0.5)
if h ~= old_h then
-- Height changed -> Change terrain
local max_y = math.max(h, old_h) + 1
local min_y = math.min(h, old_h) - 3
local i = 1
for y = max_y, min_y, -1 do
local vi = area:index(x, ppos.y + y, z)
if y > h then
if h > old_h then
data[vi] = c_air
else
data[vi] = above
end
else
data[vi] = nodes[math.min(#nodes, i)]
i = i + 1
end
_dirty_ = true
end
end
end
end
end
end
if not _dirty_ then
return
end
vm:set_data(data)
vm:write_to_map(data)
vm:update_liquids()
vm:update_map()
end
minetest.register_chatcommand("flat", {
description = "Makes the area around you flatter.",
privs = {server = true},
func = function(name, param)
local player = minetest.get_player_by_name(name)
local player_pos = vector.round(player:getpos())
-- Flatten an area of (2 * 7 + 1) ^ 2 square meters
flatten(player_pos, 7)
return true, "OK."
end
})