4 Commits

4 changed files with 47 additions and 7 deletions

View File

@ -5,10 +5,16 @@ Semi-procedural map generator for Minetest 5.x. It aims to create realistic and
Its main particularity compared to conventional Minetest mapgens is that rivers that flow strictly downhill, and combine together to form wider rivers, until they reach the sea. Another notable feature is the possibility of large lakes above sea level. Its main particularity compared to conventional Minetest mapgens is that rivers that flow strictly downhill, and combine together to form wider rivers, until they reach the sea. Another notable feature is the possibility of large lakes above sea level.
![Screenshot](https://user-images.githubusercontent.com/6905002/98825953-6289d980-2435-11eb-9e0b-704a95663ce0.png) ![Screenshot](https://content.minetest.net/uploads/fff09f2269.png)
It used to be composed of a Python script doing pre-generation, and a Lua mod reading the pre-generation output and generating the map. The code has been rewritten in full Lua for version 1.0 (July 2021), and is now usable out-of-the-box as any other Minetest mod. It used to be composed of a Python script doing pre-generation, and a Lua mod reading the pre-generation output and generating the map. The code has been rewritten in full Lua for version 1.0 (July 2021), and is now usable out-of-the-box as any other Minetest mod.
# Author and license
License: GNU LGPLv3.0
Code: Gaël de Sailly
Flow routing algorithm concept (in `terrainlib/rivermapper.lua`): Cordonnier, G., Bovy, B., & Braun, J. (2019). A versatile, linear complexity algorithm for flow routing in topographies with depressions. Earth Surface Dynamics, 7(2), 549-562.
# Requirements # Requirements
Mod dependencies: `default` required, and [`biomegen`](https://github.com/Gael-de-Sailly/biomegen) optional (provides biome system). Mod dependencies: `default` required, and [`biomegen`](https://github.com/Gael-de-Sailly/biomegen) optional (provides biome system).

View File

@ -4,6 +4,11 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) .. '/'
mapgen_rivers.modpath = modpath mapgen_rivers.modpath = modpath
mapgen_rivers.world_data_path = minetest.get_worldpath() .. '/river_data/' mapgen_rivers.world_data_path = minetest.get_worldpath() .. '/river_data/'
if minetest.get_mapgen_setting("mg_name") ~= "singlenode" then
minetest.set_mapgen_setting("mg_name", "singlenode", true)
print("[mapgen_rivers] Mapgen set to singlenode")
end
dofile(modpath .. 'settings.lua') dofile(modpath .. 'settings.lua')
local sea_level = mapgen_rivers.settings.sea_level local sea_level = mapgen_rivers.settings.sea_level

View File

@ -1,5 +1,17 @@
-- rivermapper.lua -- rivermapper.lua
-- This file provide functions to construct the river tree from an elevation model.
-- Based on a research paper:
--
-- Cordonnier, G., Bovy, B., and Braun, J.:
-- A versatile, linear complexity algorithm for flow routing in topographies with depressions,
-- Earth Surf. Dynam., 7, 549562, https://doi.org/10.5194/esurf-7-549-2019, 2019.
--
-- Big thanks to them for releasing this paper under a free license ! :)
-- The algorithm here makes use of most of the paper's concepts, including the Planar Boruvka algorithm.
-- Only flow_local and accumulate_flow are custom algorithms.
local function flow_local_semirandom(plist) local function flow_local_semirandom(plist)
local sum = 0 local sum = 0
for i=1, #plist do for i=1, #plist do

29
view.py
View File

