mirror of
https://gitlab.com/gaelysam/mapgen_rivers.git
synced 2025-07-04 09:20:41 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
b1f4437a91 | |||
a00c1cbd39 | |||
417ce1bcbc | |||
c3a798933f | |||
0c98fc0881 | |||
cb71f4400a | |||
f8f467ac3f | |||
2e29474686 | |||
27670addb3 |
@ -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).
|
||||||
|
|
||||||
|
13
geometry.lua
13
geometry.lua
@ -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)
|
||||||
|
@ -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
|
||||||
|
56
init.lua
56
init.lua
@ -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)
|
||||||
|
16
load.lua
16
load.lua
@ -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()
|
||||||
|
@ -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
|
||||||
|
41
polygons.lua
41
polygons.lua
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user