2023-12-28 15:27:36 +01:00
-- Recreate mod table if we are in a separate environment
if not minetest.global_exists ( " mapgen_rivers " ) then
mapgen_rivers = { use_mapgen_thread = true , thread = ' mapgen ' }
2023-12-30 17:38:51 +01:00
mapgen_rivers.settings = Settings ( minetest.get_worldpath ( ) .. ' /mapgen_rivers.conf ' )
2023-12-28 15:27:36 +01:00
end
if not mapgen_rivers.grid then
dofile ( minetest.get_modpath ( minetest.get_current_modname ( ) ) .. ' /load_grid.lua ' )
end
2023-12-30 17:38:51 +01:00
local settings = mapgen_rivers.settings
2023-12-28 15:27:36 +01:00
2024-01-04 23:28:33 +01:00
local sea_level = tonumber ( minetest.get_mapgen_setting ( " water_level " ) )
2023-12-28 15:27:36 +01:00
local elevation_chill = tonumber ( settings : get ( ' elevation_chill ' ) )
local use_distort = settings : get_bool ( ' distort ' )
local use_biomes = settings : get_bool ( ' biomes ' )
local use_biomegen_mod = use_biomes and minetest.global_exists ( ' biomegen ' )
2024-01-05 00:02:30 +01:00
use_biomes = use_biomes and minetest.get_modpath ( " default " ) and not use_biomegen_mod
2023-12-28 15:27:36 +01:00
local noiseparams = {
distort_x = settings : get_np_group ( ' np_distort_x ' ) ,
distort_z = settings : get_np_group ( ' np_distort_z ' ) ,
distort_amplitude = settings : get_np_group ( ' np_distort_amplitude ' ) ,
}
if use_biomes then
2024-01-04 23:28:33 +01:00
noiseparams.heat = minetest.get_mapgen_setting_noiseparams ( " mg_biome_np_heat " )
2024-01-04 22:47:50 +01:00
noiseparams.heat . offset = noiseparams.heat . offset + sea_level / elevation_chill
2024-01-04 23:28:33 +01:00
noiseparams.heat_blend = minetest.get_mapgen_setting_noiseparams ( " mg_biome_np_heat_blend " )
2023-12-28 15:27:36 +01:00
end
if use_biomegen_mod then
biomegen.set_elevation_chill ( elevation_chill )
end
local heightmaps = dofile ( minetest.get_modpath ( minetest.get_current_modname ( ) ) .. ' /heightmap.lua ' )
-- 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
-- Localize for performance
local floor , min = math.floor , math.min
local data = { }
local noise_x_obj , noise_z_obj , noise_distort_obj , noise_heat_obj , noise_heat_blend_obj
local noise_x_map = { }
local noise_z_map = { }
local noise_distort_map = { }
local noise_heat_map = { }
local noise_heat_blend_map = { }
local mapsize
local init = false
local sumtime = 0
local sumtime2 = 0
local ngen = 0
local function init_mapgen ( chulens )
mapsize = {
x = chulens.x ,
y = chulens.y + 1 ,
z = chulens.z ,
}
if use_distort then
noise_x_obj = minetest.get_perlin_map ( noiseparams.distort_x , mapsize )
noise_z_obj = minetest.get_perlin_map ( noiseparams.distort_z , mapsize )
noise_distort_obj = minetest.get_perlin_map ( noiseparams.distort_amplitude , chulens )
end
if use_biomes then
noise_heat_obj = minetest.get_perlin_map ( noiseparams.heat , chulens )
noise_heat_blend_obj = minetest.get_perlin_map ( noiseparams.heat_blend , chulens )
end
end
2024-01-05 00:02:30 +01:00
local function generate ( vm , minp , maxp , seed )
2023-12-28 15:27:36 +01:00
minetest.log ( " info " , ( " [mapgen_rivers] Generating from %s to %s " ) : format ( minetest.pos_to_string ( minp ) , minetest.pos_to_string ( maxp ) ) )
local chulens = {
x = maxp.x - minp.x + 1 ,
y = maxp.y - minp.y + 1 ,
z = maxp.z - minp.z + 1 ,
}
if not init then
init_mapgen ( chulens )
init = true
end
local t0 = os.clock ( )
local minp2d = { x = minp.x , y = minp.z }
if use_distort then
noise_x_obj : get_3d_map_flat ( minp , noise_x_map )
noise_z_obj : get_3d_map_flat ( minp , noise_z_map )
noise_distort_obj : get_2d_map_flat ( minp2d , noise_distort_map )
end
if use_biomes then
noise_heat_obj : get_2d_map_flat ( minp2d , noise_heat_map )
noise_heat_blend_obj : get_2d_map_flat ( minp2d , noise_heat_blend_map )
end
local terrain_map , lake_map , incr , i_origin
if use_distort then
local xmin , xmax , zmin , zmax = minp.x , maxp.x , minp.z , maxp.z
local i = 0
local i2d = 0
for z = minp.z , maxp.z do
for y = minp.y , maxp.y + 1 do
for x = minp.x , maxp.x do
i = i + 1
i2d = i2d + 1
local distort = noise_distort_map [ i2d ]
local xv = noise_x_map [ i ] * distort + x
if xv < xmin then xmin = xv end
if xv > xmax then xmax = xv end
noise_x_map [ i ] = xv
local zv = noise_z_map [ i ] * distort + z
if zv < zmin then zmin = zv end
if zv > zmax then zmax = zv end
noise_z_map [ i ] = zv
end
i2d = i2d - chulens.x
end
end
local pminp = { x = floor ( xmin ) , z = floor ( zmin ) }
local pmaxp = { x = floor ( xmax ) + 1 , z = floor ( zmax ) + 1 }
incr = pmaxp.x - pminp.x + 1
i_origin = 1 - pminp.z * incr - pminp.x
terrain_map , lake_map = heightmaps ( pminp , pmaxp )
else
terrain_map , lake_map = heightmaps ( minp , maxp )
end
-- Check that there is at least one position that reaches min y
if minp.y > sea_level then
local y0 = minp.y
local is_empty = true
for i = 1 , # terrain_map do
if terrain_map [ i ] >= y0 or lake_map [ i ] >= y0 then
is_empty = false
break
end
end
-- If not, skip chunk
if is_empty then
local t = os.clock ( ) - t0
ngen = ngen + 1
sumtime = sumtime + t
sumtime2 = sumtime2 + t * t
minetest.log ( " verbose " , " [mapgen_rivers] Skipping empty chunk (fully above ground level) " )
minetest.log ( " verbose " , ( " [mapgen_rivers] Done in %5.3f s " ) : format ( t ) )
return
end
end
local c_stone = minetest.get_content_id ( " mapgen_stone " )
local c_water = minetest.get_content_id ( " mapgen_water_source " )
local c_rwater = minetest.get_content_id ( " mapgen_river_water_source " )
local c_dirt , c_lawn , c_dirtsnow , c_snow , c_sand , c_ice
if use_biomes then
c_dirt = minetest.get_content_id ( " default:dirt " )
c_lawn = minetest.get_content_id ( " default:dirt_with_grass " )
c_dirtsnow = minetest.get_content_id ( " default:dirt_with_snow " )
c_snow = minetest.get_content_id ( " default:snowblock " )
c_sand = minetest.get_content_id ( " default:sand " )
c_ice = minetest.get_content_id ( " default:ice " )
end
2024-01-05 00:02:30 +01:00
local emin , emax = vm : get_emerged_area ( )
2023-12-28 15:27:36 +01:00
vm : get_data ( data )
local a = VoxelArea : new ( { MinEdge = emin , MaxEdge = emax } )
local ystride = a.ystride -- Tip : the ystride of a VoxelArea is the number to add to the array index to get the index of the position above. It's faster because it avoids to completely recalculate the index.
local nid = mapsize.x * ( mapsize.y - 1 ) + 1
local incrY = - mapsize.x
local incrX = 1 - mapsize.y * incrY
local incrZ = mapsize.x * mapsize.y - mapsize.x * incrX - mapsize.x * mapsize.y * incrY
local i2d = 1
for z = minp.z , maxp.z do
for x = minp.x , maxp.x do
local ivm = a : index ( x , maxp.y + 1 , z )
local ground_above = false
local temperature
if use_biomes then
temperature = noise_heat_map [ i2d ] + noise_heat_blend_map [ i2d ]
end
local terrain , lake
if not use_distort then
terrain = terrain_map [ i2d ]
lake = lake_map [ i2d ]
end
for y = maxp.y + 1 , minp.y , - 1 do
if use_distort then
local xn = noise_x_map [ nid ]
local zn = noise_z_map [ nid ]
local x0 = floor ( xn )
local z0 = floor ( zn )
local i0 = i_origin + z0 * incr + x0
local i1 = i0 + 1
local i2 = i1 + incr
local i3 = i2 - 1
terrain = interp ( terrain_map [ i0 ] , terrain_map [ i1 ] , terrain_map [ i2 ] , terrain_map [ i3 ] , xn - x0 , zn - z0 )
lake = min ( lake_map [ i0 ] , lake_map [ i1 ] , lake_map [ i2 ] , lake_map [ i3 ] )
end
if y <= maxp.y then
local is_lake = lake > terrain
if y <= terrain then
if not use_biomes or y <= terrain - 1 or ground_above then
data [ ivm ] = c_stone
elseif is_lake or y < sea_level then
data [ ivm ] = c_sand
else
local temperature_y = temperature - y * elevation_chill
if temperature_y >= 15 then
data [ ivm ] = c_lawn
elseif temperature_y >= 0 then
data [ ivm ] = c_dirtsnow
else
data [ ivm ] = c_snow
end
end
elseif y <= lake and lake > sea_level then
if not use_biomes or temperature - y * elevation_chill >= 0 then
data [ ivm ] = c_rwater
else
data [ ivm ] = c_ice
end
elseif y <= sea_level then
data [ ivm ] = c_water
end
end
ground_above = y <= terrain
ivm = ivm - ystride
if use_distort then
nid = nid + incrY
end
end
if use_distort then
nid = nid + incrX
end
i2d = i2d + 1
end
if use_distort then
nid = nid + incrZ
end
end
if use_biomegen_mod then
biomegen.generate_all ( data , a , vm , minp , maxp , seed )
else
vm : set_data ( data )
minetest.generate_ores ( vm , minp , maxp )
end
vm : set_lighting ( { day = 0 , night = 0 } )
vm : calc_lighting ( )
vm : update_liquids ( )
2024-01-05 00:02:30 +01:00
if mapgen_rivers.thread == " main " then
vm : write_to_map ( )
end
2023-12-28 15:27:36 +01:00
local t = os.clock ( ) - t0
ngen = ngen + 1
sumtime = sumtime + t
sumtime2 = sumtime2 + t * t
minetest.log ( " verbose " , ( " [mapgen_rivers] Done in %5.3f s " ) : format ( t ) )
end
2024-01-05 00:02:30 +01:00
if mapgen_rivers.thread == " main " then
minetest.register_on_generated ( function ( minp , maxp , seed )
local vm = minetest.get_mapgen_object ( " voxelmanip " )
generate ( vm , minp , maxp , seed )
end )
elseif mapgen_rivers.thread == " mapgen " then
minetest.register_on_generated ( generate )
end
2023-12-28 15:27:36 +01:00
minetest.register_on_shutdown ( function ( )
if ngen == 0 then
return
end
local avg = sumtime / ngen
local std = math.sqrt ( sumtime2 / ngen - avg * avg )
minetest.log ( " action " , ( " [mapgen_rivers] Mapgen statistics: \n - Mapgen calls: %4d \n - Mean time: %5.3f s \n - Standard deviation: %5.3f s " ) : format ( ngen , avg , std ) )
end )