building_lib/mapgen.lua
2024-06-14 07:24:08 +02:00

214 lines
7.8 KiB
Lua

local function select_biome(biomes, temperature, humidity)
local selected_score = -1
local selected_biome
for _, biome in ipairs(biomes) do
local score = math.abs(temperature - biome.temperature) + math.abs(humidity - biome.humidity)
if not selected_biome or score > selected_score then
selected_biome = biome
selected_score = score
end
end
return selected_biome
end
function building_lib.create_mapgen(opts)
assert(#opts.biomes > 0)
local map_lengths_xyz = {x=1, y=1, z=1}
opts.water_level = opts.water_level or 0
opts.height_params = opts.height_params or {
offset = 0,
scale = 1,
spread = {x=64, y=64, z=64},
seed = 5477835,
octaves = 2,
persist = 0.5
}
opts.temperature_params = opts.temperature_params or {
offset = 0,
scale = 1,
spread = {x=64, y=64, z=64},
seed = 952995,
octaves = 2,
persist = 0.5
}
opts.humidity_params = opts.humidity_params or {
offset = 0,
scale = 1,
spread = {x=128, y=128, z=128},
seed = 2946271,
octaves = 2,
persist = 0.5
}
local height_perlin, temperature_perlin, humidity_perlin
-- [x .. "/" .. z] = @number
local height_cache = {}
local function get_height(mapblock_pos)
local key = mapblock_pos.x .. "/" .. mapblock_pos.z
if not height_cache[key] then
height_perlin = height_perlin or minetest.get_perlin_map(opts.height_params, map_lengths_xyz)
local height_perlin_map = {}
height_perlin:get_2d_map_flat({x=mapblock_pos.x, y=mapblock_pos.z}, height_perlin_map)
local height = math.floor(math.abs(height_perlin_map[1]) * 6) -1
height_cache[key] = height
end
return height_cache[key]
end
-- [x .. "/" .. z] = @number
local temperature_cache = {}
local humidity_cache = {}
local function get_temperature_humidity(mapblock_pos)
local key = mapblock_pos.x .. "/" .. mapblock_pos.z
if not temperature_cache[key] then
temperature_perlin = temperature_perlin or minetest.get_perlin_map(opts.temperature_params, map_lengths_xyz)
humidity_perlin = humidity_perlin or minetest.get_perlin_map(opts.humidity_params, map_lengths_xyz)
local temperature_perlin_map = {}
local humidity_perlin_map = {}
temperature_perlin:get_2d_map_flat({x=mapblock_pos.x, y=mapblock_pos.z}, temperature_perlin_map)
humidity_perlin:get_2d_map_flat({x=mapblock_pos.x, y=mapblock_pos.z}, humidity_perlin_map)
local temperature = math.floor(math.abs(temperature_perlin_map[1]) * 100)
local humidity = math.floor(math.abs(humidity_perlin_map[1]) * 100)
temperature_cache[key] = temperature
humidity_cache[key] = humidity
end
return temperature_cache[key], humidity_cache[key]
end
-- return a map with every lower-height blocks flagged
local function get_height_map(mapblock_pos, height)
local hm = {}
for x=-1,1 do
hm[x] = {}
for z=-1,1 do
hm[x][z] = get_height(vector.add(mapblock_pos, {x=x,y=0,z=z})) < height
end
end
return hm
end
local function is_water(mapblock_pos)
local height = get_height(mapblock_pos)
return mapblock_pos.y == opts.water_level and height <= mapblock_pos.y
end
local function get_building(mapblock_pos)
local height = get_height(mapblock_pos)
local temperature, humidity = get_temperature_humidity(mapblock_pos)
local biome = select_biome(opts.biomes, temperature, humidity)
if is_water(mapblock_pos) then
-- nothing above, place water building
return biome.buildings.water, 0
elseif mapblock_pos.y < height or mapblock_pos.y < opts.water_level then
-- underground
return biome.buildings.underground, 0
elseif mapblock_pos.y == height then
-- surface
-- check if neighbors are lower
local hm = get_height_map(mapblock_pos, height)
local building_name = biome.buildings.surface
local rotation = 0
-- normal slopes
if hm[-1][0] and not hm[1][0] and not hm[0][-1] and not hm[0][1] then
building_name = biome.buildings.slope
rotation = 90
elseif not hm[-1][0] and hm[1][0] and not hm[0][-1] and not hm[0][1] then
building_name = biome.buildings.slope
rotation = 270
elseif not hm[-1][0] and not hm[1][0] and hm[0][-1] and not hm[0][1] then
building_name = biome.buildings.slope
rotation = 0
elseif not hm[-1][0] and not hm[1][0] and not hm[0][-1] and hm[0][1] then
building_name = biome.buildings.slope
rotation = 180
-- outer slopes
elseif hm[0][-1] and hm[-1][0] and not hm[0][1] and not hm[1][0] then
building_name = biome.buildings.slope_outer
rotation = 90
elseif not hm[0][-1] and hm[-1][0] and hm[0][1] and not hm[1][0] then
building_name = biome.buildings.slope_outer
rotation = 180
elseif not hm[0][-1] and not hm[-1][0] and hm[0][1] and hm[1][0] then
building_name = biome.buildings.slope_outer
rotation = 270
elseif hm[0][-1] and not hm[-1][0] and not hm[0][1] and hm[1][0] then
building_name = biome.buildings.slope_outer
rotation = 0
-- inner slopes
elseif hm[-1][-1] and not hm[-1][1] and not hm[1][1] and not hm[1][-1] then
building_name = biome.buildings.slope_inner
rotation = 90
elseif not hm[-1][-1] and hm[-1][1] and not hm[1][1] and not hm[1][-1] then
building_name = biome.buildings.slope_inner
rotation = 180
elseif not hm[-1][-1] and not hm[-1][1] and hm[1][1] and not hm[1][-1] then
building_name = biome.buildings.slope_inner
rotation = 270
elseif not hm[-1][-1] and not hm[-1][1] and not hm[1][1] and hm[1][-1] then
building_name = biome.buildings.slope_inner
rotation = 0
end
return building_name, rotation
end
end
local function on_generated(minp, maxp)
local min_mapblock = mapblock_lib.get_mapblock(minp)
local max_mapblock = mapblock_lib.get_mapblock(maxp)
if max_mapblock.y < opts.from_y or min_mapblock.y > opts.to_y then
-- check broad y-range
return
end
for x=min_mapblock.x,max_mapblock.x do
for y=min_mapblock.y,max_mapblock.y do
for z=min_mapblock.z,max_mapblock.z do
if y < opts.from_y or y > opts.to_y then
-- check exact y-range
break
end
local mapblock_pos = { x=x, y=y, z=z }
local building_name, rotation = get_building(mapblock_pos)
if building_name then
building_lib.build_mapgen(mapblock_pos, building_name, rotation)
end
end --z
end --y
end --x
end
-- exposed mapgen helper functions
return {
-- main function for "minetest.register_on_generated"
on_generated = on_generated,
get_height = get_height,
get_temperature_humidity = get_temperature_humidity,
get_biome = function(mapblock_pos)
local temperature, humidity = get_temperature_humidity(mapblock_pos)
return select_biome(opts.biomes, temperature, humidity)
end,
is_water = is_water,
get_building = get_building
}
end