9 Commits
v1.0 ... margin

10 changed files with 154 additions and 64 deletions

View File

@ -1,5 +1,5 @@
# Map Generator with Rivers # Map Generator with Rivers
`mapgen_rivers v1.0` by Gaël de Sailly. `mapgen_rivers v1.0.1` 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,3 +1,6 @@
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
@ -5,13 +8,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 math.sqrt(b) return 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 math.sqrt(c) return sqrt(c)
else else
-- The closest point is on the segment -- The closest point is on the segment
return math.abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / math.sqrt(a) return abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / sqrt(a)
end end
end end
@ -19,8 +22,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 = unpack(X) local x1, x2, x3, x4 = unpk(X)
local y1, y2, y3, y4 = unpack(Y) local y1, y2, y3, y4 = unpk(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,7 +6,11 @@ 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 MAP_BOTTOM = -31000 local out_elev = mapgen_rivers.settings.margin_elev
-- 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)
@ -30,11 +34,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 = unpack(poly.i) local i00, i01, i11, i10 = unpk(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 = unpack(poly.rivers) local r_west, r_north, r_east, r_south = unpk(poly.rivers)
local c_NW, c_NE, c_SE, c_SW = unpack(poly.river_corners) local c_NW, c_NE, c_SE, c_SW = unpk(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:
@ -64,10 +68,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 = math.max(r_west, c_NW-zf, zf-c_SW) local x0 = max(r_west, c_NW-zf, zf-c_SW)
local x1 = math.min(r_east, c_NE+zf, c_SE-zf) local x1 = min(r_east, c_NE+zf, c_SE-zf)
local z0 = math.max(r_north, c_NW-xf, xf-c_NE) local z0 = max(r_north, c_NW-xf, xf-c_NE)
local z1 = math.min(r_south, c_SW+xf, c_SE-xf) local z1 = 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
@ -90,7 +94,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 = math.floor(0.5+interp( local terrain_height = floor(0.5+interp(
vdem[1], vdem[1],
vdem[2], vdem[2],
vdem[3], vdem[3],
@ -115,17 +119,17 @@ local function heightmaps(minp, maxp)
lake_id = 1 lake_id = 1
end end
end end
local lake_height = math.max(math.floor(poly.lake[lake_id]), terrain_height) local lake_height = max(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 = math.min(math.max(lake_height, sea_level) - math.floor(1+depth_factor_max*riverbed_slope), terrain_height) terrain_height = min(max(lake_height, sea_level) - 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] = MAP_BOTTOM terrain_height_map[i] = out_elev
lake_height_map[i] = MAP_BOTTOM lake_height_map[i] = out_elev
end end
i = i + 1 i = i + 1
end end

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)
minetest.log("warning", "[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
@ -27,6 +32,9 @@ 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
@ -43,7 +51,7 @@ local sumtime2 = 0
local ngen = 0 local ngen = 0
local function generate(minp, maxp, seed) local function generate(minp, maxp, seed)
print(("[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)))
local chulens = { local chulens = {
x = maxp.x-minp.x+1, x = maxp.x-minp.x+1,
@ -106,8 +114,8 @@ local function generate(minp, maxp, seed)
end end
end end
local pminp = {x=math.floor(xmin), z=math.floor(zmin)} local pminp = {x=floor(xmin), z=floor(zmin)}
local pmaxp = {x=math.floor(xmax)+1, z=math.floor(zmax)+1} local pmaxp = {x=floor(xmax)+1, z=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)
@ -115,6 +123,30 @@ 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")
@ -140,7 +172,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, minp.y, z) local ivm = a:index(x, maxp.y+1, z)
local ground_above = false local ground_above = false
local temperature local temperature
if use_biomes then if use_biomes then
@ -156,8 +188,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 = math.floor(xn) local x0 = floor(xn)
local z0 = math.floor(zn) local z0 = floor(zn)
local i0 = i_origin + z0*incr + x0 local i0 = i_origin + z0*incr + x0
local i1 = i0+1 local i1 = i0+1
@ -165,13 +197,12 @@ 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 = math.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 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
@ -200,7 +231,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
@ -228,18 +259,17 @@ 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 = t1-t0 local t = os.clock()-t0
ngen = ngen + 1 ngen = ngen + 1
sumtime = sumtime + t sumtime = sumtime + t
sumtime2 = sumtime2 + t*t sumtime2 = sumtime2 + t*t
print(("[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(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)
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)) 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) end)

View File

@ -1,12 +1,15 @@
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 = {}
@ -35,8 +38,6 @@ 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
@ -75,9 +76,6 @@ 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
@ -85,15 +83,15 @@ function mapgen_rivers.write_map(filename, data, bytes)
end end
for i=1, size do for i=1, size do
local n = mfloor(data[i]) local n = floor(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 = mfloor(n / 256) n = floor(n / 256)
end end
bytelist[1] = n % 256 bytelist[1] = n % 256
file:write(schar(upack(bytelist))) file:write(schar(unpk(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
print("[mapgen_rivers] Noise " .. name .. ": 'octaves' reduced to " .. omax) minetest.log("warning", "[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()
print('[mapgen_rivers] Generating grid') minetest.log("action", '[mapgen_rivers] Generating grid, this may take a while...')
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
print('[mapgen_rivers] Loading full grid') minetest.log("action", '[mapgen_rivers] Loading full grid')
else else
print('[mapgen_rivers] Loading grid as interactive loaders') minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders')
end end
local grid = mapgen_rivers.grid local grid = mapgen_rivers.grid
@ -90,16 +90,19 @@ 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 = math.abs(flow) flow = abs(flow)
if flow < min_catchment then if flow < min_catchment then
return 0 return 0
end end
return math.min(wfactor * flow ^ wpower, 1) return 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
@ -138,8 +141,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 = 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 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 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) 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)
-- Iterate over the polygons -- Iterate over the polygons
for xp = xpmin, xpmax do for xp = xpmin, xpmax do
@ -165,8 +168,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 = math.max(math.floor(math.min(unpack(poly_z)))+1, minp.z) local zmin = max(floor(min(unpack(poly_z)))+1, minp.z)
local zmax = math.min(math.floor(math.max(unpack(poly_z))), maxp.z) local zmax = min(floor(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] = {}
@ -176,14 +179,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 = math.floor(math.min(z1, z2))+1 local lzmin = floor(min(z1, z2))+1
local lzmax = math.floor(math.max(z1, z2)) local lzmax = floor(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=math.max(lzmin, minp.z), math.min(lzmax, maxp.z) do for z=max(lzmin, minp.z), 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
@ -194,11 +197,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 = math.floor(#zlist/2) local c = 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 = math.max(math.floor(zlist[l*2-1])+1, minp.x) local xmin = max(floor(zlist[l*2-1])+1, minp.x)
local xmax = math.min(math.floor(zlist[l*2]), maxp.x) local xmax = min(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
@ -220,16 +223,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 = math.min(riverA*glacier_factor, 1) riverA = 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 = math.min(riverB*glacier_factor, 1) riverB = 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 = math.min(riverC*glacier_factor, 1) riverC = 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 = math.min(riverD*glacier_factor, 1) riverD = min(riverD*glacier_factor, 1)
end end
end end

View File

@ -13,6 +13,38 @@ 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
@ -26,6 +58,10 @@ 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)
@ -33,7 +69,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
print("[mapgen_rivers] Iteration " .. i .. " of " .. niter) minetest.log("info", "[mapgen_rivers] Iteration " .. i .. " of " .. niter)
model:diffuse(time_step) model:diffuse(time_step)
model:flow() model:flow()
@ -41,6 +77,9 @@ 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" mapgen_rivers.version = "1.0.1"
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,6 +74,9 @@ 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,13 +17,23 @@ 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 a river needs to receive more tributaries to grow in width. # Higher value means that a river will grow more when receiving a tributary.
# 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