@ -11,10 +11,12 @@ try:
import colorcet as cc import colorcet as cc
cmap1 = cc.cm.CET_L11 cmap1 = cc.cm.CET_L11
cmap2 = cc.cm.CET_L12 cmap2 = cc.cm.CET_L12
cmap3 = cc.cm.CET_L6.reversed()
except ImportError: # No module colorcet except ImportError: # No module colorcet
import matplotlib.cm as cm import matplotlib.cm as cm
cmap1 = cm.summer cmap1 = cm.summer
cmap2 = cm.Blues cmap2 = cm.ocean.reversed()
cmap3 = cm.Blues
except ImportError: # No module matplotlib except ImportError: # No module matplotlib
has_matplotlib = False has_matplotlib = False
@ -24,10 +26,12 @@ if has_matplotlib:
water = np.maximum(lakes_sea - dem, 0) water = np.maximum(lakes_sea - dem, 0)
max_elev = dem.max() max_elev = dem.max()
max_depth = water.max() max_depth = water.max()
max_lake_depth = lakes.max()
ls = mcl.LightSource(azdeg=315, altdeg=45) ls = mcl.LightSource(azdeg=315, altdeg=45)
norm_ground = plt.Normalize(vmin=sea_level, vmax=max_elev) norm_ground = plt.Normalize(vmin=sea_level, vmax=max_elev)
norm_sea = plt.Normalize(vmin=0, vmax=max_depth) norm_sea = plt.Normalize(vmin=0, vmax=max_depth)
norm_lake = plt.Normalize(vmin=0, vmax=max_lake_depth)
rgb = ls.shade(dem, cmap=cmap1, vert_exag=1/scale, blend_mode='soft', norm=norm_ground) rgb = ls.shade(dem, cmap=cmap1, vert_exag=1/scale, blend_mode='soft', norm=norm_ground)
(X, Y) = dem.shape (X, Y) = dem.shape
@ -37,13 +41,23 @@ if has_matplotlib:
extent = (-0.5*scale, (Y-0.5)*scale, -0.5*scale, (X-0.5)*scale) extent = (-0.5*scale, (Y-0.5)*scale, -0.5*scale, (X-0.5)*scale)
plt.imshow(np.flipud(rgb), extent=extent, interpolation='antialiased') plt.imshow(np.flipud(rgb), extent=extent, interpolation='antialiased')
alpha = (water > 0).astype('u1') alpha = (water > 0).astype('u1')
plt.imshow(np.flipud(water), alpha=np.flipud(alpha), cmap=cmap2, extent=extent, vmin=0, vmax=max_depth, interpolation='antialiased') lakes_alpha = ((lakes_sea - np.maximum(dem,sea_level)) > 0).astype('u1')
# plt.imshow(np.flipud(water), alpha=np.flipud(alpha), cmap=cmap2, extent=extent, vmin=0, vmax=max_depth, interpolation='antialiased')
plt.imshow(np.flipud(water), alpha=np.flipud(alpha), cmap=cmap3, extent=extent, vmin=0, vmax=max_depth, interpolation='antialiased')
plt.imshow(np.flipud(water), alpha=np.flipud(lakes_alpha), cmap=cmap2, extent=extent, vmin=0, vmax=max_depth, interpolation='antialiased')
sm1 = plt.cm.ScalarMappable(cmap=cmap1, norm=norm_ground) sm1 = plt.cm.ScalarMappable(cmap=cmap1, norm=norm_ground)
plt.colorbar(sm1).set_label('Elevation') plt.colorbar(sm1).set_label('Elevation')
sm2 = plt.cm.ScalarMappable(cmap=cmap2, norm=norm_sea) sm2 = plt.cm.ScalarMappable(cmap=cmap2, norm=norm_lake)
plt.colorbar(sm2).set_label('Water depth') cb2 = plt.colorbar(sm2)
cb2.ax.invert_yaxis()
cb2.set_label('Lake Depth')
sm3 = plt.cm.ScalarMappable(cmap=cmap3, norm=norm_sea)
cb3 = plt.colorbar(sm3)
cb3.ax.invert_yaxis()
cb3.set_label('Ocean Depth')
plt.xlabel('X') plt.xlabel('X')
plt.ylabel('Z') plt.ylabel('Z')
@ -84,9 +98,10 @@ def stats(dem, lakes, scale=1):
lake_surface = lake.sum() lake_surface = lake.sum()
print('--- General ---') print('--- General ---')
print('Grid size: {:5d}x{:5d}'.format(dem.shape[0], dem.shape[1])) print('Grid size (dem): {:5d}x{:5d}'.format(dem.shape[0], dem.shape[1]))
print('Grid size (lakes): {:5d}x{:5d}'.format(lakes.shape[0], lakes.shape[1]))
if scale > 1: if scale > 1:
print('Map size: {:5d}x{:5d}'.format(int(dem.shape[0]*scale), int(dem.shape[1]*scale))) print('Map size: {:5d}x{:5d}'.format(int(dem.shape[0]*scale), int(dem.shape[1]*scale)))
print() print()
print('--- Surfaces ---') print('--- Surfaces ---')
print('Continents: {:6.2%}'.format(continent_surface/surface)) print('Continents: {:6.2%}'.format(continent_surface/surface))
@ -100,3 +115,5 @@ def stats(dem, lakes, scale=1):
print('Mean continent elev: {:4.0f}'.format((dem*continent).sum()/continent_surface)) print('Mean continent elev: {:4.0f}'.format((dem*continent).sum()/continent_surface))
print('Lowest elevation: {:4.0f}'.format(dem.min())) print('Lowest elevation: {:4.0f}'.format(dem.min()))
print('Highest elevation: {:4.0f}'.format(dem.max())) print('Highest elevation: {:4.0f}'.format(dem.max()))
print()