12 Commits
dev ... caves

Author SHA1 Message Date
77b05f044a Added caves (hardcoded for now) 2024-02-12 22:22:24 +01:00
b406bebb7b Use biomegen.skip_chunk and place it before timer. 2024-02-11 22:19:21 +01:00
dcc71225ae Remove debug print 2024-02-11 12:35:28 +01:00
c8b96e2836 Find spawn position and effectively spawn player (yay!) 2024-02-11 09:26:05 +01:00
6017510df0 Re-implement minetest.get_spawn_level 2024-02-11 08:40:48 +01:00
70418f9526 Fix 2D index not being incremented 2024-02-11 08:27:30 +01:00
0e3c83e1d2 Use biomegen.make_void_maps if present.
(latest version of biomegen)
2024-02-08 22:58:26 +01:00
a91a13bbec Insert mapgen callback in first position, to ensure it is called first
Improves compatibility with mods working on mapgen
2024-02-07 10:54:31 +01:00
146f009684 Re-organize grid management code for less dependance between files
Remove gridio.lua and move its function to appropriate files
2024-02-01 19:30:07 +01:00
2cf3b19167 Generate grid directly in pregenerate.lua, not in a function 2024-01-31 11:32:24 +01:00
4697f9c948 Remove "full" grid loading method
I see no reason to let the choice between a greedy and a lighter loading method, so better remove it to simplify the code.
2024-01-31 10:47:37 +01:00
ed832a0806 Use only the 10 last digits of world seed at pregenerate 2024-01-28 22:12:05 +01:00
9 changed files with 323 additions and 296 deletions

View File

@ -1,114 +0,0 @@
-- Input and output functions for grid maps
local worldpath = mapgen_rivers.world_data_path
local floor = math.floor
local sbyte, schar = string.byte, string.char
local unpk = unpack
-- Loading files
local function load_full_map(filename, bytes, signed, size, converter)
local file = io.open(worldpath .. filename, 'rb')
local data = file:read('*all')
if #data < bytes*size then
data = minetest.decompress(data)
end
local map = {}
for i=1, size do
local i0 = (i-1)*bytes+1
local elements = {data:byte(i0, i1)}
local n = sbyte(data, i0)
if signed and n >= 128 then
n = n - 256
end
for j=1, bytes-1 do
n = n*256 + sbyte(data, i0+j)
end
map[i] = n
end
file:close()
if converter then
for i=1, size do
map[i] = converter(map[i])
end
end
return map
end
local loader_mt = {
__index = function(loader, i)
local file = loader.file
local bytes = loader.bytes
file:seek('set', (i-1)*bytes)
local strnum = file:read(bytes)
local n = sbyte(strnum, 1)
if loader.signed and n >= 128 then
n = n - 256
end
for j=2, bytes do
n = n*256 + sbyte(strnum, j)
end
if loader.conv then
n = loader.conv(n)
end
loader[i] = n
return n
end,
}
local function interactive_loader(filename, bytes, signed, size, converter)
local file = io.open(worldpath .. filename, 'rb')
if file then
converter = converter or false
return setmetatable({file=file, bytes=bytes, signed=signed, size=size, conv=converter}, loader_mt)
end
end
local load_methods = {
full = load_full_map,
interactive = interactive_loader,
}
function mapgen_rivers.load_file(...)
local load_method = mapgen_rivers.settings.load_method
local load_func = load_methods[load_method]
if load_func then
return load_func(...)
else
minetest.log("error", ("[mapgen_rivers] Unknown load method %s"):format(load_method))
end
end
-- Writing files
function mapgen_rivers.write_file(filename, data, bytes)
local size = #data
local file = io.open(worldpath .. filename, 'wb')
local bytelist = {}
for j=1, bytes do
bytelist[j] = 0
end
for i=1, size do
local n = floor(data[i])
data[i] = n
for j=bytes, 2, -1 do
bytelist[j] = n % 256
n = floor(n / 256)
end
bytelist[1] = n % 256
file:write(schar(unpk(bytelist)))
end
file:close()
end

