mirror of
https://gitlab.com/gaelysam/mapgen_rivers.git
synced 2025-07-14 22:40:27 +02:00
Compare commits
1 Commits
margin
...
distinguis
Author | SHA1 | Date | |
---|---|---|---|
b246cb775b |
@ -1,5 +1,5 @@
|
||||
# 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).
|
||||
|
||||
|
13
geometry.lua
13
geometry.lua
@ -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)
|
||||
-- get the distance between point (x,y) and segment (x1,y1)-(x2,y2)
|
||||
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
|
||||
if a + b < c then
|
||||
-- The closest point of the segment is the extremity 1
|
||||
return sqrt(b)
|
||||
return math.sqrt(b)
|
||||
elseif a + c < b then
|
||||
-- The closest point of the segment is the extremity 2
|
||||
return sqrt(c)
|
||||
return math.sqrt(c)
|
||||
else
|
||||
-- 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
|
||||
|
||||
@ -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)
|
||||
-- X, Y 4-vectors giving the coordinates of the 4 vertices
|
||||
-- x, y position to index.
|
||||
local x1, x2, x3, x4 = unpk(X)
|
||||
local y1, y2, y3, y4 = unpk(Y)
|
||||
local x1, x2, x3, x4 = unpack(X)
|
||||
local y1, y2, y3, y4 = unpack(Y)
|
||||
|
||||
-- Compare distance to 2 opposite edges, they give the X coordinate
|
||||
local d23 = distance_to_segment(x2,y2,x3,y3,x,y)
|
||||
|
@ -6,11 +6,7 @@ local transform_quadri = dofile(modpath .. 'geometry.lua')
|
||||
local sea_level = mapgen_rivers.settings.sea_level
|
||||
local riverbed_slope = mapgen_rivers.settings.riverbed_slope * mapgen_rivers.settings.blocksize
|
||||
|
||||
local out_elev = mapgen_rivers.settings.margin_elev
|
||||
|
||||
-- Localize for performance
|
||||
local floor, min, max = math.floor, math.min, math.max
|
||||
local unpk = unpack
|
||||
local MAP_BOTTOM = -31000
|
||||
|
||||
-- Linear interpolation
|
||||
local function interp(v00, v01, v11, v10, xf, zf)
|
||||
@ -34,11 +30,11 @@ local function heightmaps(minp, maxp)
|
||||
|
||||
if poly then
|
||||
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
|
||||
local r_west, r_north, r_east, r_south = unpk(poly.rivers)
|
||||
local c_NW, c_NE, c_SE, c_SW = unpk(poly.river_corners)
|
||||
local r_west, r_north, r_east, r_south = unpack(poly.rivers)
|
||||
local c_NW, c_NE, c_SE, c_SW = unpack(poly.river_corners)
|
||||
|
||||
-- Calculate the depth factor for each edge and corner.
|
||||
-- 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)
|
||||
if imax == 0 then
|
||||
local x0 = max(r_west, c_NW-zf, zf-c_SW)
|
||||
local x1 = min(r_east, c_NE+zf, c_SE-zf)
|
||||
local z0 = max(r_north, c_NW-xf, xf-c_NE)
|
||||
local z1 = min(r_south, c_SW+xf, c_SE-xf)
|
||||
local x0 = math.max(r_west, c_NW-zf, zf-c_SW)
|
||||
local x1 = math.min(r_east, c_NE+zf, c_SE-zf)
|
||||
local z0 = math.max(r_north, c_NW-xf, xf-c_NE)
|
||||
local z1 = math.min(r_south, c_SW+xf, c_SE-xf)
|
||||
xf = (xf-x0) / (x1-x0)
|
||||
zf = (zf-z0) / (z1-z0)
|
||||
elseif imax == 1 then
|
||||
@ -94,7 +90,7 @@ local function heightmaps(minp, maxp)
|
||||
|
||||
-- Determine elevation by interpolation
|
||||
local vdem = poly.dem
|
||||
local terrain_height = floor(0.5+interp(
|
||||
local terrain_height = math.floor(0.5+interp(
|
||||
vdem[1],
|
||||
vdem[2],
|
||||
vdem[3],
|
||||
@ -119,17 +115,17 @@ local function heightmaps(minp, maxp)
|
||||
lake_id = 1
|
||||
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
|
||||
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
|
||||
|
||||
terrain_height_map[i] = terrain_height
|
||||
lake_height_map[i] = lake_height
|
||||
else
|
||||
terrain_height_map[i] = out_elev
|
||||
lake_height_map[i] = out_elev
|
||||
terrain_height_map[i] = MAP_BOTTOM
|
||||
lake_height_map[i] = MAP_BOTTOM
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
53
init.lua
53
init.lua
@ -6,7 +6,7 @@ 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")
|
||||
print("[mapgen_rivers] Mapgen set to singlenode")
|
||||
end
|
||||
|
||||
dofile(modpath .. 'settings.lua')
|
||||
@ -32,9 +32,6 @@ local function interp(v00, v01, v11, v10, xf, zf)
|
||||
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
|
||||
@ -51,7 +48,7 @@ local sumtime2 = 0
|
||||
local ngen = 0
|
||||
|
||||
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 = {
|
||||
x = maxp.x-minp.x+1,
|
||||
@ -114,8 +111,8 @@ local function generate(minp, maxp, seed)
|
||||
end
|
||||
end
|
||||
|
||||
local pminp = {x=floor(xmin), z=floor(zmin)}
|
||||
local pmaxp = {x=floor(xmax)+1, z=floor(zmax)+1}
|
||||
local pminp = {x=math.floor(xmin), z=math.floor(zmin)}
|
||||
local pmaxp = {x=math.floor(xmax)+1, z=math.floor(zmax)+1}
|
||||
incr = pmaxp.x-pminp.x+1
|
||||
i_origin = 1 - pminp.z*incr - pminp.x
|
||||
terrain_map, lake_map = heightmaps(pminp, pmaxp)
|
||||
@ -123,30 +120,6 @@ local function generate(minp, maxp, seed)
|
||||
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("default:stone")
|
||||
local c_dirt = minetest.get_content_id("default:dirt")
|
||||
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 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 temperature
|
||||
if use_biomes then
|
||||
@ -188,8 +161,8 @@ local function generate(minp, maxp, seed)
|
||||
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 x0 = math.floor(xn)
|
||||
local z0 = math.floor(zn)
|
||||
|
||||
local i0 = i_origin + z0*incr + x0
|
||||
local i1 = i0+1
|
||||
@ -197,12 +170,13 @@ local function generate(minp, maxp, seed)
|
||||
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])
|
||||
lake = math.min(lake_map[i0], lake_map[i1], lake_map[i2], lake_map[i3])
|
||||
end
|
||||
|
||||
if y <= maxp.y then
|
||||
|
||||
local is_lake = lake > terrain
|
||||
local ivm = a:index(x, y, z)
|
||||
if y <= terrain then
|
||||
if not use_biomes or y <= terrain-1 or ground_above then
|
||||
data[ivm] = c_stone
|
||||
@ -231,7 +205,7 @@ local function generate(minp, maxp, seed)
|
||||
|
||||
ground_above = y <= terrain
|
||||
|
||||
ivm = ivm - ystride
|
||||
ivm = ivm + ystride
|
||||
if use_distort then
|
||||
nid = nid + incrY
|
||||
end
|
||||
@ -259,17 +233,18 @@ local function generate(minp, maxp, seed)
|
||||
vm:calc_lighting()
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
local t1 = os.clock()
|
||||
|
||||
local t = os.clock()-t0
|
||||
local t = t1-t0
|
||||
ngen = ngen + 1
|
||||
sumtime = sumtime + 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
|
||||
|
||||
minetest.register_on_generated(generate)
|
||||
minetest.register_on_shutdown(function()
|
||||
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))
|
||||
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)
|
||||
|
16
load.lua
16
load.lua
@ -1,15 +1,12 @@
|
||||
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)
|
||||
local file = io.open(worldpath .. filename, 'rb')
|
||||
local data = file:read('*all')
|
||||
if #data < bytes*size then
|
||||
data = minetest.decompress(data)
|
||||
end
|
||||
local sbyte = string.byte
|
||||
|
||||
local map = {}
|
||||
|
||||
@ -38,6 +35,8 @@ function mapgen_rivers.load_map(filename, bytes, signed, size, converter)
|
||||
return map
|
||||
end
|
||||
|
||||
local sbyte = string.byte
|
||||
|
||||
local loader_mt = {
|
||||
__index = function(loader, i)
|
||||
local file = loader.file
|
||||
@ -76,6 +75,9 @@ end
|
||||
function mapgen_rivers.write_map(filename, data, bytes)
|
||||
local size = #data
|
||||
local file = io.open(worldpath .. filename, 'wb')
|
||||
local mfloor = math.floor
|
||||
local schar = string.char
|
||||
local upack = unpack
|
||||
|
||||
local bytelist = {}
|
||||
for j=1, bytes do
|
||||
@ -83,15 +85,15 @@ function mapgen_rivers.write_map(filename, data, bytes)
|
||||
end
|
||||
|
||||
for i=1, size do
|
||||
local n = floor(data[i])
|
||||
local n = mfloor(data[i])
|
||||
data[i] = n
|
||||
for j=bytes, 2, -1 do
|
||||
bytelist[j] = n % 256
|
||||
n = floor(n / 256)
|
||||
n = mfloor(n / 256)
|
||||
end
|
||||
bytelist[1] = n % 256
|
||||
|
||||
file:write(schar(unpk(bytelist)))
|
||||
file:write(schar(upack(bytelist)))
|
||||
end
|
||||
|
||||
file:close()
|
||||
|
@ -73,7 +73,7 @@ for name, np in pairs(mapgen_rivers.noise_params) do
|
||||
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
|
||||
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
|
||||
end
|
||||
end
|
||||
|
41
polygons.lua
41
polygons.lua
@ -33,7 +33,7 @@ if first_mapgen then
|
||||
-- Generate a map!!
|
||||
local pregenerate = dofile(mapgen_rivers.modpath .. '/pregenerate.lua')
|
||||
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)
|
||||
|
||||
if load_all then
|
||||
@ -58,9 +58,9 @@ if not (first_mapgen and load_all) then
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
if load_all then
|
||||
minetest.log("action", '[mapgen_rivers] Loading full grid')
|
||||
print('[mapgen_rivers] Loading full grid')
|
||||
else
|
||||
minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders')
|
||||
print('[mapgen_rivers] Loading grid as interactive loaders')
|
||||
end
|
||||
local grid = mapgen_rivers.grid
|
||||
|
||||
@ -90,19 +90,16 @@ if mapgen_rivers.settings.center then
|
||||
map_offset.z = blocksize*Z/2
|
||||
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 wpower = mapgen_rivers.settings.river_widening_power
|
||||
local wfactor = 1/(2*blocksize * min_catchment^wpower)
|
||||
local function river_width(flow)
|
||||
flow = abs(flow)
|
||||
flow = math.abs(flow)
|
||||
if flow < min_catchment then
|
||||
return 0
|
||||
end
|
||||
|
||||
return min(wfactor * flow ^ wpower, 1)
|
||||
return math.min(wfactor * flow ^ wpower, 1)
|
||||
end
|
||||
|
||||
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 = {}
|
||||
-- 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 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 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 = 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
|
||||
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)
|
||||
-- Calculate the min and max Z positions
|
||||
local zmin = max(floor(min(unpack(poly_z)))+1, minp.z)
|
||||
local zmax = min(floor(max(unpack(poly_z))), maxp.z)
|
||||
local zmin = math.max(math.floor(math.min(unpack(poly_z)))+1, minp.z)
|
||||
local zmax = math.min(math.floor(math.max(unpack(poly_z))), maxp.z)
|
||||
-- And initialize the arrays
|
||||
for z=zmin, zmax do
|
||||
bounds[z] = {}
|
||||
@ -179,14 +176,14 @@ local function make_polygons(minp, maxp)
|
||||
for i2=1, 4 do -- Loop on 4 edges
|
||||
local z1, z2 = poly_z[i1], poly_z[i2]
|
||||
-- Calculate the integer Z positions over which this edge spans
|
||||
local lzmin = floor(min(z1, z2))+1
|
||||
local lzmax = floor(max(z1, z2))
|
||||
local lzmin = math.floor(math.min(z1, z2))+1
|
||||
local lzmax = math.floor(math.max(z1, z2))
|
||||
if lzmin <= lzmax then -- If there is at least one position in it
|
||||
local x1, x2 = poly_x[i1], poly_x[i2]
|
||||
-- Calculate coefficient of the equation defining the edge: X=aZ+b
|
||||
local a = (x1-x2) / (z1-z2)
|
||||
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
|
||||
table.insert(bounds[z], a*z+b)
|
||||
end
|
||||
@ -197,11 +194,11 @@ local function make_polygons(minp, maxp)
|
||||
-- Now sort the bounds list
|
||||
local zlist = bounds[z]
|
||||
table.sort(zlist)
|
||||
local c = floor(#zlist/2)
|
||||
local c = math.floor(#zlist/2)
|
||||
for l=1, c do
|
||||
-- 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 xmax = min(floor(zlist[l*2]), maxp.x)
|
||||
local xmin = math.max(math.floor(zlist[l*2-1])+1, minp.x)
|
||||
local xmax = math.min(math.floor(zlist[l*2]), maxp.x)
|
||||
local i = (z-minp.z) * chulens + (xmin-minp.x) + 1
|
||||
for x=xmin, xmax do
|
||||
-- Fill the map at these places
|
||||
@ -223,16 +220,16 @@ local function make_polygons(minp, maxp)
|
||||
local riverD = river_width(rivers[iD])
|
||||
if glaciers then -- Widen the river
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -13,38 +13,6 @@ local time_step = mapgen_rivers.settings.evol_time_step
|
||||
local niter = math.ceil(time/time_step)
|
||||
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 grid = mapgen_rivers.grid
|
||||
local size = grid.size
|
||||
@ -58,10 +26,6 @@ local function pregenerate(keep_loaded)
|
||||
dem.X = size.x
|
||||
dem.Y = size.y
|
||||
|
||||
if use_margin then
|
||||
margin(dem, margin_width, margin_elev)
|
||||
end
|
||||
|
||||
local model = EvolutionModel(evol_params)
|
||||
model.dem = dem
|
||||
local ref_dem = model:define_isostasy(dem)
|
||||
@ -69,7 +33,7 @@ local function pregenerate(keep_loaded)
|
||||
local tectonic_step = tectonic_speed * time_step
|
||||
collectgarbage()
|
||||
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:flow()
|
||||
@ -77,9 +41,6 @@ local function pregenerate(keep_loaded)
|
||||
if i < niter then
|
||||
if tectonic_step ~= 0 then
|
||||
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
|
||||
model:isostasy()
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
local mtsettings = minetest.settings
|
||||
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_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_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 = {
|
||||
K = def_setting('river_erosion_coef', 'number', 0.5),
|
||||
m = def_setting('river_erosion_power', 'number', 0.4),
|
||||
|
@ -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
|
||||
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
|
||||
# Lower value means bigger river density
|
||||
mapgen_rivers_min_catchment (Minimal catchment area) float 3600.0 100.0 1000000.0
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
mapgen_rivers_river_widening_power (River widening power) float 0.5 0.0 1.0
|
||||
|
||||
|
29
view.py
29
view.py
@ -11,10 +11,12 @@ try:
|
||||
import colorcet as cc
|
||||
cmap1 = cc.cm.CET_L11
|
||||
cmap2 = cc.cm.CET_L12
|
||||
cmap3 = cc.cm.CET_L6.reversed()
|
||||
except ImportError: # No module colorcet
|
||||
import matplotlib.cm as cm
|
||||
cmap1 = cm.summer
|
||||
cmap2 = cm.Blues
|
||||
cmap2 = cm.ocean.reversed()
|
||||
cmap3 = cm.Blues
|
||||
except ImportError: # No module matplotlib
|
||||
has_matplotlib = False
|
||||
|
||||
@ -24,10 +26,12 @@ if has_matplotlib:
|
||||
water = np.maximum(lakes_sea - dem, 0)
|
||||
max_elev = dem.max()
|
||||
max_depth = water.max()
|
||||
max_lake_depth = lakes.max()
|
||||
|
||||
ls = mcl.LightSource(azdeg=315, altdeg=45)
|
||||
norm_ground = plt.Normalize(vmin=sea_level, vmax=max_elev)
|
||||
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)
|
||||
|
||||
(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)
|
||||
plt.imshow(np.flipud(rgb), extent=extent, interpolation='antialiased')
|
||||
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)
|
||||
plt.colorbar(sm1).set_label('Elevation')
|
||||
|
||||
sm2 = plt.cm.ScalarMappable(cmap=cmap2, norm=norm_sea)
|
||||
plt.colorbar(sm2).set_label('Water depth')
|
||||
sm2 = plt.cm.ScalarMappable(cmap=cmap2, norm=norm_lake)
|
||||
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.ylabel('Z')
|
||||
@ -84,9 +98,10 @@ def stats(dem, lakes, scale=1):
|
||||
lake_surface = lake.sum()
|
||||
|
||||
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:
|
||||
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('--- Surfaces ---')
|
||||
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('Lowest elevation: {:4.0f}'.format(dem.min()))
|
||||
print('Highest elevation: {:4.0f}'.format(dem.max()))
|
||||
print()
|
||||
|
||||
|
Reference in New Issue
Block a user