1 Commits

11 changed files with 88 additions and 156 deletions

View File

@ -1,5 +1,5 @@
# Map Generator with Rivers # Map Generator with Rivers
`mapgen_rivers v1.0.1` by Gaël de Sailly. `mapgen_rivers v1.0` by Gaël de Sailly.
Semi-procedural map generator for Minetest 5.x. It aims to create realistic and nice-looking landscapes for the game, focused on river networks. It is based on algorithms modelling water flow and river erosion at a broad scale, similar to some used by researchers in Earth Sciences. It is taking some inspiration from [Fastscape](https://github.com/fastscape-lem/fastscape). Semi-procedural map generator for Minetest 5.x. It aims to create realistic and nice-looking landscapes for the game, focused on river networks. It is based on algorithms modelling water flow and river erosion at a broad scale, similar to some used by researchers in Earth Sciences. It is taking some inspiration from [Fastscape](https://github.com/fastscape-lem/fastscape).

View File

@ -1,6 +1,3 @@
local sqrt, abs = math.sqrt, math.abs
local unpk = unpack
local function distance_to_segment(x1, y1, x2, y2, x, y) local function distance_to_segment(x1, y1, x2, y2, x, y)
-- get the distance between point (x,y) and segment (x1,y1)-(x2,y2) -- get the distance between point (x,y) and segment (x1,y1)-(x2,y2)
local a = (x1-x2)^2 + (y1-y2)^2 -- square of distance local a = (x1-x2)^2 + (y1-y2)^2 -- square of distance
@ -8,13 +5,13 @@ local function distance_to_segment(x1, y1, x2, y2, x, y)
local c = (x2-x)^2 + (y2-y)^2 local c = (x2-x)^2 + (y2-y)^2
if a + b < c then if a + b < c then
-- The closest point of the segment is the extremity 1 -- The closest point of the segment is the extremity 1
return sqrt(b) return math.sqrt(b)
elseif a + c < b then elseif a + c < b then
-- The closest point of the segment is the extremity 2 -- The closest point of the segment is the extremity 2
return sqrt(c) return math.sqrt(c)
else else
-- The closest point is on the segment -- The closest point is on the segment
return abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / sqrt(a) return math.abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / math.sqrt(a)
end end
end end
@ -22,8 +19,8 @@ local function transform_quadri(X, Y, x, y)
-- To index points in an irregular quadrilateral, giving x and y between 0 (one edge) and 1 (opposite edge) -- To index points in an irregular quadrilateral, giving x and y between 0 (one edge) and 1 (opposite edge)
-- X, Y 4-vectors giving the coordinates of the 4 vertices -- X, Y 4-vectors giving the coordinates of the 4 vertices
-- x, y position to index. -- x, y position to index.
local x1, x2, x3, x4 = unpk(X) local x1, x2, x3, x4 = unpack(X)
local y1, y2, y3, y4 = unpk(Y) local y1, y2, y3, y4 = unpack(Y)
-- Compare distance to 2 opposite edges, they give the X coordinate -- Compare distance to 2 opposite edges, they give the X coordinate
local d23 = distance_to_segment(x2,y2,x3,y3,x,y) local d23 = distance_to_segment(x2,y2,x3,y3,x,y)

View File

@ -6,11 +6,7 @@ local transform_quadri = dofile(modpath .. 'geometry.lua')
local sea_level = mapgen_rivers.settings.sea_level local sea_level = mapgen_rivers.settings.sea_level
local riverbed_slope = mapgen_rivers.settings.riverbed_slope * mapgen_rivers.settings.blocksize local riverbed_slope = mapgen_rivers.settings.riverbed_slope * mapgen_rivers.settings.blocksize
local out_elev = mapgen_rivers.settings.margin_elev local MAP_BOTTOM = -31000
-- Localize for performance
local floor, min, max = math.floor, math.min, math.max
local unpk = unpack
-- Linear interpolation -- Linear interpolation
local function interp(v00, v01, v11, v10, xf, zf) local function interp(v00, v01, v11, v10, xf, zf)
@ -34,11 +30,11 @@ local function heightmaps(minp, maxp)
if poly then if poly then
local xf, zf = transform_quadri(poly.x, poly.z, x, z) local xf, zf = transform_quadri(poly.x, poly.z, x, z)
local i00, i01, i11, i10 = unpk(poly.i) local i00, i01, i11, i10 = unpack(poly.i)
-- Load river width on 4 edges and corners -- Load river width on 4 edges and corners
local r_west, r_north, r_east, r_south = unpk(poly.rivers) local r_west, r_north, r_east, r_south = unpack(poly.rivers)
local c_NW, c_NE, c_SE, c_SW = unpk(poly.river_corners) local c_NW, c_NE, c_SE, c_SW = unpack(poly.river_corners)
-- Calculate the depth factor for each edge and corner. -- Calculate the depth factor for each edge and corner.
-- Depth factor: -- Depth factor:
@ -68,10 +64,10 @@ local function heightmaps(minp, maxp)
-- Transform the coordinates to have xf and zf = 0 or 1 in rivers (to avoid rivers having lateral slope and to accomodate the surrounding smoothly) -- Transform the coordinates to have xf and zf = 0 or 1 in rivers (to avoid rivers having lateral slope and to accomodate the surrounding smoothly)
if imax == 0 then if imax == 0 then
local x0 = max(r_west, c_NW-zf, zf-c_SW) local x0 = math.max(r_west, c_NW-zf, zf-c_SW)
local x1 = min(r_east, c_NE+zf, c_SE-zf) local x1 = math.min(r_east, c_NE+zf, c_SE-zf)
local z0 = max(r_north, c_NW-xf, xf-c_NE) local z0 = math.max(r_north, c_NW-xf, xf-c_NE)
local z1 = min(r_south, c_SW+xf, c_SE-xf) local z1 = math.min(r_south, c_SW+xf, c_SE-xf)
xf = (xf-x0) / (x1-x0) xf = (xf-x0) / (x1-x0)
zf = (zf-z0) / (z1-z0) zf = (zf-z0) / (z1-z0)
elseif imax == 1 then elseif imax == 1 then
@ -94,7 +90,7 @@ local function heightmaps(minp, maxp)
-- Determine elevation by interpolation -- Determine elevation by interpolation
local vdem = poly.dem local vdem = poly.dem
local terrain_height = floor(0.5+interp( local terrain_height = math.floor(0.5+interp(
vdem[1], vdem[1],
vdem[2], vdem[2],
vdem[3], vdem[3],
@ -119,17 +115,17 @@ local function heightmaps(minp, maxp)
lake_id = 1 lake_id = 1
end end
end end
local lake_height = max(floor(poly.lake[lake_id]), terrain_height) local lake_height = math.max(math.floor(poly.lake[lake_id]), terrain_height)
if imax > 0 and depth_factor_max > 0 then if imax > 0 and depth_factor_max > 0 then
terrain_height = min(max(lake_height, sea_level) - floor(1+depth_factor_max*riverbed_slope), terrain_height) terrain_height = math.min(math.max(lake_height, sea_level) - math.floor(1+depth_factor_max*riverbed_slope), terrain_height)
end end
terrain_height_map[i] = terrain_height terrain_height_map[i] = terrain_height
lake_height_map[i] = lake_height lake_height_map[i] = lake_height
else else
terrain_height_map[i] = out_elev terrain_height_map[i] = MAP_BOTTOM
lake_height_map[i] = out_elev lake_height_map[i] = MAP_BOTTOM
end end
i = i + 1 i = i + 1
end end

View File

@ -6,7 +6,7 @@ mapgen_rivers.world_data_path = minetest.get_worldpath() .. '/river_data/'
if minetest.get_mapgen_setting("mg_name") ~= "singlenode" then if minetest.get_mapgen_setting("mg_name") ~= "singlenode" then
minetest.set_mapgen_setting("mg_name", "singlenode", true) minetest.set_mapgen_setting("mg_name", "singlenode", true)
minetest.log("warning", "[mapgen_rivers] Mapgen set to singlenode") print("[mapgen_rivers] Mapgen set to singlenode")
end end
dofile(modpath .. 'settings.lua') dofile(modpath .. 'settings.lua')
@ -32,9 +32,6 @@ local function interp(v00, v01, v11, v10, xf, zf)
return v1*zf + v0*(1-zf) return v1*zf + v0*(1-zf)
end end
-- Localize for performance
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
@ -51,7 +48,7 @@ local sumtime2 = 0
local ngen = 0 local ngen = 0
local function generate(minp, maxp, seed) local function generate(minp, maxp, seed)
minetest.log("info", ("[mapgen_rivers] Generating from %s to %s"):format(minetest.pos_to_string(minp), minetest.pos_to_string(maxp))) print(("[mapgen_rivers] Generating from %s to %s"):format(minetest.pos_to_string(minp), minetest.pos_to_string(maxp)))
local chulens = { local chulens = {
x = maxp.x-minp.x+1, x = maxp.x-minp.x+1,
@ -114,8 +111,8 @@ local function generate(minp, maxp, seed)
end end
end end
local pminp = {x=floor(xmin), z=floor(zmin)} local pminp = {x=math.floor(xmin), z=math.floor(zmin)}
local pmaxp = {x=floor(xmax)+1, z=floor(zmax)+1} local pmaxp = {x=math.floor(xmax)+1, z=math.floor(zmax)+1}
incr = pmaxp.x-pminp.x+1 incr = pmaxp.x-pminp.x+1
i_origin = 1 - pminp.z*incr - pminp.x i_origin = 1 - pminp.z*incr - pminp.x
terrain_map, lake_map = heightmaps(pminp, pmaxp) terrain_map, lake_map = heightmaps(pminp, pmaxp)
@ -123,30 +120,6 @@ local function generate(minp, maxp, seed)
terrain_map, lake_map = heightmaps(minp, maxp) terrain_map, lake_map = heightmaps(minp, maxp)
end 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("default:stone") local c_stone = minetest.get_content_id("default:stone")
local c_dirt = minetest.get_content_id("default:dirt") local c_dirt = minetest.get_content_id("default:dirt")
local c_lawn = minetest.get_content_id("default:dirt_with_grass") local c_lawn = minetest.get_content_id("default:dirt_with_grass")
@ -172,7 +145,7 @@ local function generate(minp, maxp, seed)
for z = minp.z, maxp.z do for z = minp.z, maxp.z do
for x = minp.x, maxp.x do for x = minp.x, maxp.x do
local ivm = a:index(x, maxp.y+1, z) local ivm = a:index(x, minp.y, z)
local ground_above = false local ground_above = false
local temperature local temperature
if use_biomes then if use_biomes then
@ -188,8 +161,8 @@ local function generate(minp, maxp, seed)
if use_distort then if use_distort then
local xn = noise_x_map[nid] local xn = noise_x_map[nid]
local zn = noise_z_map[nid] local zn = noise_z_map[nid]
local x0 = floor(xn) local x0 = math.floor(xn)
local z0 = floor(zn) local z0 = math.floor(zn)
local i0 = i_origin + z0*incr + x0 local i0 = i_origin + z0*incr + x0
local i1 = i0+1 local i1 = i0+1
@ -197,12 +170,13 @@ local function generate(minp, maxp, seed)
local i3 = i2-1 local i3 = i2-1
terrain = interp(terrain_map[i0], terrain_map[i1], terrain_map[i2], terrain_map[i3], xn-x0, zn-z0) 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]) lake = math.min(lake_map[i0], lake_map[i1], lake_map[i2], lake_map[i3])
end end
if y <= maxp.y then if y <= maxp.y then
local is_lake = lake > terrain local is_lake = lake > terrain
local ivm = a:index(x, y, z)
if y <= terrain then if y <= terrain then
if not use_biomes or y <= terrain-1 or ground_above then if not use_biomes or y <= terrain-1 or ground_above then
data[ivm] = c_stone data[ivm] = c_stone
@ -231,7 +205,7 @@ local function generate(minp, maxp, seed)
ground_above = y <= terrain ground_above = y <= terrain
ivm = ivm - ystride ivm = ivm + ystride
if use_distort then if use_distort then
nid = nid + incrY nid = nid + incrY
end end
@ -259,17 +233,18 @@ local function generate(minp, maxp, seed)
vm:calc_lighting() vm:calc_lighting()
vm:update_liquids() vm:update_liquids()
vm:write_to_map() vm:write_to_map()
local t1 = os.clock()
local t = os.clock()-t0 local t = t1-t0
ngen = ngen + 1 ngen = ngen + 1
sumtime = sumtime + t sumtime = sumtime + t
sumtime2 = sumtime2 + t*t sumtime2 = sumtime2 + t*t
minetest.log("verbose", ("[mapgen_rivers] Done in %5.3f s"):format(t)) print(("[mapgen_rivers] Done in %5.3f s"):format(t))
end end
minetest.register_on_generated(generate) minetest.register_on_generated(generate)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
local avg = sumtime / ngen local avg = sumtime / ngen
local std = math.sqrt(sumtime2/ngen - avg*avg) 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)) print(("[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) end)

View File

@ -1,15 +1,12 @@
local worldpath = mapgen_rivers.world_data_path local worldpath = mapgen_rivers.world_data_path
local floor = math.floor
local sbyte, schar = string.byte, string.char
local unpk = unpack
function mapgen_rivers.load_map(filename, bytes, signed, size, converter) function mapgen_rivers.load_map(filename, bytes, signed, size, converter)
local file = io.open(worldpath .. filename, 'rb') local file = io.open(worldpath .. filename, 'rb')
local data = file:read('*all') local data = file:read('*all')
if #data < bytes*size then if #data < bytes*size then
data = minetest.decompress(data) data = minetest.decompress(data)
end end
local sbyte = string.byte
local map = {} local map = {}
@ -38,6 +35,8 @@ function mapgen_rivers.load_map(filename, bytes, signed, size, converter)
return map return map
end end
local sbyte = string.byte
local loader_mt = { local loader_mt = {
__index = function(loader, i) __index = function(loader, i)
local file = loader.file local file = loader.file
@ -76,6 +75,9 @@ end
function mapgen_rivers.write_map(filename, data, bytes) function mapgen_rivers.write_map(filename, data, bytes)
local size = #data local size = #data
local file = io.open(worldpath .. filename, 'wb') local file = io.open(worldpath .. filename, 'wb')
local mfloor = math.floor
local schar = string.char
local upack = unpack
local bytelist = {} local bytelist = {}
for j=1, bytes do for j=1, bytes do
@ -83,15 +85,15 @@ function mapgen_rivers.write_map(filename, data, bytes)
end end
for i=1, size do for i=1, size do
local n = floor(data[i]) local n = mfloor(data[i])
data[i] = n data[i] = n
for j=bytes, 2, -1 do for j=bytes, 2, -1 do
bytelist[j] = n % 256 bytelist[j] = n % 256
n = floor(n / 256) n = mfloor(n / 256)
end end
bytelist[1] = n % 256 bytelist[1] = n % 256
file:write(schar(unpk(bytelist))) file:write(schar(upack(bytelist)))
end end
file:close() file:close()

View File

@ -73,7 +73,7 @@ for name, np in pairs(mapgen_rivers.noise_params) do
if lac > 1 then if lac > 1 then
local omax = math.floor(math.log(math.min(np.spread.x, np.spread.y, np.spread.z)) / math.log(lac))+1 local omax = math.floor(math.log(math.min(np.spread.x, np.spread.y, np.spread.z)) / math.log(lac))+1
if np.octaves > omax then if np.octaves > omax then
minetest.log("warning", "[mapgen_rivers] Noise " .. name .. ": 'octaves' reduced to " .. omax) print("[mapgen_rivers] Noise " .. name .. ": 'octaves' reduced to " .. omax)
np.octaves = omax np.octaves = omax
end end
end end

View File

@ -33,7 +33,7 @@ if first_mapgen then
-- Generate a map!! -- Generate a map!!
local pregenerate = dofile(mapgen_rivers.modpath .. '/pregenerate.lua') local pregenerate = dofile(mapgen_rivers.modpath .. '/pregenerate.lua')
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
minetest.log("action", '[mapgen_rivers] Generating grid, this may take a while...') print('[mapgen_rivers] Generating grid')
pregenerate(load_all) pregenerate(load_all)
if load_all then if load_all then
@ -58,9 +58,9 @@ if not (first_mapgen and load_all) then
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
if load_all then if load_all then
minetest.log("action", '[mapgen_rivers] Loading full grid') print('[mapgen_rivers] Loading full grid')
else else
minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders') print('[mapgen_rivers] Loading grid as interactive loaders')
end end
local grid = mapgen_rivers.grid local grid = mapgen_rivers.grid
@ -90,19 +90,16 @@ if mapgen_rivers.settings.center then
map_offset.z = blocksize*Z/2 map_offset.z = blocksize*Z/2
end end
-- Localize for performance
local floor, ceil, min, max, abs = math.floor, math.ceil, math.min, math.max, math.abs
local min_catchment = mapgen_rivers.settings.min_catchment / (blocksize*blocksize) local min_catchment = mapgen_rivers.settings.min_catchment / (blocksize*blocksize)
local wpower = mapgen_rivers.settings.river_widening_power local wpower = mapgen_rivers.settings.river_widening_power
local wfactor = 1/(2*blocksize * min_catchment^wpower) local wfactor = 1/(2*blocksize * min_catchment^wpower)
local function river_width(flow) local function river_width(flow)
flow = abs(flow) flow = math.abs(flow)
if flow < min_catchment then if flow < min_catchment then
return 0 return 0
end end
return min(wfactor * flow ^ wpower, 1) return math.min(wfactor * flow ^ wpower, 1)
end end
local noise_heat -- Need a large-scale noise here so no heat blend local noise_heat -- Need a large-scale noise here so no heat blend
@ -141,8 +138,8 @@ local function make_polygons(minp, maxp)
local polygons = {} local polygons = {}
-- Determine the minimum and maximum coordinates of the polygons that could be on the chunk, knowing that they have an average size of 'blocksize' and a maximal offset of 0.5 blocksize. -- Determine the minimum and maximum coordinates of the polygons that could be on the chunk, knowing that they have an average size of 'blocksize' and a maximal offset of 0.5 blocksize.
local xpmin, xpmax = max(floor((minp.x+map_offset.x)/blocksize - 0.5), 0), min(ceil((maxp.x+map_offset.x)/blocksize + 0.5), X-2) local xpmin, xpmax = math.max(math.floor((minp.x+map_offset.x)/blocksize - 0.5), 0), math.min(math.ceil((maxp.x+map_offset.x)/blocksize + 0.5), X-2)
local zpmin, zpmax = max(floor((minp.z+map_offset.z)/blocksize - 0.5), 0), min(ceil((maxp.z+map_offset.z)/blocksize + 0.5), Z-2) local zpmin, zpmax = math.max(math.floor((minp.z+map_offset.z)/blocksize - 0.5), 0), math.min(math.ceil((maxp.z+map_offset.z)/blocksize + 0.5), Z-2)
-- Iterate over the polygons -- Iterate over the polygons
for xp = xpmin, xpmax do for xp = xpmin, xpmax do
@ -168,8 +165,8 @@ local function make_polygons(minp, maxp)
local bounds = {} -- Will be a list of the intercepts of polygon edges for every Z position (scanline algorithm) local bounds = {} -- Will be a list of the intercepts of polygon edges for every Z position (scanline algorithm)
-- Calculate the min and max Z positions -- Calculate the min and max Z positions
local zmin = max(floor(min(unpack(poly_z)))+1, minp.z) local zmin = math.max(math.floor(math.min(unpack(poly_z)))+1, minp.z)
local zmax = min(floor(max(unpack(poly_z))), maxp.z) local zmax = math.min(math.floor(math.max(unpack(poly_z))), maxp.z)
-- And initialize the arrays -- And initialize the arrays
for z=zmin, zmax do for z=zmin, zmax do
bounds[z] = {} bounds[z] = {}
@ -179,14 +176,14 @@ local function make_polygons(minp, maxp)
for i2=1, 4 do -- Loop on 4 edges for i2=1, 4 do -- Loop on 4 edges
local z1, z2 = poly_z[i1], poly_z[i2] local z1, z2 = poly_z[i1], poly_z[i2]
-- Calculate the integer Z positions over which this edge spans -- Calculate the integer Z positions over which this edge spans
local lzmin = floor(min(z1, z2))+1 local lzmin = math.floor(math.min(z1, z2))+1
local lzmax = floor(max(z1, z2)) local lzmax = math.floor(math.max(z1, z2))
if lzmin <= lzmax then -- If there is at least one position in it if lzmin <= lzmax then -- If there is at least one position in it
local x1, x2 = poly_x[i1], poly_x[i2] local x1, x2 = poly_x[i1], poly_x[i2]
-- Calculate coefficient of the equation defining the edge: X=aZ+b -- Calculate coefficient of the equation defining the edge: X=aZ+b
local a = (x1-x2) / (z1-z2) local a = (x1-x2) / (z1-z2)
local b = (x1 - a*z1) local b = (x1 - a*z1)
for z=max(lzmin, minp.z), min(lzmax, maxp.z) do for z=math.max(lzmin, minp.z), math.min(lzmax, maxp.z) do
-- For every Z position involved, add the intercepted X position in the table -- For every Z position involved, add the intercepted X position in the table
table.insert(bounds[z], a*z+b) table.insert(bounds[z], a*z+b)
end end
@ -197,11 +194,11 @@ local function make_polygons(minp, maxp)
-- Now sort the bounds list -- Now sort the bounds list
local zlist = bounds[z] local zlist = bounds[z]
table.sort(zlist) table.sort(zlist)
local c = floor(#zlist/2) local c = math.floor(#zlist/2)
for l=1, c do for l=1, c do
-- Take pairs of X coordinates: all positions between them belong to the polygon. -- Take pairs of X coordinates: all positions between them belong to the polygon.
local xmin = max(floor(zlist[l*2-1])+1, minp.x) local xmin = math.max(math.floor(zlist[l*2-1])+1, minp.x)
local xmax = min(floor(zlist[l*2]), maxp.x) local xmax = math.min(math.floor(zlist[l*2]), maxp.x)
local i = (z-minp.z) * chulens + (xmin-minp.x) + 1 local i = (z-minp.z) * chulens + (xmin-minp.x) + 1
for x=xmin, xmax do for x=xmin, xmax do
-- Fill the map at these places -- Fill the map at these places
@ -223,16 +220,16 @@ local function make_polygons(minp, maxp)
local riverD = river_width(rivers[iD]) local riverD = river_width(rivers[iD])
if glaciers then -- Widen the river if glaciers then -- Widen the river
if get_temperature(poly_x[1], poly_dem[1], poly_z[1]) < 0 then if get_temperature(poly_x[1], poly_dem[1], poly_z[1]) < 0 then
riverA = min(riverA*glacier_factor, 1) riverA = math.min(riverA*glacier_factor, 1)
end end
if get_temperature(poly_x[2], poly_dem[2], poly_z[2]) < 0 then if get_temperature(poly_x[2], poly_dem[2], poly_z[2]) < 0 then
riverB = min(riverB*glacier_factor, 1) riverB = math.min(riverB*glacier_factor, 1)
end end
if get_temperature(poly_x[3], poly_dem[3], poly_z[3]) < 0 then if get_temperature(poly_x[3], poly_dem[3], poly_z[3]) < 0 then
riverC = min(riverC*glacier_factor, 1) riverC = math.min(riverC*glacier_factor, 1)
end end
if get_temperature(poly_x[4], poly_dem[4], poly_z[4]) < 0 then if get_temperature(poly_x[4], poly_dem[4], poly_z[4]) < 0 then
riverD = min(riverD*glacier_factor, 1) riverD = math.min(riverD*glacier_factor, 1)
end end
end end

View File

@ -13,38 +13,6 @@ local time_step = mapgen_rivers.settings.evol_time_step
local niter = math.ceil(time/time_step) local niter = math.ceil(time/time_step)
time_step = time / niter time_step = time / niter
local use_margin = mapgen_rivers.settings.margin
local margin_width = mapgen_rivers.settings.margin_width / blocksize
local margin_elev = mapgen_rivers.settings.margin_elev
local function margin(dem, width, elev)
local X, Y = dem.X, dem.Y
for i=1, width do
local c1 = ((i-1)/width) ^ 0.5
local c2 = (1-c1) * elev
local index = (i-1)*X + 1
for x=1, X do
dem[index] = dem[index] * c1 + c2
index = index + 1
end
index = i
for y=1, Y do
dem[index] = dem[index] * c1 + c2
index = index + X
end
index = X*(Y-i) + 1
for x=1, X do
dem[index] = dem[index] * c1 + c2
index = index + 1
end
index = X-i + 1
for y=1, Y do
dem[index] = dem[index] * c1 + c2
index = index + X
end
end
end
local function pregenerate(keep_loaded) local function pregenerate(keep_loaded)
local grid = mapgen_rivers.grid local grid = mapgen_rivers.grid
local size = grid.size local size = grid.size
@ -58,10 +26,6 @@ local function pregenerate(keep_loaded)
dem.X = size.x dem.X = size.x
dem.Y = size.y dem.Y = size.y
if use_margin then
margin(dem, margin_width, margin_elev)
end
local model = EvolutionModel(evol_params) local model = EvolutionModel(evol_params)
model.dem = dem model.dem = dem
local ref_dem = model:define_isostasy(dem) local ref_dem = model:define_isostasy(dem)
@ -69,7 +33,7 @@ local function pregenerate(keep_loaded)
local tectonic_step = tectonic_speed * time_step local tectonic_step = tectonic_speed * time_step
collectgarbage() collectgarbage()
for i=1, niter do for i=1, niter do
minetest.log("info", "[mapgen_rivers] Iteration " .. i .. " of " .. niter) print("[mapgen_rivers] Iteration " .. i .. " of " .. niter)
model:diffuse(time_step) model:diffuse(time_step)
model:flow() model:flow()
@ -77,9 +41,6 @@ local function pregenerate(keep_loaded)
if i < niter then if i < niter then
if tectonic_step ~= 0 then if tectonic_step ~= 0 then
nobj_base:get_3d_map_flat({x=0, y=tectonic_step*i, z=0}, ref_dem) nobj_base:get_3d_map_flat({x=0, y=tectonic_step*i, z=0}, ref_dem)
if use_margin then
margin(ref_dem, margin_width, margin_elev)
end
end end
model:isostasy() model:isostasy()
end end

View File

@ -1,7 +1,7 @@
local mtsettings = minetest.settings local mtsettings = minetest.settings
local mgrsettings = Settings(minetest.get_worldpath() .. '/mapgen_rivers.conf') local mgrsettings = Settings(minetest.get_worldpath() .. '/mapgen_rivers.conf')
mapgen_rivers.version = "1.0.1" mapgen_rivers.version = "1.0"
local previous_version_mt = mtsettings:get("mapgen_rivers_version") or "0.0" local previous_version_mt = mtsettings:get("mapgen_rivers_version") or "0.0"
local previous_version_mgr = mgrsettings:get("version") or "0.0" local previous_version_mgr = mgrsettings:get("version") or "0.0"
@ -74,9 +74,6 @@ mapgen_rivers.settings = {
grid_x_size = def_setting('grid_x_size', 'number', 1000), grid_x_size = def_setting('grid_x_size', 'number', 1000),
grid_z_size = def_setting('grid_z_size', 'number', 1000), grid_z_size = def_setting('grid_z_size', 'number', 1000),
margin = def_setting('margin', 'bool', true),
margin_width = def_setting('margin_width', 'number', 2000),
margin_elev = def_setting('margin_elev', 'number', -200),
evol_params = { evol_params = {
K = def_setting('river_erosion_coef', 'number', 0.5), K = def_setting('river_erosion_coef', 'number', 0.5),
m = def_setting('river_erosion_power', 'number', 0.4), m = def_setting('river_erosion_power', 'number', 0.4),

View File

@ -17,23 +17,13 @@ mapgen_rivers_grid_x_size (Grid X size) int 1000 50 5000
# Actual size of the map is grid_z_size * blocksize # Actual size of the map is grid_z_size * blocksize
mapgen_rivers_grid_z_size (Grid Z size) int 1000 50 5000 mapgen_rivers_grid_z_size (Grid Z size) int 1000 50 5000
# If margin is enabled, elevation becomes closer to a fixed value when approaching
# the edges of the map.
mapgen_rivers_margin (Margin) bool true
# Width of the transition at map borders, in nodes
mapgen_rivers_margin_width (Margin width) float 2000.0 0.0 15000.0
# Elevation toward which to converge at map borders
mapgen_rivers_margin_elev (Margin elevation) float -200.0 -31000.0 31000.0
# Minimal catchment area for a river to be drawn, in square nodes # Minimal catchment area for a river to be drawn, in square nodes
# Lower value means bigger river density # Lower value means bigger river density
mapgen_rivers_min_catchment (Minimal catchment area) float 3600.0 100.0 1000000.0 mapgen_rivers_min_catchment (Minimal catchment area) float 3600.0 100.0 1000000.0
# Coefficient describing how rivers widen when merging. # Coefficient describing how rivers widen when merging.
# Riwer width is a power law W = a*D^p. D is river flow and p is this parameter. # Riwer width is a power law W = a*D^p. D is river flow and p is this parameter.
# Higher value means that a river will grow more when receiving a tributary. # Higher value means a river needs to receive more tributaries to grow in width.
# Note that a river can never exceed 2*blocksize. # Note that a river can never exceed 2*blocksize.
mapgen_rivers_river_widening_power (River widening power) float 0.5 0.0 1.0 mapgen_rivers_river_widening_power (River widening power) float 0.5 0.0 1.0

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()