View File

@ -1,6 +1,6 @@
-- Manages grid loading, writing and generation -- Manages grid loading, writing and generation
local world_data = mapgen_rivers.world_data_path local datapath = mapgen_rivers.world_data_path
local registered_on_grid_loaded = {} local registered_on_grid_loaded = {}
function mapgen_rivers.register_on_grid_loaded(func) function mapgen_rivers.register_on_grid_loaded(func)
@ -21,128 +21,85 @@ local function offset_conv(o)
return (o + 0.5) * (1/256) return (o + 0.5) * (1/256)
end end
local grid_maps_list = { local floor = math.floor
dem = {bytes=2, signed=true}, local sbyte, schar = string.byte, string.char
lakes = {bytes=2, signed=true}, local unpk = unpack
dirs = {bytes=1, signed=false},
rivers = {bytes=4, signed=false},
offset_x = {bytes=1, signed=true, conv=offset_conv}, -- Loading files
offset_y = {bytes=1, signed=true, conv=offset_conv},
-- Never load the full map during mapgen. Instead, create an empty lookup table
-- and read the file on-the-fly when an element is requested for the first time,
-- using __index metamethod.
local loader_mt = {
__index = function(loader, i) -- Called when accessing a missing key
local file = loader.file
local bytes = loader.bytes
file:seek('set', (i-1)*bytes)
local strnum = file:read(bytes)
local n = sbyte(strnum, 1)
if loader.signed and n >= 128 then
n = n - 256
end
for j=2, bytes do
n = n*256 + sbyte(strnum, j)
end
if loader.conv then
n = loader.conv(n)
end
-- Cache key for next use
loader[i] = n
return n
end,
} }
local function apply_grid_conversion(grid) local function load_file(filename, bytes, signed, size, converter)
if grid.load_method ~= "full" then local file = io.open(datapath .. filename, 'rb')
minetest.log("warning", ("Could not apply data conversion for load method %s"):format(grid.load_method)) if file then
return false converter = converter or false
return setmetatable({file=file, bytes=bytes, signed=signed, size=size, conv=converter}, loader_mt)
end end
if grid.conv_applied then end
function mapgen_rivers.load_or_generate_grid()
-- First, check whether a grid is already loaded
if mapgen_rivers.grid then
return true return true
end end
local size = grid.size.x * grid.size.y -- If not, try to load the grid from the files
for mapname, params in pairs(grid_maps_list) do local sfile = io.open(datapath .. 'size', 'r')
local conv = params.conv if not sfile then
if conv then dofile(mapgen_rivers.modpath .. "/pregenerate.lua")
local map = grid[mapname] collectgarbage()
for i=1, size do
map[i] = conv(map[i])
end
end
end
grid.conv_applied = true
return true sfile = io.open(datapath .. 'size', 'r')
end
function mapgen_rivers.try_load_grid(grid)
local load_method = mapgen_rivers.settings.load_method
-- First, check whether a grid is already loaded with the appropriate method
if mapgen_rivers.grid and mapgen_rivers.grid.load_method == load_method then
if not mapgen_rivers.grid.conv_applied then
apply_grid_conversion(mapgen_rivers.grid)
end
return true
-- Then, check the provided argument is a valid grid
elseif grid and grid.load_method == load_method then
if not mapgen_rivers.grid.conv_applied then
apply_grid_conversion(grid)
end
mapgen_rivers.grid = grid
on_grid_loaded_callback(grid)
return true
end
-- Fall back to loading the grid from the files
local sfile = io.open(world_data .. 'size', 'r')
if not sfile then if not sfile then
return false return false
end end
end
local x, z = sfile:read('*n'), sfile:read('*n') local x, z = sfile:read('*n'), sfile:read('*n')
if not x or not z then if not x or not z then
return false return false
end end
if load_method == "full" then minetest.log("action", '[mapgen_rivers] Loading grid')
minetest.log("action", '[mapgen_rivers] Loading full grid')
elseif load_method == "interactive" then
minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders')
end
grid = { local grid = {
load_method = load_method,
size = {x=x, y=z}, size = {x=x, y=z},
dem = load_file('dem', 2, true, x*z),
lakes = load_file('lakes', 2, true, x*z),
dirs = load_file('dirs', 1, false, x*z),
rivers = load_file('rivers', 4, false, x*z),
offset_x = load_file('offset_x', 1, true, x*z, offset_conv),
offset_y = load_file('offset_y', 1, true, x*z, offset_conv),
} }
for map, params in pairs(grid_maps_list) do
grid[map] = mapgen_rivers.load_file(map, params.bytes, params.signed, x*z, params.conv)
end
grid.conv_applied = true
mapgen_rivers.grid = grid mapgen_rivers.grid = grid
on_grid_loaded_callback(grid) on_grid_loaded_callback(grid)
return true return true
end end
function mapgen_rivers.generate_grid()
minetest.log("action", '[mapgen_rivers] Generating grid, this may take a while...')
local grid = {}
local blocksize = mapgen_rivers.settings.blocksize
local xsize = math.floor(mapgen_rivers.settings.map_x_size / blocksize)
local zsize = math.floor(mapgen_rivers.settings.map_z_size / blocksize)
grid.size = {x=xsize, y=zsize}
grid.conv_applied = false
if not mapgen_rivers.pregenerate then
minetest.log("error", "[mapgen_rivers] Pre-generation function is not available.")
return false
end
mapgen_rivers.pregenerate(grid)
return grid
end
function mapgen_rivers.write_grid(grid)
minetest.mkdir(world_data)
if grid.conv_applied then
minetest.log("error", '[mapgen_rivers] Could not write grid if data conversion is already done')
return false
end
for map, params in pairs(grid_maps_list) do
mapgen_rivers.write_file(map, grid[map], params.bytes)
end
local sfile = io.open(world_data .. 'size', "w")
sfile:write(grid.size.x..'\n'..grid.size.y)
sfile:close()
return true
end

View File

@ -8,19 +8,9 @@ mapgen_rivers.world_data_path = minetest.get_worldpath() .. '/river_data/'
dofile(modpath .. 'settings.lua') dofile(modpath .. 'settings.lua')
dofile(modpath .. 'gridmanager.lua') dofile(modpath .. 'gridmanager.lua')
dofile(modpath .. 'gridio.lua')
dofile(modpath .. 'polygons.lua') dofile(modpath .. 'polygons.lua')
dofile(modpath .. 'heightmap.lua') dofile(modpath .. 'heightmap.lua')
dofile(modpath .. 'mapgen.lua') dofile(modpath .. 'mapgen.lua')
dofile(modpath .. 'spawn.lua')
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(mapgen_rivers.load_or_generate_grid)
local exist = mapgen_rivers.try_load_grid()
if not exist then -- If grid does not exist yet, generate it
dofile(modpath .. 'pregenerate.lua')
local grid = mapgen_rivers.generate_grid()
mapgen_rivers.write_grid(grid)
mapgen_rivers.try_load_grid(grid) -- Reload if needed
end
end)

View File

@ -28,12 +28,13 @@ local floor, min = math.floor, math.min
local data = {} local data = {}
local noise_x_obj, noise_z_obj, noise_distort_obj, noise_heat_obj, noise_heat_blend_obj local noise_x_obj, noise_z_obj, noise_distort_obj, noise_heat_obj, noise_heat_blend_obj, noise_cave_obj
local noise_x_map = {} local noise_x_map = {}
local noise_z_map = {} local noise_z_map = {}
local noise_distort_map = {} local noise_distort_map = {}
local noise_heat_map = {} local noise_heat_map = {}
local noise_heat_blend_map = {} local noise_heat_blend_map = {}
local noise_cave = {}
local mapsize local mapsize
local init = false local init = false
@ -41,6 +42,18 @@ local sumtime = 0
local sumtime2 = 0 local sumtime2 = 0
local ngen = 0 local ngen = 0
local use_caves = true
local np_cave = {
offset = 0,
scale = 8,
spread = {x=256, y=128, z=256},
seed = -9152,
octaves = 4,
persist = 0.65,
lacunarity = 2,
flags = "absvalue",
}
function mapgen_rivers.make_chunk(minp, maxp, seed) function mapgen_rivers.make_chunk(minp, maxp, seed)
minetest.log("info", ("[mapgen_rivers] Generating from %s to %s"):format(minetest.pos_to_string(minp), minetest.pos_to_string(maxp))) minetest.log("info", ("[mapgen_rivers] Generating from %s to %s"):format(minetest.pos_to_string(minp), minetest.pos_to_string(maxp)))
@ -65,6 +78,9 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
noise_heat_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat, chulens) noise_heat_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat, chulens)
noise_heat_blend_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat_blend, chulens) noise_heat_blend_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat_blend, chulens)
end end
if use_caves then
noise_cave_obj = minetest.get_perlin_map(np_cave, mapsize)
end
init = true init = true
end end
@ -79,7 +95,9 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
noise_heat_obj:get_2d_map_flat(minp2d, noise_heat_map) noise_heat_obj:get_2d_map_flat(minp2d, noise_heat_map)
noise_heat_blend_obj:get_2d_map_flat(minp2d, noise_heat_blend_map) noise_heat_blend_obj:get_2d_map_flat(minp2d, noise_heat_blend_map)
end end
if use_caves then
noise_cave_obj:get_3d_map_flat(minp, noise_cave)
end
local terrain_map, lake_map, incr, i_origin local terrain_map, lake_map, incr, i_origin
if use_distort then if use_distort then
@ -103,6 +121,7 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
end end
i2d = i2d-chulens.x i2d = i2d-chulens.x
end end
i2d = i2d+chulens.x
end end
local pminp = {x=floor(xmin), z=floor(zmin)} local pminp = {x=floor(xmin), z=floor(zmin)}
@ -127,6 +146,10 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
-- If not, skip chunk -- If not, skip chunk
if is_empty then if is_empty then
if use_biomegen_mod and biomegen.skip_chunk then
biomegen.skip_chunk(minp, maxp)
end
local t = os.clock() - t0 local t = os.clock() - t0
ngen = ngen + 1 ngen = ngen + 1
sumtime = sumtime + t sumtime = sumtime + t
@ -141,6 +164,7 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
local c_stone = minetest.get_content_id("mapgen_stone") local c_stone = minetest.get_content_id("mapgen_stone")
local c_water = minetest.get_content_id("mapgen_water_source") local c_water = minetest.get_content_id("mapgen_water_source")
local c_rwater = minetest.get_content_id("mapgen_river_water_source") local c_rwater = minetest.get_content_id("mapgen_river_water_source")
local c_air = minetest.get_content_id("air")
local c_dirt, c_lawn, c_dirtsnow, c_snow, c_sand, c_ice local c_dirt, c_lawn, c_dirtsnow, c_snow, c_sand, c_ice
if use_biomes then if use_biomes then
@ -173,7 +197,7 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
if use_biomes then if use_biomes then
temperature = noise_heat_map[i2d]+noise_heat_blend_map[i2d] temperature = noise_heat_map[i2d]+noise_heat_blend_map[i2d]
end end
local terrain, lake local terrain, lake, caveness
if not use_distort then if not use_distort then
terrain = terrain_map[i2d] terrain = terrain_map[i2d]
lake = lake_map[i2d] lake = lake_map[i2d]
@ -195,6 +219,10 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
lake = min(lake_map[i0], lake_map[i1], lake_map[i2], lake_map[i3]) lake = min(lake_map[i0], lake_map[i1], lake_map[i2], lake_map[i3])
end end
if use_caves then
noise_cave[nid] = noise_cave[nid] < math.min(math.max(terrain - y, 0), 100) ^ 0.5 / 10
end
if y <= maxp.y then if y <= maxp.y then
local is_lake = lake > terrain local is_lake = lake > terrain
@ -244,10 +272,32 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
end end
if use_biomegen_mod then if use_biomegen_mod then
biomegen.generate_all(data, a, vm, minp, maxp, seed) biomegen.generate_biomes(data, a, minp, maxp)
else end
if use_caves then
local i = 1
for z=minp.z, maxp.z do
for y=minp.y, maxp.y do
local vi = a:index(minp.x, y, z)
for x=minp.x, maxp.x do
if noise_cave[i] then
data[vi] = c_air
end
i = i + 1
vi = vi + 1
end
end
i = i + chulens.x -- Skip etra row
end
end
vm:set_data(data) vm:set_data(data)
if use_biomegen_mod then
biomegen.place_all_decos(data, a, vm, minp, maxp, seed)
end
minetest.generate_ores(vm, minp, maxp) minetest.generate_ores(vm, minp, maxp)
if use_biomegen_mod then
vm:get_data(data)
biomegen.dust_top_nodes(data, a, vm, minp, maxp)
end end
vm:set_lighting({day = 0, night = 0}) vm:set_lighting({day = 0, night = 0})
@ -262,7 +312,9 @@ function mapgen_rivers.make_chunk(minp, maxp, seed)
minetest.log("verbose", ("[mapgen_rivers] Done in %5.3f s"):format(t)) minetest.log("verbose", ("[mapgen_rivers] Done in %5.3f s"):format(t))
end end
minetest.register_on_generated(mapgen_rivers.make_chunk) -- Enforce first position in mapgen callbacks
table.insert(minetest.registered_on_generateds, 1, mapgen_rivers.make_chunk)
--minetest.register_on_generated(mapgen_rivers.make_chunk)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
local avg = sumtime / ngen local avg = sumtime / ngen

View File

@ -1,6 +1,8 @@
-- Generate the grid using terrainlib_lua -- Generate the grid using terrainlib_lua
-- Only called on first mapgen, if there is no grid yet -- Only called on first mapgen, if there is no grid yet
-- Constants
local EvolutionModel = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/erosion.lua') local EvolutionModel = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/erosion.lua')
local twist = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/twist.lua') local twist = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/twist.lua')
@ -21,6 +23,9 @@ local use_margin = mapgen_rivers.settings.margin
local margin_width = mapgen_rivers.settings.margin_width / blocksize local margin_width = mapgen_rivers.settings.margin_width / blocksize
local margin_elev = mapgen_rivers.settings.margin_elev local margin_elev = mapgen_rivers.settings.margin_elev
local X = math.floor(mapgen_rivers.settings.map_x_size / blocksize)
local Y = math.floor(mapgen_rivers.settings.map_z_size / blocksize)
local function margin(dem, width, elev) local function margin(dem, width, elev)
local X, Y = dem.X, dem.Y local X, Y = dem.X, dem.Y
for i=1, width do for i=1, width do
@ -49,21 +54,22 @@ local function margin(dem, width, elev)
end end
end end
function mapgen_rivers.pregenerate(grid) -- Generate grid
local size = grid.size
if size.x * size.y > 4e6 then minetest.log("action", '[mapgen_rivers] Generating grid, this may take a while...')
if X*Y > 4e6 then
minetest.log("warning", "[mapgen_rivers] You are going to generate a very large grid (>4M nodes). If you experience problems, you should increase blocksize or reduce map size.") minetest.log("warning", "[mapgen_rivers] You are going to generate a very large grid (>4M nodes). If you experience problems, you should increase blocksize or reduce map size.")
end end
local seed = tonumber(minetest.get_mapgen_setting("seed")) local seed = tonumber(minetest.get_mapgen_setting("seed"):sub(-10))
np_base.seed = (np_base.seed or 0) + seed np_base.seed = (np_base.seed or 0) + seed
local nobj_base = PerlinNoiseMap(np_base, {x=size.x, y=1, z=size.y}) local nobj_base = PerlinNoiseMap(np_base, {x=X, y=1, z=Y})
local dem = nobj_base:get_3d_map_flat({x=0, y=0, z=0}) local dem = nobj_base:get_3d_map_flat({x=0, y=0, z=0})
dem.X = size.x dem.X = X
dem.Y = size.y dem.Y = Y
if use_margin then if use_margin then
margin(dem, margin_width, margin_elev) margin(dem, margin_width, margin_elev)
@ -97,20 +103,48 @@ function mapgen_rivers.pregenerate(grid)
local mfloor = math.floor local mfloor = math.floor
local mmin, mmax = math.min, math.max local mmin, mmax = math.min, math.max
local unpk, schar = unpack, string.char
local offset_x, offset_y = twist(model.dirs, model.rivers, 5) local offset_x, offset_y = twist(model.dirs, model.rivers, 5)
for i=1, size.x*size.y do for i=1, X*Y do
offset_x[i] = mmin(mmax(offset_x[i]*256, -128), 127) offset_x[i] = mmin(mmax(offset_x[i]*256, -128), 127)
offset_y[i] = mmin(mmax(offset_y[i]*256, -128), 127) offset_y[i] = mmin(mmax(offset_y[i]*256, -128), 127)
end end
grid.dem = model.dem -- Write data
grid.lakes = model.lakes
grid.dirs = model.dirs
grid.rivers = model.rivers
grid.offset_x = offset_x
grid.offset_y = offset_y
grid.load_method = "full" local datapath = mapgen_rivers.world_data_path
grid.conv_applied = false minetest.mkdir(datapath)
collectgarbage()
local sfile = io.open(datapath .. 'size', "w")
sfile:write(X..'\n'..Y)
sfile:close()
local function write_file(filename, data, bytes)
local file = io.open(datapath .. filename, 'wb')
local bytelist = {}
for j=1, bytes do
bytelist[j] = 0
end end
for i=1, #data do
local n = mfloor(data[i])
data[i] = n
for j=bytes, 2, -1 do
bytelist[j] = n % 256
n = mfloor(n / 256)
end
bytelist[1] = n % 256
file:write(schar(unpk(bytelist)))
end
file:close()
end
write_file('dem', model.dem, 2)
write_file('lakes', model.lakes, 2)
write_file('dirs', model.dirs, 1)
write_file('rivers', model.rivers, 4)
write_file('offset_x', offset_x, 1)
write_file('offset_y', offset_y, 1)

View File

@ -109,13 +109,8 @@ mapgen_rivers.settings = {
tectonic_speed = def_setting('tectonic_speed', 'number'), tectonic_speed = def_setting('tectonic_speed', 'number'),
evol_time = def_setting('evol_time', 'number'), evol_time = def_setting('evol_time', 'number'),
evol_time_step = def_setting('evol_time_step', 'number'), evol_time_step = def_setting('evol_time_step', 'number'),
load_all = mtsettings:get_bool('mapgen_rivers_load_all')
} }
mapgen_rivers.settings.load_method =
mapgen_rivers.settings.load_all and "full" or "interactive"
mapgen_rivers.noise_params = { mapgen_rivers.noise_params = {
base = def_setting("np_base", "noise"), base = def_setting("np_base", "noise"),
distort_x = def_setting("np_distort_x", "noise"), distort_x = def_setting("np_distort_x", "noise"),

View File

@ -25,7 +25,6 @@
"tectonic_speed": 70, "tectonic_speed": 70,
"evol_time": 10, "evol_time": 10,
"evol_time_step": 1, "evol_time_step": 1,
"load_all": false,
"np_base": { "np_base": {
"offset": 0, "offset": 0,

View File

@ -70,11 +70,6 @@ mapgen_rivers_glacier_widening_factor (Glacier widening factor) float 8.0 1.0 20
# This results in mountains being more covered by snow. # This results in mountains being more covered by snow.
mapgen_rivers_elevation_chill (Elevation chill) float 0.25 0.0 5.0 mapgen_rivers_elevation_chill (Elevation chill) float 0.25 0.0 5.0
# If enabled, loads all grid data in memory at init time.
# If disabled, data will be loaded on request and cached in memory.
# It's recommended to disable it for very large maps (> 2000 grid nodes or so)
mapgen_rivers_load_all (Load all data in memory) bool false
[Landscape evolution parameters] [Landscape evolution parameters]
# Modelled landscape evolution time, in arbitrary units # Modelled landscape evolution time, in arbitrary units

119
spawn.lua Normal file
View File

@ -0,0 +1,119 @@
local np_distort_x = mapgen_rivers.noise_params.distort_x
local np_distort_z = mapgen_rivers.noise_params.distort_z
local np_distort_amplitude = mapgen_rivers.noise_params.distort_amplitude
local nobj_distort_x, nobj_distort_z, nobj_distort_amplitude
local sea_level = mapgen_rivers.settings.sea_level
local distort = mapgen_rivers.settings.distort
-- Linear interpolation
local function interp(v00, v01, v11, v10, xf, zf)
local v0 = v01*xf + v00*(1-xf)
local v1 = v11*xf + v10*(1-xf)
return v1*zf + v0*(1-zf)
end
local function estimate_spawn_level(pos, use_distort)
local x, z = pos.x, pos.z
if distort and use_distort then
nobj_distort_x = nobj_distort_x or minetest.get_perlin(np_distort_x)
nobj_distort_z = nobj_distort_z or minetest.get_perlin(np_distort_z)
nobj_distort_amplitude = nobj_distort_amplitude or minetest.get_perlin(np_distort_amplitude)
local amplitude = nobj_distort_amplitude:get_2d({x=pos.x, y=pos.z})
x = x + nobj_distort_x:get_3d(pos)*amplitude
z = z + nobj_distort_z:get_3d(pos)*amplitude
end
local terrain, lakes = mapgen_rivers.make_heightmaps(
{x=math.floor(x), z=math.floor(z) },
{x=math.floor(x)+1, z=math.floor(z)+1}
)
local ex, ez = x % 1, z % 1
--local h = terrain[1]*(1-ex)*(1-ez) + terrain[2]*ex*(1-ez) + terrain[3]*(1-ex)*ez + terrain[4]*ex*ez
local h = interp(terrain[1], terrain[2], terrain[4], terrain[3], ex, ez)
local lake = math.min(lakes[1], lakes[2], lakes[3], lakes[4])
if h < lake or h <= sea_level then
return false, h
end
return true, h
end
local function get_spawn_level(x, z)
local pos = {x=x, z=z}
local suitable, y = estimate_spawn_level(pos, false)
if not suitable then
return
end
if not distort then
return math.floor(y) + 1
end
local low_bound = -math.huge
local high_bound = math.huge
local suitable_high = false
repeat
pos.y = math.max(math.min(math.floor(y+0.5), high_bound-1), low_bound+1)
suitable, y = estimate_spawn_level(pos, true)
if y > pos.y then
low_bound = pos.y
else
high_bound = pos.y
suitable_high = suitable
end
until high_bound - low_bound <= 1
if not suitable_high then
return
end
return high_bound + 1
end
minetest.get_spawn_level = get_spawn_level
local rmax = 2000
local function find_spawn_point(seed)
local level = get_spawn_level(0, 0)
if level then
return {x=0, y=level, z=0}
end
local pr = PcgRandom(seed or os.time())
local incr = 16
local r0 = 0
while r0 < rmax do
local r1 = r0 + incr
local r = pr:next(r0*r0+1, r1*r1) ^ 0.5
local a = pr:next() / 2147483648 * math.pi
local x = math.floor(math.cos(a) * r + 0.5)
local z = math.floor(math.sin(a) * r + 0.5)
level = get_spawn_level(x, z)
if level then
return {x=x, y=level, z=z}
end
r0 = r1
end
end
local function spawn_player(player)
if minetest.settings:get("static_spawnpoint") then
return
end
local spawn_point = find_spawn_point()
if spawn_point then
player:set_pos(spawn_point)
end
end
minetest.register_on_newplayer(spawn_player)
minetest.register_on_respawnplayer(spawn_player)