mirror of
https://gitlab.com/gaelysam/mapgen_rivers.git
synced 2025-07-03 08:50:40 +02:00
Compare commits
17 Commits
refactor_s
...
dev
Author | SHA1 | Date | |
---|---|---|---|
72e2f3e670 | |||
f350f8785c | |||
4bce5fab77 | |||
b54f2c4546 | |||
c723b28ec6 | |||
fe6e281130 | |||
2acefb2660 | |||
0bc100030c | |||
d00295600d | |||
0983c27cca | |||
6564d40b85 | |||
cd2a77803f | |||
b0930f4d40 | |||
975ad02739 | |||
6d8ee5af1f | |||
fabe107336 | |||
7e155b7076 |
@ -1,3 +1,6 @@
|
|||||||
|
-- Fix compatibility for settings-related changes
|
||||||
|
-- Only loaded if the versions of the mod and the world mismatch
|
||||||
|
|
||||||
local function version_is_lower(v1, v2)
|
local function version_is_lower(v1, v2)
|
||||||
local d1, c1, d2, c2
|
local d1, c1, d2, c2
|
||||||
while #v1 > 0 and #v2 > 0 do
|
while #v1 > 0 and #v2 > 0 do
|
||||||
|
40
geometry.lua
40
geometry.lua
@ -1,40 +0,0 @@
|
|||||||
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
|
|
||||||
local b = (x1-x)^2 + (y1-y)^2
|
|
||||||
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)
|
|
||||||
elseif a + c < b then
|
|
||||||
-- The closest point of the segment is the extremity 2
|
|
||||||
return sqrt(c)
|
|
||||||
else
|
|
||||||
-- The closest point is on the segment
|
|
||||||
return abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / sqrt(a)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
-- Compare distance to 2 opposite edges, they give the X coordinate
|
|
||||||
local d23 = distance_to_segment(x2,y2,x3,y3,x,y)
|
|
||||||
local d41 = distance_to_segment(x4,y4,x1,y1,x,y)
|
|
||||||
local xc = d41 / (d23+d41)
|
|
||||||
|
|
||||||
-- Same for the 2 other edges, they give the Y coordinate
|
|
||||||
local d12 = distance_to_segment(x1,y1,x2,y2,x,y)
|
|
||||||
local d34 = distance_to_segment(x3,y3,x4,y4,x,y)
|
|
||||||
local yc = d12 / (d12+d34)
|
|
||||||
return xc, yc
|
|
||||||
end
|
|
||||||
|
|
||||||
return transform_quadri
|
|
@ -1,10 +1,13 @@
|
|||||||
|
-- Input and output functions for grid maps
|
||||||
|
|
||||||
local worldpath = mapgen_rivers.world_data_path
|
local worldpath = mapgen_rivers.world_data_path
|
||||||
|
|
||||||
local floor = math.floor
|
local floor = math.floor
|
||||||
local sbyte, schar = string.byte, string.char
|
local sbyte, schar = string.byte, string.char
|
||||||
local unpk = unpack
|
local unpk = unpack
|
||||||
|
|
||||||
function mapgen_rivers.load_map(filename, bytes, signed, size, converter)
|
-- Loading files
|
||||||
|
local function load_full_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
|
||||||
@ -62,18 +65,31 @@ local loader_mt = {
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapgen_rivers.interactive_loader(filename, bytes, signed, size, converter)
|
local function interactive_loader(filename, bytes, signed, size, converter)
|
||||||
local file = io.open(worldpath .. filename, 'rb')
|
local file = io.open(worldpath .. filename, 'rb')
|
||||||
if file then
|
if file then
|
||||||
minetest.register_on_shutdown(function()
|
|
||||||
file:close()
|
|
||||||
end)
|
|
||||||
converter = converter or false
|
converter = converter or false
|
||||||
return setmetatable({file=file, bytes=bytes, signed=signed, size=size, conv=converter}, loader_mt)
|
return setmetatable({file=file, bytes=bytes, signed=signed, size=size, conv=converter}, loader_mt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function mapgen_rivers.write_map(filename, data, bytes)
|
local load_methods = {
|
||||||
|
full = load_full_map,
|
||||||
|
interactive = interactive_loader,
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapgen_rivers.load_file(...)
|
||||||
|
local load_method = mapgen_rivers.settings.load_method
|
||||||
|
local load_func = load_methods[load_method]
|
||||||
|
if load_func then
|
||||||
|
return load_func(...)
|
||||||
|
else
|
||||||
|
minetest.log("error", ("[mapgen_rivers] Unknown load method %s"):format(load_method))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Writing files
|
||||||
|
function mapgen_rivers.write_file(filename, data, bytes)
|
||||||
local size = #data
|
local size = #data
|
||||||
local file = io.open(worldpath .. filename, 'wb')
|
local file = io.open(worldpath .. filename, 'wb')
|
||||||
|
|
148
gridmanager.lua
Normal file
148
gridmanager.lua
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
-- Manages grid loading, writing and generation
|
||||||
|
|
||||||
|
local world_data = mapgen_rivers.world_data_path
|
||||||
|
|
||||||
|
local registered_on_grid_loaded = {}
|
||||||
|
function mapgen_rivers.register_on_grid_loaded(func)
|
||||||
|
if type(func) == "function" then
|
||||||
|
registered_on_grid_loaded[#registered_on_grid_loaded+1] = func
|
||||||
|
else
|
||||||
|
minetest.log("error", "[mapgen_rivers] register_on_grid_loaded can only register functions!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_grid_loaded_callback(grid)
|
||||||
|
for _, func in ipairs(registered_on_grid_loaded) do
|
||||||
|
func(grid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function offset_conv(o)
|
||||||
|
return (o + 0.5) * (1/256)
|
||||||
|
end
|
||||||
|
|
||||||
|
local grid_maps_list = {
|
||||||
|
dem = {bytes=2, signed=true},
|
||||||
|
lakes = {bytes=2, signed=true},
|
||||||
|
dirs = {bytes=1, signed=false},
|
||||||
|
rivers = {bytes=4, signed=false},
|
||||||
|
|
||||||
|
offset_x = {bytes=1, signed=true, conv=offset_conv},
|
||||||
|
offset_y = {bytes=1, signed=true, conv=offset_conv},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function apply_grid_conversion(grid)
|
||||||
|
if grid.load_method ~= "full" then
|
||||||
|
minetest.log("warning", ("Could not apply data conversion for load method %s"):format(grid.load_method))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if grid.conv_applied then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local size = grid.size.x * grid.size.y
|
||||||
|
for mapname, params in pairs(grid_maps_list) do
|
||||||
|
local conv = params.conv
|
||||||
|
if conv then
|
||||||
|
local map = grid[mapname]
|
||||||
|
for i=1, size do
|
||||||
|
map[i] = conv(map[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
grid.conv_applied = true
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function mapgen_rivers.try_load_grid(grid)
|
||||||
|
local load_method = mapgen_rivers.settings.load_method
|
||||||
|
|
||||||
|
-- First, check whether a grid is already loaded with the appropriate method
|
||||||
|
if mapgen_rivers.grid and mapgen_rivers.grid.load_method == load_method then
|
||||||
|
if not mapgen_rivers.grid.conv_applied then
|
||||||
|
apply_grid_conversion(mapgen_rivers.grid)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
-- Then, check the provided argument is a valid grid
|
||||||
|
elseif grid and grid.load_method == load_method then
|
||||||
|
if not mapgen_rivers.grid.conv_applied then
|
||||||
|
apply_grid_conversion(grid)
|
||||||
|
end
|
||||||
|
mapgen_rivers.grid = grid
|
||||||
|
on_grid_loaded_callback(grid)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fall back to loading the grid from the files
|
||||||
|
local sfile = io.open(world_data .. 'size', 'r')
|
||||||
|
if not sfile then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, z = sfile:read('*n'), sfile:read('*n')
|
||||||
|
if not x or not z then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if load_method == "full" then
|
||||||
|
minetest.log("action", '[mapgen_rivers] Loading full grid')
|
||||||
|
elseif load_method == "interactive" then
|
||||||
|
minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders')
|
||||||
|
end
|
||||||
|
|
||||||
|
grid = {
|
||||||
|
load_method = load_method,
|
||||||
|
size = {x=x, y=z},
|
||||||
|
}
|
||||||
|
|
||||||
|
for map, params in pairs(grid_maps_list) do
|
||||||
|
grid[map] = mapgen_rivers.load_file(map, params.bytes, params.signed, x*z, params.conv)
|
||||||
|
end
|
||||||
|
grid.conv_applied = true
|
||||||
|
|
||||||
|
mapgen_rivers.grid = grid
|
||||||
|
on_grid_loaded_callback(grid)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function mapgen_rivers.generate_grid()
|
||||||
|
minetest.log("action", '[mapgen_rivers] Generating grid, this may take a while...')
|
||||||
|
local grid = {}
|
||||||
|
|
||||||
|
local blocksize = mapgen_rivers.settings.blocksize
|
||||||
|
local xsize = math.floor(mapgen_rivers.settings.map_x_size / blocksize)
|
||||||
|
local zsize = math.floor(mapgen_rivers.settings.map_z_size / blocksize)
|
||||||
|
grid.size = {x=xsize, y=zsize}
|
||||||
|
grid.conv_applied = false
|
||||||
|
|
||||||
|
if not mapgen_rivers.pregenerate then
|
||||||
|
minetest.log("error", "[mapgen_rivers] Pre-generation function is not available.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
mapgen_rivers.pregenerate(grid)
|
||||||
|
|
||||||
|
return grid
|
||||||
|
end
|
||||||
|
|
||||||
|
function mapgen_rivers.write_grid(grid)
|
||||||
|
minetest.mkdir(world_data)
|
||||||
|
|
||||||
|
if grid.conv_applied then
|
||||||
|
minetest.log("error", '[mapgen_rivers] Could not write grid if data conversion is already done')
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for map, params in pairs(grid_maps_list) do
|
||||||
|
mapgen_rivers.write_file(map, grid[map], params.bytes)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sfile = io.open(world_data .. 'size', "w")
|
||||||
|
sfile:write(grid.size.x..'\n'..grid.size.y)
|
||||||
|
sfile:close()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
145
heightmap.lua
145
heightmap.lua
@ -1,17 +1,53 @@
|
|||||||
local modpath = mapgen_rivers.modpath
|
-- Transform polygon data into a heightmap
|
||||||
|
|
||||||
local make_polygons = dofile(modpath .. 'polygons.lua')
|
local modpath = mapgen_rivers.modpath
|
||||||
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
|
-- Localize for performance
|
||||||
local floor, min, max = math.floor, math.min, math.max
|
local floor, min, max, sqrt, abs = math.floor, math.min, math.max, math.sqrt, math.abs
|
||||||
local unpk = unpack
|
local unpk = unpack
|
||||||
|
|
||||||
|
-- Geometrical helpers
|
||||||
|
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
|
||||||
|
local b = (x1-x)^2 + (y1-y)^2
|
||||||
|
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)
|
||||||
|
elseif a + c < b then
|
||||||
|
-- The closest point of the segment is the extremity 2
|
||||||
|
return sqrt(c)
|
||||||
|
else
|
||||||
|
-- The closest point is on the segment
|
||||||
|
return abs(x1 * (y2-y) + x2 * (y-y1) + x * (y1-y2)) / sqrt(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- Compare distance to 2 opposite edges, they give the X coordinate
|
||||||
|
local d23 = distance_to_segment(x2,y2,x3,y3,x,y)
|
||||||
|
local d41 = distance_to_segment(x4,y4,x1,y1,x,y)
|
||||||
|
local xc = d41 / (d23+d41)
|
||||||
|
|
||||||
|
-- Same for the 2 other edges, they give the Y coordinate
|
||||||
|
local d12 = distance_to_segment(x1,y1,x2,y2,x,y)
|
||||||
|
local d34 = distance_to_segment(x3,y3,x4,y4,x,y)
|
||||||
|
local yc = d12 / (d12+d34)
|
||||||
|
return xc, yc
|
||||||
|
end
|
||||||
|
|
||||||
-- Linear interpolation
|
-- Linear interpolation
|
||||||
local function interp(v00, v01, v11, v10, xf, zf)
|
local function interp(v00, v01, v11, v10, xf, zf)
|
||||||
local v0 = v01*xf + v00*(1-xf)
|
local v0 = v01*xf + v00*(1-xf)
|
||||||
@ -19,9 +55,9 @@ local function interp(v00, v01, v11, v10, xf, zf)
|
|||||||
return v1*zf + v0*(1-zf)
|
return v1*zf + v0*(1-zf)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function heightmaps(minp, maxp)
|
function mapgen_rivers.make_heightmaps(minp, maxp)
|
||||||
|
|
||||||
local polygons = make_polygons(minp, maxp)
|
local polygons = mapgen_rivers.make_polygons(minp, maxp)
|
||||||
local incr = maxp.z-minp.z+1
|
local incr = maxp.z-minp.z+1
|
||||||
|
|
||||||
local terrain_height_map = {}
|
local terrain_height_map = {}
|
||||||
@ -46,50 +82,57 @@ local function heightmaps(minp, maxp)
|
|||||||
-- = 0: on riverbank
|
-- = 0: on riverbank
|
||||||
-- > 0: inside river
|
-- > 0: inside river
|
||||||
local depth_factors = {
|
local depth_factors = {
|
||||||
r_west - xf,
|
r_west - xf , -- West edge (1)
|
||||||
r_north - zf,
|
r_north - zf , -- North edge (2)
|
||||||
xf - r_east,
|
r_east - (1-xf), -- East edge (3)
|
||||||
zf - r_south,
|
r_south - (1-zf), -- South edge (4)
|
||||||
c_NW-xf-zf,
|
c_NW - xf - zf , -- North-West corner (5)
|
||||||
xf-zf-c_NE,
|
c_NE - (1-xf) - zf , -- North-East corner (6)
|
||||||
xf+zf-c_SE,
|
c_SE - (1-xf) - (1-zf), -- South-East corner (7)
|
||||||
zf-xf-c_SW,
|
c_SW - xf - (1-zf), -- South-West corner (8)
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Find the maximal depth factor and determine to which river it belongs
|
-- Find the maximal depth factor, which determines to which of the 8 river sections (4 edges + 4 corners) the current point belongs.
|
||||||
local depth_factor_max = 0
|
-- If imax is still at 0, it means that we are not in a river.
|
||||||
|
local dpmax = 0
|
||||||
local imax = 0
|
local imax = 0
|
||||||
for i=1, 8 do
|
for i=1, 8 do
|
||||||
if depth_factors[i] >= depth_factor_max then
|
if depth_factors[i] > dpmax then
|
||||||
depth_factor_max = depth_factors[i]
|
dpmax = depth_factors[i]
|
||||||
imax = i
|
imax = i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 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 xfc and zfc = 0 or 1 in rivers (to avoid rivers having lateral slope and to accomodate the riverbanks smoothly)
|
||||||
if imax == 0 then
|
local xfc, zfc
|
||||||
local x0 = max(r_west, c_NW-zf, zf-c_SW)
|
-- xfc:
|
||||||
local x1 = min(r_east, c_NE+zf, c_SE-zf)
|
if imax == 0 or imax == 2 or imax == 4 then -- river segment does not constrain X coordinate, so accomodate xf in function of other river sections
|
||||||
local z0 = max(r_north, c_NW-xf, xf-c_NE)
|
local x0 = max(r_west-dpmax, c_NW-zf-dpmax, c_SW-(1-zf)-dpmax, 0) -- new xf will be bounded to 0 by western riverbank
|
||||||
local z1 = min(r_south, c_SW+xf, c_SE-xf)
|
local x1 = 1-max(r_east-dpmax, c_NE-zf-dpmax, c_SE-(1-zf)-dpmax, 0) -- and bounded to 1 by eastern riverbank
|
||||||
xf = (xf-x0) / (x1-x0)
|
if x0 >= x1 then
|
||||||
zf = (zf-z0) / (z1-z0)
|
xfc = 0.5
|
||||||
elseif imax == 1 then
|
else
|
||||||
xf = 0
|
xfc = (xf-x0) / (x1-x0)
|
||||||
elseif imax == 2 then
|
end
|
||||||
zf = 0
|
elseif imax == 1 or imax == 5 or imax == 8 then -- river at the western side of the polygon
|
||||||
elseif imax == 3 then
|
xfc = 0
|
||||||
xf = 1
|
else -- 3, 6, 7 : river at the eastern side of the polygon
|
||||||
elseif imax == 4 then
|
xfc = 1
|
||||||
zf = 1
|
end
|
||||||
elseif imax == 5 then
|
|
||||||
xf, zf = 0, 0
|
-- Same for zfc:
|
||||||
elseif imax == 6 then
|
if imax == 0 or imax == 1 or imax == 3 then -- river segment does not constrain Z coordinate, so accomodate zf in function of other river sections
|
||||||
xf, zf = 1, 0
|
local z0 = max(r_north-dpmax, c_NW-xf-dpmax, c_NE-(1-xf)-dpmax, 0) -- new zf will be bounded to 0 by northern riverbank
|
||||||
elseif imax == 7 then
|
local z1 = 1-max(r_south-dpmax, c_SW-xf-dpmax, c_SE-(1-xf)-dpmax, 0) -- and bounded to 1 by southern riverbank
|
||||||
xf, zf = 1, 1
|
if z0 >= z1 then
|
||||||
elseif imax == 8 then
|
zfc = 0.5
|
||||||
xf, zf = 0, 1
|
else
|
||||||
|
zfc = (zf-z0) / (z1-z0)
|
||||||
|
end
|
||||||
|
elseif imax == 2 or imax == 5 or imax == 6 then -- river at the northern side of the polygon
|
||||||
|
zfc = 0
|
||||||
|
else -- 4, 7, 8 : river at the southern side of the polygon
|
||||||
|
zfc = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Determine elevation by interpolation
|
-- Determine elevation by interpolation
|
||||||
@ -99,12 +142,12 @@ local function heightmaps(minp, maxp)
|
|||||||
vdem[2],
|
vdem[2],
|
||||||
vdem[3],
|
vdem[3],
|
||||||
vdem[4],
|
vdem[4],
|
||||||
xf, zf
|
xfc, zfc
|
||||||
))
|
))
|
||||||
|
|
||||||
-- Spatial gradient of the interpolation
|
-- Spatial gradient of the interpolation
|
||||||
local slope_x = zf*(vdem[3]-vdem[4]) + (1-zf)*(vdem[2]-vdem[1]) < 0
|
local slope_x = zfc*(vdem[3]-vdem[4]) + (1-zfc)*(vdem[2]-vdem[1]) < 0
|
||||||
local slope_z = xf*(vdem[3]-vdem[2]) + (1-xf)*(vdem[4]-vdem[1]) < 0
|
local slope_z = xfc*(vdem[3]-vdem[2]) + (1-xfc)*(vdem[4]-vdem[1]) < 0
|
||||||
local lake_id = 0
|
local lake_id = 0
|
||||||
if slope_x then
|
if slope_x then
|
||||||
if slope_z then
|
if slope_z then
|
||||||
@ -121,15 +164,15 @@ local function heightmaps(minp, maxp)
|
|||||||
end
|
end
|
||||||
local lake_height = max(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 dpmax > 0 then
|
||||||
terrain_height = min(max(lake_height, sea_level) - floor(1+depth_factor_max*riverbed_slope), terrain_height)
|
terrain_height = min(max(lake_height, sea_level) - floor(1+dpmax*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
|
||||||
@ -137,5 +180,3 @@ local function heightmaps(minp, maxp)
|
|||||||
|
|
||||||
return terrain_height_map, lake_height_map
|
return terrain_height_map, lake_height_map
|
||||||
end
|
end
|
||||||
|
|
||||||
return heightmaps
|
|
||||||
|
280
init.lua
280
init.lua
@ -1,278 +1,26 @@
|
|||||||
|
-- Main file, calls the other files and triggers main functions
|
||||||
|
|
||||||
mapgen_rivers = {}
|
mapgen_rivers = {}
|
||||||
|
|
||||||
local modpath = minetest.get_modpath(minetest.get_current_modname()) .. '/'
|
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')
|
||||||
|
dofile(modpath .. 'gridmanager.lua')
|
||||||
|
dofile(modpath .. 'gridio.lua')
|
||||||
|
dofile(modpath .. 'polygons.lua')
|
||||||
|
dofile(modpath .. 'heightmap.lua')
|
||||||
|
dofile(modpath .. 'mapgen.lua')
|
||||||
|
|
||||||
local sea_level = mapgen_rivers.settings.sea_level
|
minetest.register_on_mods_loaded(function()
|
||||||
local elevation_chill = mapgen_rivers.settings.elevation_chill
|
local exist = mapgen_rivers.try_load_grid()
|
||||||
local use_distort = mapgen_rivers.settings.distort
|
|
||||||
local use_biomes = mapgen_rivers.settings.biomes
|
|
||||||
local use_biomegen_mod = use_biomes and minetest.global_exists('biomegen')
|
|
||||||
use_biomes = use_biomes and minetest.global_exists('default') and not use_biomegen_mod
|
|
||||||
|
|
||||||
if use_biomegen_mod then
|
if not exist then -- If grid does not exist yet, generate it
|
||||||
biomegen.set_elevation_chill(elevation_chill)
|
dofile(modpath .. 'pregenerate.lua')
|
||||||
end
|
|
||||||
|
|
||||||
local heightmaps = dofile(modpath .. 'heightmap.lua')
|
local grid = mapgen_rivers.generate_grid()
|
||||||
|
mapgen_rivers.write_grid(grid)
|
||||||
-- Linear interpolation
|
mapgen_rivers.try_load_grid(grid) -- Reload if needed
|
||||||
local function interp(v00, v01, v11, v10, xf, zf)
|
|
||||||
local v0 = v01*xf + v00*(1-xf)
|
|
||||||
local v1 = v11*xf + v10*(1-xf)
|
|
||||||
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
|
|
||||||
local noise_x_map = {}
|
|
||||||
local noise_z_map = {}
|
|
||||||
local noise_distort_map = {}
|
|
||||||
local noise_heat_map = {}
|
|
||||||
local noise_heat_blend_map = {}
|
|
||||||
local mapsize
|
|
||||||
local init = false
|
|
||||||
|
|
||||||
local sumtime = 0
|
|
||||||
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)))
|
|
||||||
|
|
||||||
local chulens = {
|
|
||||||
x = maxp.x-minp.x+1,
|
|
||||||
y = maxp.y-minp.y+1,
|
|
||||||
z = maxp.z-minp.z+1,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not init then
|
|
||||||
mapsize = {
|
|
||||||
x = chulens.x,
|
|
||||||
y = chulens.y+1,
|
|
||||||
z = chulens.z,
|
|
||||||
}
|
|
||||||
if use_distort then
|
|
||||||
noise_x_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_x, mapsize)
|
|
||||||
noise_z_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_z, mapsize)
|
|
||||||
noise_distort_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_amplitude, chulens)
|
|
||||||
end
|
|
||||||
if use_biomes then
|
|
||||||
noise_heat_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat, chulens)
|
|
||||||
noise_heat_blend_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat_blend, chulens)
|
|
||||||
end
|
|
||||||
init = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local t0 = os.clock()
|
|
||||||
local minp2d = {x=minp.x, y=minp.z}
|
|
||||||
if use_distort then
|
|
||||||
noise_x_obj:get_3d_map_flat(minp, noise_x_map)
|
|
||||||
noise_z_obj:get_3d_map_flat(minp, noise_z_map)
|
|
||||||
noise_distort_obj:get_2d_map_flat(minp2d, noise_distort_map)
|
|
||||||
end
|
|
||||||
if use_biomes then
|
|
||||||
noise_heat_obj:get_2d_map_flat(minp2d, noise_heat_map)
|
|
||||||
noise_heat_blend_obj:get_2d_map_flat(minp2d, noise_heat_blend_map)
|
|
||||||
end
|
|
||||||
|
|
||||||
local terrain_map, lake_map, incr, i_origin
|
|
||||||
|
|
||||||
if use_distort then
|
|
||||||
local xmin, xmax, zmin, zmax = minp.x, maxp.x, minp.z, maxp.z
|
|
||||||
local i = 0
|
|
||||||
local i2d = 0
|
|
||||||
for z=minp.z, maxp.z do
|
|
||||||
for y=minp.y, maxp.y+1 do
|
|
||||||
for x=minp.x, maxp.x do
|
|
||||||
i = i+1
|
|
||||||
i2d = i2d+1
|
|
||||||
local distort = noise_distort_map[i2d]
|
|
||||||
local xv = noise_x_map[i]*distort + x
|
|
||||||
if xv < xmin then xmin = xv end
|
|
||||||
if xv > xmax then xmax = xv end
|
|
||||||
noise_x_map[i] = xv
|
|
||||||
local zv = noise_z_map[i]*distort + z
|
|
||||||
if zv < zmin then zmin = zv end
|
|
||||||
if zv > zmax then zmax = zv end
|
|
||||||
noise_z_map[i] = zv
|
|
||||||
end
|
|
||||||
i2d = i2d-chulens.x
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local pminp = {x=floor(xmin), z=floor(zmin)}
|
|
||||||
local pmaxp = {x=floor(xmax)+1, z=floor(zmax)+1}
|
|
||||||
incr = pmaxp.x-pminp.x+1
|
|
||||||
i_origin = 1 - pminp.z*incr - pminp.x
|
|
||||||
terrain_map, lake_map = heightmaps(pminp, pmaxp)
|
|
||||||
else
|
|
||||||
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("mapgen_stone")
|
|
||||||
local c_water = minetest.get_content_id("mapgen_water_source")
|
|
||||||
local c_rwater = minetest.get_content_id("mapgen_river_water_source")
|
|
||||||
|
|
||||||
local c_dirt, c_lawn, c_dirtsnow, c_snow, c_sand, c_ice
|
|
||||||
if use_biomes then
|
|
||||||
c_dirt = minetest.get_content_id("default:dirt")
|
|
||||||
c_lawn = minetest.get_content_id("default:dirt_with_grass")
|
|
||||||
c_dirtsnow = minetest.get_content_id("default:dirt_with_snow")
|
|
||||||
c_snow = minetest.get_content_id("default:snowblock")
|
|
||||||
c_sand = minetest.get_content_id("default:sand")
|
|
||||||
c_ice = minetest.get_content_id("default:ice")
|
|
||||||
end
|
|
||||||
|
|
||||||
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
|
||||||
vm:get_data(data)
|
|
||||||
|
|
||||||
local a = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
|
||||||
local ystride = a.ystride -- Tip : the ystride of a VoxelArea is the number to add to the array index to get the index of the position above. It's faster because it avoids to completely recalculate the index.
|
|
||||||
|
|
||||||
local nid = mapsize.x*(mapsize.y-1) + 1
|
|
||||||
local incrY = -mapsize.x
|
|
||||||
local incrX = 1 - mapsize.y*incrY
|
|
||||||
local incrZ = mapsize.x*mapsize.y - mapsize.x*incrX - mapsize.x*mapsize.y*incrY
|
|
||||||
|
|
||||||
local i2d = 1
|
|
||||||
|
|
||||||
for z = minp.z, maxp.z do
|
|
||||||
for x = minp.x, maxp.x do
|
|
||||||
local ivm = a:index(x, maxp.y+1, z)
|
|
||||||
local ground_above = false
|
|
||||||
local temperature
|
|
||||||
if use_biomes then
|
|
||||||
temperature = noise_heat_map[i2d]+noise_heat_blend_map[i2d]
|
|
||||||
end
|
|
||||||
local terrain, lake
|
|
||||||
if not use_distort then
|
|
||||||
terrain = terrain_map[i2d]
|
|
||||||
lake = lake_map[i2d]
|
|
||||||
end
|
|
||||||
|
|
||||||
for y = maxp.y+1, minp.y, -1 do
|
|
||||||
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 i0 = i_origin + z0*incr + x0
|
|
||||||
local i1 = i0+1
|
|
||||||
local i2 = i1+incr
|
|
||||||
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])
|
|
||||||
end
|
|
||||||
|
|
||||||
if y <= maxp.y then
|
|
||||||
|
|
||||||
local is_lake = lake > terrain
|
|
||||||
if y <= terrain then
|
|
||||||
if not use_biomes or y <= terrain-1 or ground_above then
|
|
||||||
data[ivm] = c_stone
|
|
||||||
elseif is_lake or y < sea_level then
|
|
||||||
data[ivm] = c_sand
|
|
||||||
else
|
|
||||||
local temperature_y = temperature - y*elevation_chill
|
|
||||||
if temperature_y >= 15 then
|
|
||||||
data[ivm] = c_lawn
|
|
||||||
elseif temperature_y >= 0 then
|
|
||||||
data[ivm] = c_dirtsnow
|
|
||||||
else
|
|
||||||
data[ivm] = c_snow
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif y <= lake and lake > sea_level then
|
|
||||||
if not use_biomes or temperature - y*elevation_chill >= 0 then
|
|
||||||
data[ivm] = c_rwater
|
|
||||||
else
|
|
||||||
data[ivm] = c_ice
|
|
||||||
end
|
|
||||||
elseif y <= sea_level then
|
|
||||||
data[ivm] = c_water
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ground_above = y <= terrain
|
|
||||||
|
|
||||||
ivm = ivm - ystride
|
|
||||||
if use_distort then
|
|
||||||
nid = nid + incrY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if use_distort then
|
|
||||||
nid = nid + incrX
|
|
||||||
end
|
|
||||||
i2d = i2d + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if use_distort then
|
|
||||||
nid = nid + incrZ
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if use_biomegen_mod then
|
|
||||||
biomegen.generate_all(data, a, vm, minp, maxp, seed)
|
|
||||||
else
|
|
||||||
vm:set_data(data)
|
|
||||||
minetest.generate_ores(vm, minp, maxp)
|
|
||||||
end
|
|
||||||
|
|
||||||
vm:set_lighting({day = 0, night = 0})
|
|
||||||
vm:calc_lighting()
|
|
||||||
vm:update_liquids()
|
|
||||||
vm:write_to_map()
|
|
||||||
|
|
||||||
local t = os.clock()-t0
|
|
||||||
ngen = ngen + 1
|
|
||||||
sumtime = sumtime + t
|
|
||||||
sumtime2 = sumtime2 + t*t
|
|
||||||
minetest.log("verbose", ("[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))
|
|
||||||
end)
|
end)
|
||||||
|
271
mapgen.lua
Normal file
271
mapgen.lua
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
-- Mapgen loop and mapgen-related things
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
local sea_level = mapgen_rivers.settings.sea_level
|
||||||
|
local elevation_chill = mapgen_rivers.settings.elevation_chill
|
||||||
|
local use_distort = mapgen_rivers.settings.distort
|
||||||
|
local use_biomes = mapgen_rivers.settings.biomes
|
||||||
|
local use_biomegen_mod = use_biomes and minetest.global_exists('biomegen')
|
||||||
|
use_biomes = use_biomes and minetest.global_exists('default') and not use_biomegen_mod
|
||||||
|
|
||||||
|
if use_biomegen_mod then
|
||||||
|
biomegen.set_elevation_chill(elevation_chill)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Linear interpolation
|
||||||
|
local function interp(v00, v01, v11, v10, xf, zf)
|
||||||
|
local v0 = v01*xf + v00*(1-xf)
|
||||||
|
local v1 = v11*xf + v10*(1-xf)
|
||||||
|
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
|
||||||
|
local noise_x_map = {}
|
||||||
|
local noise_z_map = {}
|
||||||
|
local noise_distort_map = {}
|
||||||
|
local noise_heat_map = {}
|
||||||
|
local noise_heat_blend_map = {}
|
||||||
|
local mapsize
|
||||||
|
local init = false
|
||||||
|
|
||||||
|
local sumtime = 0
|
||||||
|
local sumtime2 = 0
|
||||||
|
local ngen = 0
|
||||||
|
|
||||||
|
function mapgen_rivers.make_chunk(minp, maxp, seed)
|
||||||
|
minetest.log("info", ("[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,
|
||||||
|
y = maxp.y-minp.y+1,
|
||||||
|
z = maxp.z-minp.z+1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not init then
|
||||||
|
mapsize = {
|
||||||
|
x = chulens.x,
|
||||||
|
y = chulens.y+1,
|
||||||
|
z = chulens.z,
|
||||||
|
}
|
||||||
|
if use_distort then
|
||||||
|
noise_x_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_x, mapsize)
|
||||||
|
noise_z_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_z, mapsize)
|
||||||
|
noise_distort_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.distort_amplitude, chulens)
|
||||||
|
end
|
||||||
|
if use_biomes then
|
||||||
|
noise_heat_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat, chulens)
|
||||||
|
noise_heat_blend_obj = minetest.get_perlin_map(mapgen_rivers.noise_params.heat_blend, chulens)
|
||||||
|
end
|
||||||
|
init = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local t0 = os.clock()
|
||||||
|
local minp2d = {x=minp.x, y=minp.z}
|
||||||
|
if use_distort then
|
||||||
|
noise_x_obj:get_3d_map_flat(minp, noise_x_map)
|
||||||
|
noise_z_obj:get_3d_map_flat(minp, noise_z_map)
|
||||||
|
noise_distort_obj:get_2d_map_flat(minp2d, noise_distort_map)
|
||||||
|
end
|
||||||
|
if use_biomes then
|
||||||
|
noise_heat_obj:get_2d_map_flat(minp2d, noise_heat_map)
|
||||||
|
noise_heat_blend_obj:get_2d_map_flat(minp2d, noise_heat_blend_map)
|
||||||
|
end
|
||||||
|
|
||||||
|
local terrain_map, lake_map, incr, i_origin
|
||||||
|
|
||||||
|
if use_distort then
|
||||||
|
local xmin, xmax, zmin, zmax = minp.x, maxp.x, minp.z, maxp.z
|
||||||
|
local i = 0
|
||||||
|
local i2d = 0
|
||||||
|
for z=minp.z, maxp.z do
|
||||||
|
for y=minp.y, maxp.y+1 do
|
||||||
|
for x=minp.x, maxp.x do
|
||||||
|
i = i+1
|
||||||
|
i2d = i2d+1
|
||||||
|
local distort = noise_distort_map[i2d]
|
||||||
|
local xv = noise_x_map[i]*distort + x
|
||||||
|
if xv < xmin then xmin = xv end
|
||||||
|
if xv > xmax then xmax = xv end
|
||||||
|
noise_x_map[i] = xv
|
||||||
|
local zv = noise_z_map[i]*distort + z
|
||||||
|
if zv < zmin then zmin = zv end
|
||||||
|
if zv > zmax then zmax = zv end
|
||||||
|
noise_z_map[i] = zv
|
||||||
|
end
|
||||||
|
i2d = i2d-chulens.x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local pminp = {x=floor(xmin), z=floor(zmin)}
|
||||||
|
local pmaxp = {x=floor(xmax)+1, z=floor(zmax)+1}
|
||||||
|
incr = pmaxp.x-pminp.x+1
|
||||||
|
i_origin = 1 - pminp.z*incr - pminp.x
|
||||||
|
terrain_map, lake_map = mapgen_rivers.make_heightmaps(pminp, pmaxp)
|
||||||
|
else
|
||||||
|
terrain_map, lake_map = mapgen_rivers.make_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("mapgen_stone")
|
||||||
|
local c_water = minetest.get_content_id("mapgen_water_source")
|
||||||
|
local c_rwater = minetest.get_content_id("mapgen_river_water_source")
|
||||||
|
|
||||||
|
local c_dirt, c_lawn, c_dirtsnow, c_snow, c_sand, c_ice
|
||||||
|
if use_biomes then
|
||||||
|
c_dirt = minetest.get_content_id("default:dirt")
|
||||||
|
c_lawn = minetest.get_content_id("default:dirt_with_grass")
|
||||||
|
c_dirtsnow = minetest.get_content_id("default:dirt_with_snow")
|
||||||
|
c_snow = minetest.get_content_id("default:snowblock")
|
||||||
|
c_sand = minetest.get_content_id("default:sand")
|
||||||
|
c_ice = minetest.get_content_id("default:ice")
|
||||||
|
end
|
||||||
|
|
||||||
|
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
||||||
|
vm:get_data(data)
|
||||||
|
|
||||||
|
local a = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
||||||
|
local ystride = a.ystride -- Tip : the ystride of a VoxelArea is the number to add to the array index to get the index of the position above. It's faster because it avoids to completely recalculate the index.
|
||||||
|
|
||||||
|
local nid = mapsize.x*(mapsize.y-1) + 1
|
||||||
|
local incrY = -mapsize.x
|
||||||
|
local incrX = 1 - mapsize.y*incrY
|
||||||
|
local incrZ = mapsize.x*mapsize.y - mapsize.x*incrX - mapsize.x*mapsize.y*incrY
|
||||||
|
|
||||||
|
local i2d = 1
|
||||||
|
|
||||||
|
for z = minp.z, maxp.z do
|
||||||
|
for x = minp.x, maxp.x do
|
||||||
|
local ivm = a:index(x, maxp.y+1, z)
|
||||||
|
local ground_above = false
|
||||||
|
local temperature
|
||||||
|
if use_biomes then
|
||||||
|
temperature = noise_heat_map[i2d]+noise_heat_blend_map[i2d]
|
||||||
|
end
|
||||||
|
local terrain, lake
|
||||||
|
if not use_distort then
|
||||||
|
terrain = terrain_map[i2d]
|
||||||
|
lake = lake_map[i2d]
|
||||||
|
end
|
||||||
|
|
||||||
|
for y = maxp.y+1, minp.y, -1 do
|
||||||
|
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 i0 = i_origin + z0*incr + x0
|
||||||
|
local i1 = i0+1
|
||||||
|
local i2 = i1+incr
|
||||||
|
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])
|
||||||
|
end
|
||||||
|
|
||||||
|
if y <= maxp.y then
|
||||||
|
|
||||||
|
local is_lake = lake > terrain
|
||||||
|
if y <= terrain then
|
||||||
|
if not use_biomes or y <= terrain-1 or ground_above then
|
||||||
|
data[ivm] = c_stone
|
||||||
|
elseif is_lake or y < sea_level then
|
||||||
|
data[ivm] = c_sand
|
||||||
|
else
|
||||||
|
local temperature_y = temperature - y*elevation_chill
|
||||||
|
if temperature_y >= 15 then
|
||||||
|
data[ivm] = c_lawn
|
||||||
|
elseif temperature_y >= 0 then
|
||||||
|
data[ivm] = c_dirtsnow
|
||||||
|
else
|
||||||
|
data[ivm] = c_snow
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif y <= lake and lake > sea_level then
|
||||||
|
if not use_biomes or temperature - y*elevation_chill >= 0 then
|
||||||
|
data[ivm] = c_rwater
|
||||||
|
else
|
||||||
|
data[ivm] = c_ice
|
||||||
|
end
|
||||||
|
elseif y <= sea_level then
|
||||||
|
data[ivm] = c_water
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ground_above = y <= terrain
|
||||||
|
|
||||||
|
ivm = ivm - ystride
|
||||||
|
if use_distort then
|
||||||
|
nid = nid + incrY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if use_distort then
|
||||||
|
nid = nid + incrX
|
||||||
|
end
|
||||||
|
i2d = i2d + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if use_distort then
|
||||||
|
nid = nid + incrZ
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if use_biomegen_mod then
|
||||||
|
biomegen.generate_all(data, a, vm, minp, maxp, seed)
|
||||||
|
else
|
||||||
|
vm:set_data(data)
|
||||||
|
minetest.generate_ores(vm, minp, maxp)
|
||||||
|
end
|
||||||
|
|
||||||
|
vm:set_lighting({day = 0, night = 0})
|
||||||
|
vm:calc_lighting()
|
||||||
|
vm:update_liquids()
|
||||||
|
vm:write_to_map()
|
||||||
|
|
||||||
|
local t = os.clock()-t0
|
||||||
|
ngen = ngen + 1
|
||||||
|
sumtime = sumtime + t
|
||||||
|
sumtime2 = sumtime2 + t*t
|
||||||
|
minetest.log("verbose", ("[mapgen_rivers] Done in %5.3f s"):format(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_on_generated(mapgen_rivers.make_chunk)
|
||||||
|
|
||||||
|
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))
|
||||||
|
end)
|
101
polygons.lua
101
polygons.lua
@ -1,94 +1,23 @@
|
|||||||
local modpath = mapgen_rivers.modpath
|
-- Fetch polygons from a given areas, and compute their properties
|
||||||
local mod_data_path = modpath .. 'river_data/'
|
-- and find to which polygon every point belongs
|
||||||
if not io.open(mod_data_path .. 'size', 'r') then
|
|
||||||
mod_data_path = modpath .. 'demo_data/'
|
|
||||||
end
|
|
||||||
|
|
||||||
local world_data_path = mapgen_rivers.world_data_path
|
|
||||||
minetest.mkdir(world_data_path)
|
|
||||||
|
|
||||||
dofile(modpath .. 'load.lua')
|
|
||||||
|
|
||||||
mapgen_rivers.grid = {}
|
|
||||||
|
|
||||||
local blocksize = mapgen_rivers.settings.blocksize
|
local blocksize = mapgen_rivers.settings.blocksize
|
||||||
local X = math.floor(mapgen_rivers.settings.map_x_size / blocksize)
|
local X = math.floor(mapgen_rivers.settings.map_x_size / blocksize)
|
||||||
local Z = math.floor(mapgen_rivers.settings.map_z_size / blocksize)
|
local Z = math.floor(mapgen_rivers.settings.map_z_size / blocksize)
|
||||||
|
|
||||||
local function offset_converter(o)
|
|
||||||
return (o + 0.5) * (1/256)
|
|
||||||
end
|
|
||||||
|
|
||||||
local load_all = mapgen_rivers.settings.load_all
|
|
||||||
|
|
||||||
-- Try to read file 'size'
|
|
||||||
local sfile = io.open(world_data_path..'size', 'r')
|
|
||||||
local first_mapgen = true
|
|
||||||
if sfile then
|
|
||||||
X, Z = tonumber(sfile:read('*l')), tonumber(sfile:read('*l'))
|
|
||||||
sfile:close()
|
|
||||||
first_mapgen = false
|
|
||||||
end
|
|
||||||
|
|
||||||
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...')
|
|
||||||
pregenerate(load_all)
|
|
||||||
|
|
||||||
if load_all then
|
|
||||||
local offset_x = mapgen_rivers.grid.offset_x
|
|
||||||
local offset_y = mapgen_rivers.grid.offset_y
|
|
||||||
for i=1, X*Z do
|
|
||||||
offset_x[i] = offset_converter(offset_x[i])
|
|
||||||
offset_y[i] = offset_converter(offset_y[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if data not already loaded
|
|
||||||
if not (first_mapgen and load_all) then
|
|
||||||
local load_map
|
|
||||||
if load_all then
|
|
||||||
load_map = mapgen_rivers.load_map
|
|
||||||
else
|
|
||||||
load_map = mapgen_rivers.interactive_loader
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.register_on_mods_loaded(function()
|
|
||||||
if load_all then
|
|
||||||
minetest.log("action", '[mapgen_rivers] Loading full grid')
|
|
||||||
else
|
|
||||||
minetest.log("action", '[mapgen_rivers] Loading grid as interactive loaders')
|
|
||||||
end
|
|
||||||
local grid = mapgen_rivers.grid
|
|
||||||
|
|
||||||
grid.dem = load_map('dem', 2, true, X*Z)
|
|
||||||
grid.lakes = load_map('lakes', 2, true, X*Z)
|
|
||||||
grid.dirs = load_map('dirs', 1, false, X*Z)
|
|
||||||
grid.rivers = load_map('rivers', 4, false, X*Z)
|
|
||||||
|
|
||||||
grid.offset_x = load_map('offset_x', 1, true, X*Z, offset_converter)
|
|
||||||
grid.offset_y = load_map('offset_y', 1, true, X*Z, offset_converter)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
mapgen_rivers.grid.size = {x=X, y=Z}
|
|
||||||
|
|
||||||
local function index(x, z)
|
local function index(x, z)
|
||||||
return z*X+x+1
|
return z*X+x+1
|
||||||
end
|
end
|
||||||
|
|
||||||
local min_catchment = mapgen_rivers.settings.min_catchment
|
|
||||||
local max_catchment = mapgen_rivers.settings.max_catchment
|
|
||||||
|
|
||||||
local map_offset = {x=0, z=0}
|
local map_offset = {x=0, z=0}
|
||||||
if mapgen_rivers.settings.center then
|
mapgen_rivers.register_on_grid_loaded(function(grid)
|
||||||
map_offset.x = blocksize*X/2
|
X = grid.size.x
|
||||||
map_offset.z = blocksize*Z/2
|
Z = grid.size.y
|
||||||
end
|
if mapgen_rivers.settings.center then
|
||||||
|
map_offset.x = blocksize*X/2
|
||||||
|
map_offset.z = blocksize*Z/2
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- Localize for performance
|
-- Localize for performance
|
||||||
local floor, ceil, min, max, abs = math.floor, math.ceil, math.min, math.max, math.abs
|
local floor, ceil, min, max, abs = math.floor, math.ceil, math.min, math.max, math.abs
|
||||||
@ -119,7 +48,7 @@ local init = false
|
|||||||
|
|
||||||
-- On map generation, determine into which polygon every point (in 2D) will fall.
|
-- On map generation, determine into which polygon every point (in 2D) will fall.
|
||||||
-- Also store polygon-specific data
|
-- Also store polygon-specific data
|
||||||
local function make_polygons(minp, maxp)
|
function mapgen_rivers.make_polygons(minp, maxp)
|
||||||
|
|
||||||
local grid = mapgen_rivers.grid
|
local grid = mapgen_rivers.grid
|
||||||
local dem = grid.dem
|
local dem = grid.dem
|
||||||
@ -236,15 +165,15 @@ local function make_polygons(minp, maxp)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
polygon.river_corners = {riverA, 1-riverB, 2-riverC, 1-riverD}
|
polygon.river_corners = {riverA, riverB, riverC, riverD}
|
||||||
|
|
||||||
-- Flow directions
|
-- Flow directions
|
||||||
local dirA, dirB, dirC, dirD = dirs[iA], dirs[iB], dirs[iC], dirs[iD]
|
local dirA, dirB, dirC, dirD = dirs[iA], dirs[iB], dirs[iC], dirs[iD]
|
||||||
-- Determine the river flux on the edges, by testing dirs values
|
-- Determine the river flux on the edges, by testing dirs values
|
||||||
local river_west = (dirA==1 and riverA or 0) + (dirD==3 and riverD or 0)
|
local river_west = (dirA==1 and riverA or 0) + (dirD==3 and riverD or 0)
|
||||||
local river_north = (dirA==2 and riverA or 0) + (dirB==4 and riverB or 0)
|
local river_north = (dirA==2 and riverA or 0) + (dirB==4 and riverB or 0)
|
||||||
local river_east = 1 - (dirB==1 and riverB or 0) - (dirC==3 and riverC or 0)
|
local river_east = (dirB==1 and riverB or 0) + (dirC==3 and riverC or 0)
|
||||||
local river_south = 1 - (dirD==2 and riverD or 0) - (dirC==4 and riverC or 0)
|
local river_south = (dirD==2 and riverD or 0) + (dirC==4 and riverC or 0)
|
||||||
|
|
||||||
polygon.rivers = {river_west, river_north, river_east, river_south}
|
polygon.rivers = {river_west, river_north, river_east, river_south}
|
||||||
end
|
end
|
||||||
@ -252,5 +181,3 @@ local function make_polygons(minp, maxp)
|
|||||||
|
|
||||||
return polygons
|
return polygons
|
||||||
end
|
end
|
||||||
|
|
||||||
return make_polygons
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
-- Generate the grid using terrainlib_lua
|
||||||
|
-- Only called on first mapgen, if there is no grid yet
|
||||||
|
|
||||||
local EvolutionModel = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/erosion.lua')
|
local EvolutionModel = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/erosion.lua')
|
||||||
local twist = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/twist.lua')
|
local twist = dofile(mapgen_rivers.modpath .. '/terrainlib_lua/twist.lua')
|
||||||
|
|
||||||
@ -14,8 +17,39 @@ 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 function pregenerate(keep_loaded)
|
local use_margin = mapgen_rivers.settings.margin
|
||||||
local grid = mapgen_rivers.grid
|
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
|
||||||
|
|
||||||
|
function mapgen_rivers.pregenerate(grid)
|
||||||
local size = grid.size
|
local size = grid.size
|
||||||
|
|
||||||
if size.x * size.y > 4e6 then
|
if size.x * size.y > 4e6 then
|
||||||
@ -31,6 +65,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)
|
||||||
@ -46,6 +84,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
|
||||||
@ -62,25 +103,14 @@ local function pregenerate(keep_loaded)
|
|||||||
offset_y[i] = mmin(mmax(offset_y[i]*256, -128), 127)
|
offset_y[i] = mmin(mmax(offset_y[i]*256, -128), 127)
|
||||||
end
|
end
|
||||||
|
|
||||||
mapgen_rivers.write_map('dem', model.dem, 2)
|
grid.dem = model.dem
|
||||||
mapgen_rivers.write_map('lakes', model.lakes, 2)
|
grid.lakes = model.lakes
|
||||||
mapgen_rivers.write_map('dirs', model.dirs, 1)
|
grid.dirs = model.dirs
|
||||||
mapgen_rivers.write_map('rivers', model.rivers, 4)
|
grid.rivers = model.rivers
|
||||||
mapgen_rivers.write_map('offset_x', offset_x, 1)
|
grid.offset_x = offset_x
|
||||||
mapgen_rivers.write_map('offset_y', offset_y, 1)
|
grid.offset_y = offset_y
|
||||||
local sfile = io.open(mapgen_rivers.world_data_path .. 'size', "w")
|
|
||||||
sfile:write(size.x..'\n'..size.y)
|
|
||||||
sfile:close()
|
|
||||||
|
|
||||||
if keep_loaded then
|
grid.load_method = "full"
|
||||||
grid.dem = model.dem
|
grid.conv_applied = false
|
||||||
grid.lakes = model.lakes
|
|
||||||
grid.dirs = model.dirs
|
|
||||||
grid.rivers = model.rivers
|
|
||||||
grid.offset_x = offset_x
|
|
||||||
grid.offset_y = offset_y
|
|
||||||
end
|
|
||||||
collectgarbage()
|
collectgarbage()
|
||||||
end
|
end
|
||||||
|
|
||||||
return pregenerate
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
-- Read global and per-world settings
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
@ -95,6 +97,9 @@ mapgen_rivers.settings = {
|
|||||||
|
|
||||||
map_x_size = def_setting('map_x_size', 'number'),
|
map_x_size = def_setting('map_x_size', 'number'),
|
||||||
map_z_size = def_setting('map_z_size', 'number'),
|
map_z_size = def_setting('map_z_size', 'number'),
|
||||||
|
margin = def_setting('margin', 'bool'),
|
||||||
|
margin_width = def_setting('margin_width', 'number'),
|
||||||
|
margin_elev = def_setting('margin_elev', 'number'),
|
||||||
evol_params = {
|
evol_params = {
|
||||||
K = def_setting('river_erosion_coef', 'number'),
|
K = def_setting('river_erosion_coef', 'number'),
|
||||||
m = def_setting('river_erosion_power', 'number'),
|
m = def_setting('river_erosion_power', 'number'),
|
||||||
@ -108,6 +113,9 @@ mapgen_rivers.settings = {
|
|||||||
load_all = mtsettings:get_bool('mapgen_rivers_load_all')
|
load_all = mtsettings:get_bool('mapgen_rivers_load_all')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapgen_rivers.settings.load_method =
|
||||||
|
mapgen_rivers.settings.load_all and "full" or "interactive"
|
||||||
|
|
||||||
mapgen_rivers.noise_params = {
|
mapgen_rivers.noise_params = {
|
||||||
base = def_setting("np_base", "noise"),
|
base = def_setting("np_base", "noise"),
|
||||||
distort_x = def_setting("np_distort_x", "noise"),
|
distort_x = def_setting("np_distort_x", "noise"),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.2",
|
"version": "1.0.2-dev1",
|
||||||
|
|
||||||
"center": true,
|
"center": true,
|
||||||
"water_level": 1,
|
"water_level": 1,
|
||||||
@ -15,6 +15,9 @@
|
|||||||
|
|
||||||
"map_x_size": 15000,
|
"map_x_size": 15000,
|
||||||
"map_z_size": 15000,
|
"map_z_size": 15000,
|
||||||
|
"margin": true,
|
||||||
|
"margin_width": 2000,
|
||||||
|
"margin_elev": -200,
|
||||||
"river_erosion_coef": 0.5,
|
"river_erosion_coef": 0.5,
|
||||||
"river_erosion_power": 0.4,
|
"river_erosion_power": 0.4,
|
||||||
"diffusive_erosion": 0.5,
|
"diffusive_erosion": 0.5,
|
||||||
|
@ -23,6 +23,16 @@ mapgen_rivers_map_x_size (Map X size) int 15000 500 66000
|
|||||||
# When increasing, it is recommended to increase blocksize too
|
# When increasing, it is recommended to increase blocksize too
|
||||||
mapgen_rivers_map_z_size (Map Z size) int 15000 500 66000
|
mapgen_rivers_map_z_size (Map Z size) int 15000 500 66000
|
||||||
|
|
||||||
|
# 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
|
||||||
|
@ -134,7 +134,7 @@ local rivermapper = dofile(modpath .. "rivermapper.lua")
|
|||||||
local gaussian = dofile(modpath .. "gaussian.lua")
|
local gaussian = dofile(modpath .. "gaussian.lua")
|
||||||
|
|
||||||
local function flow(model)
|
local function flow(model)
|
||||||
model.dirs, model.lakes = rivermapper.flow_routing(model.dem, model.dirs, model.lakes, 'semirandom')
|
model.dirs, model.lakes = rivermapper.flow_routing(model.dem, model.dirs, model.lakes)
|
||||||
model.rivers = rivermapper.accumulate(model.dirs, model.rivers)
|
model.rivers = rivermapper.accumulate(model.dirs, model.rivers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -10,50 +10,18 @@
|
|||||||
-- Big thanks to them for releasing this paper under a free license ! :)
|
-- Big thanks to them for releasing this paper under a free license ! :)
|
||||||
|
|
||||||
-- The algorithm here makes use of most of the paper's concepts, including the Planar Borůvka algorithm.
|
-- The algorithm here makes use of most of the paper's concepts, including the Planar Borůvka algorithm.
|
||||||
-- Only flow_local and accumulate_flow are custom algorithms.
|
|
||||||
|
|
||||||
|
|
||||||
local function flow_local_semirandom(plist)
|
|
||||||
-- Determines how water should flow at 1 node scale.
|
|
||||||
-- The straightforward approach would be "Water will flow to the lowest of the 4 neighbours", but here water flows to one of the lower neighbours, chosen randomly, but probability depends on height difference.
|
|
||||||
-- This makes rivers better follow the curvature of the topography at large scale, and be less biased by pure N/E/S/W directions.
|
|
||||||
-- 'plist': array of downward height differences (0 if upward)
|
|
||||||
local sum = 0
|
|
||||||
for i=1, #plist do
|
|
||||||
sum = sum + plist[i] -- Sum of probabilities
|
|
||||||
end
|
|
||||||
|
|
||||||
if sum == 0 then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
local r = math.random() * sum
|
|
||||||
for i=1, #plist do
|
|
||||||
local p = plist[i]
|
|
||||||
if r < p then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
r = r - p
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Maybe implement more flow methods in the future?
|
|
||||||
local flow_methods = {
|
|
||||||
semirandom = flow_local_semirandom,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Applies all steps of the flow routing, to calculate flow direction for every node, and lake surface elevation.
|
-- Applies all steps of the flow routing, to calculate flow direction for every node, and lake surface elevation.
|
||||||
-- It's quite a hard piece of code, but we will go step by step and explain what's going on, so stay with me and... let's goooooooo!
|
-- It's quite a hard piece of code, but we will go step by step and explain what's going on, so stay with me and... let's goooooooo!
|
||||||
local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are optional tables to reuse for memory optimization, they may contain any data.
|
local function flow_routing(dem, dirs, lakes) -- 'dirs' and 'lakes' are optional tables to reuse for memory optimization, they may contain any data.
|
||||||
method = method or 'semirandom'
|
|
||||||
local flow_local = flow_methods[method] or flow_local_semirandom
|
|
||||||
|
|
||||||
dirs = dirs or {}
|
dirs = dirs or {}
|
||||||
lakes = lakes or {}
|
lakes = lakes or {}
|
||||||
|
|
||||||
-- Localize for performance
|
-- Localize for performance
|
||||||
local tremove = table.remove
|
local tremove = table.remove
|
||||||
local mmax = math.max
|
local mmax = math.max
|
||||||
|
local mrand = math.random
|
||||||
|
|
||||||
local X, Y = dem.X, dem.Y
|
local X, Y = dem.X, dem.Y
|
||||||
dirs.X = X
|
dirs.X = X
|
||||||
@ -74,14 +42,29 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
for y=1, Y do
|
for y=1, Y do
|
||||||
for x=1, X do
|
for x=1, X do
|
||||||
local zi = dem[i]
|
local zi = dem[i]
|
||||||
local plist = { -- Get the height difference of the 4 neighbours (and 0 if uphill)
|
-- Determine how water should flow at 1 node scale.
|
||||||
y<Y and mmax(zi-dem[i+X], 0) or 0, -- Southward
|
-- The straightforward approach would be "Water will flow to the lowest of the 4 neighbours", but here water flows to one of the lower neighbours, chosen randomly, with probability depending on height difference.
|
||||||
x<X and mmax(zi-dem[i+1], 0) or 0, -- Eastward
|
-- This makes rivers better follow the curvature of the topography at large scale, and be less biased by pure N/E/S/W directions.
|
||||||
y>1 and mmax(zi-dem[i-X], 0) or 0, -- Northward
|
local pSouth = y<Y and mmax(zi-dem[i+X], 0) or 0
|
||||||
x>1 and mmax(zi-dem[i-1], 0) or 0, -- Westward
|
local pEast = x<X and mmax(zi-dem[i+1], 0) or 0
|
||||||
}
|
local pNorth = y>1 and mmax(zi-dem[i-X], 0) or 0
|
||||||
|
local pWest = x>1 and mmax(zi-dem[i-1], 0) or 0
|
||||||
|
|
||||||
|
local d = 0
|
||||||
|
local sum = pSouth + pEast + pNorth + pWest
|
||||||
|
local r = mrand() * sum
|
||||||
|
if sum > 0 then
|
||||||
|
if r < pSouth then
|
||||||
|
d = 1
|
||||||
|
elseif r-pSouth < pEast then
|
||||||
|
d = 2
|
||||||
|
elseif r-pSouth-pEast < pNorth then
|
||||||
|
d = 3
|
||||||
|
else
|
||||||
|
d = 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local d = flow_local(plist)
|
|
||||||
-- 'dirs': Direction toward which water flow
|
-- 'dirs': Direction toward which water flow
|
||||||
-- 'dirs2': Directions from which water comes
|
-- 'dirs2': Directions from which water comes
|
||||||
dirs[i] = d
|
dirs[i] = d
|
||||||
@ -110,89 +93,172 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
local links = {}
|
local links = {}
|
||||||
local basin_links
|
local basin_links
|
||||||
|
|
||||||
-- Function to analyse a link between two nodes
|
|
||||||
local function add_link(i1, i2, b1, isY)
|
|
||||||
-- i1, i2: coordinates of two nodes
|
|
||||||
-- b1: basin that contains i1
|
|
||||||
-- isY: whether the link is in Y direction
|
|
||||||
local b2
|
|
||||||
-- Note that basin number #0 represents the outside of the map; or if the coordinate is inside the map, means that the basin number is uninitialized.
|
|
||||||
if i2 == 0 then -- If outside the map
|
|
||||||
b2 = 0
|
|
||||||
else
|
|
||||||
b2 = basin_id[i2]
|
|
||||||
if b2 == 0 then -- If basin of i2 is not already computed, skip
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if b2 ~= b1 then -- If these two nodes don't belong to the same basin, we have found a link between two adjacent basins
|
|
||||||
local elev = i2 == 0 and dem[i1] or mmax(dem[i1], dem[i2]) -- Elevation of the highest of the two sides of the link (or only i1 if b2 is map outside)
|
|
||||||
local l2 = basin_links[b2]
|
|
||||||
if not l2 then
|
|
||||||
l2 = {}
|
|
||||||
basin_links[b2] = l2
|
|
||||||
end
|
|
||||||
if not l2.elev or l2.elev > elev then -- If this link is lower than the lowest registered link between these two basins, register it as the new lowest pass
|
|
||||||
l2.elev = elev
|
|
||||||
l2.i = mmax(i1,i2)
|
|
||||||
l2.is_y = isY
|
|
||||||
l2[1] = b2
|
|
||||||
l2[2] = b1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for i=1, X*Y do
|
for i=1, X*Y do
|
||||||
basin_id[i] = 0
|
basin_id[i] = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
for ib=1, nbasins do
|
local cur = nbasins
|
||||||
-- Here we will recursively search upstream from the singular node to determine its drainage basin
|
local ib = 0
|
||||||
local queue = {singular[ib]} -- Start with the singular node, then this queue will be filled with water donors neighbours
|
while cur > 0 do
|
||||||
basin_links = {}
|
local i = singular[cur]
|
||||||
links[#links+1] = basin_links
|
cur = cur - 1
|
||||||
while #queue > 0 do
|
if dirs[i] == 0 then
|
||||||
local i = tremove(queue)
|
basin_links = {}
|
||||||
|
links[#links+1] = basin_links
|
||||||
|
ib = ib + 1
|
||||||
|
end
|
||||||
basin_id[i] = ib
|
basin_id[i] = ib
|
||||||
local d = dirs2[i] -- Get the directions water is coming from
|
local d = dirs2[i] -- Get the directions water is coming from
|
||||||
|
|
||||||
-- Iterate through the 4 directions
|
-- Iterate through the 4 directions
|
||||||
|
-- Loop is unrolled on purpose, for performance (critical part!)
|
||||||
|
----------
|
||||||
|
-- EAST --
|
||||||
|
----------
|
||||||
if d >= 8 then -- River coming from the East
|
if d >= 8 then -- River coming from the East
|
||||||
d = d - 8
|
d = d - 8
|
||||||
queue[#queue+1] = i+1
|
cur = cur + 1
|
||||||
|
singular[cur] = i+1
|
||||||
-- If no river is coming from the East, we might be at the limit of two basins, thus we need to test adjacency.
|
-- If no river is coming from the East, we might be at the limit of two basins, thus we need to test adjacency.
|
||||||
elseif i%X > 0 then
|
elseif i%X > 0 then
|
||||||
add_link(i, i+1, ib, false)
|
if basin_id[i+1] ~= ib and basin_id[i+1] ~= 0 then
|
||||||
|
local b2 = basin_id[i+1]
|
||||||
|
local elev = mmax(dem[i], dem[i+1]) -- Elevation of the highest of the two sides of the link (or only i1 if b2 is map outside)
|
||||||
|
local l2 = basin_links[b2]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {b2, ib, elev=elev, i=i+1, is_y=false}
|
||||||
|
basin_links[b2] = l2 -- Potential non-linear complexity here
|
||||||
|
elseif l2.elev > elev then -- If this link is lower than the lowest registered link between these two basins, register it as the new lowest pass
|
||||||
|
l2.elev = elev
|
||||||
|
l2.i = i+1
|
||||||
|
l2.is_y = false
|
||||||
|
l2[1] = b2
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
|
end
|
||||||
else -- If the eastern neighbour is outside the map
|
else -- If the eastern neighbour is outside the map
|
||||||
add_link(i, 0, ib, false)
|
local l2 = basin_links[0]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {0, ib, elev=dem[i], i=i, is_y=false}
|
||||||
|
basin_links[0] = l2
|
||||||
|
elseif l2.elev > dem[i] then
|
||||||
|
l2.elev = dem[i]
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = false
|
||||||
|
l2[1] = 0
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-----------
|
||||||
|
-- SOUTH --
|
||||||
|
-----------
|
||||||
if d >= 4 then -- River coming from the South
|
if d >= 4 then -- River coming from the South
|
||||||
d = d - 4
|
d = d - 4
|
||||||
queue[#queue+1] = i+X
|
cur = cur + 1
|
||||||
|
singular[cur] = i+X
|
||||||
elseif i <= X*(Y-1) then
|
elseif i <= X*(Y-1) then
|
||||||
add_link(i, i+X, ib, true)
|
if basin_id[i+X] ~= ib and basin_id[i+X] ~= 0 then
|
||||||
|
local b2 = basin_id[i+X]
|
||||||
|
local elev = mmax(dem[i], dem[i+X])
|
||||||
|
local l2 = basin_links[b2]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {b2, ib, elev=elev, i=i+X, is_y=true}
|
||||||
|
basin_links[b2] = l2
|
||||||
|
elseif l2.elev > elev then
|
||||||
|
l2.elev = elev
|
||||||
|
l2.i = i+X
|
||||||
|
l2.is_y = true
|
||||||
|
l2[1] = b2
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
add_link(i, 0, ib, true)
|
local l2 = basin_links[0]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {0, ib, elev=dem[i], i=i, is_y=true}
|
||||||
|
basin_links[0] = l2
|
||||||
|
elseif l2.elev > dem[i] then
|
||||||
|
l2.elev = dem[i]
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = true
|
||||||
|
l2[1] = 0
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
----------
|
||||||
|
-- WEST --
|
||||||
|
----------
|
||||||
if d >= 2 then -- River coming from the West
|
if d >= 2 then -- River coming from the West
|
||||||
d = d - 2
|
d = d - 2
|
||||||
queue[#queue+1] = i-1
|
cur = cur + 1
|
||||||
|
singular[cur] = i-1
|
||||||
elseif i%X ~= 1 then
|
elseif i%X ~= 1 then
|
||||||
add_link(i, i-1, ib, false)
|
if basin_id[i-1] ~= ib and basin_id[i-1] ~= 0 then
|
||||||
|
local b2 = basin_id[i-1]
|
||||||
|
local elev = mmax(dem[i], dem[i-1])
|
||||||
|
local l2 = basin_links[b2]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {b2, ib, elev=elev, i=i, is_y=false}
|
||||||
|
basin_links[b2] = l2
|
||||||
|
elseif l2.elev > elev then
|
||||||
|
l2.elev = elev
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = false
|
||||||
|
l2[1] = b2
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
add_link(i, 0, ib, false)
|
local l2 = basin_links[0]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {0, ib, elev=dem[i], i=i, is_y=false}
|
||||||
|
basin_links[0] = l2
|
||||||
|
elseif l2.elev > dem[i] then
|
||||||
|
l2.elev = dem[i]
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = false
|
||||||
|
l2[1] = 0
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-----------
|
||||||
|
-- NORTH --
|
||||||
|
-----------
|
||||||
if d >= 1 then -- River coming from the North
|
if d >= 1 then -- River coming from the North
|
||||||
queue[#queue+1] = i-X
|
cur = cur + 1
|
||||||
|
singular[cur] = i-X
|
||||||
elseif i > X then
|
elseif i > X then
|
||||||
add_link(i, i-X, ib, true)
|
if basin_id[i-X] ~= ib and basin_id[i-X] ~= 0 then
|
||||||
|
local b2 = basin_id[i-X]
|
||||||
|
local elev = mmax(dem[i], dem[i-X])
|
||||||
|
local l2 = basin_links[b2]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {b2, ib, elev=elev, i=i, is_y=true}
|
||||||
|
basin_links[b2] = l2
|
||||||
|
elseif l2.elev > elev then
|
||||||
|
l2.elev = elev
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = true
|
||||||
|
l2[1] = b2
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
add_link(i, 0, ib, true)
|
local l2 = basin_links[0]
|
||||||
|
if not l2 then
|
||||||
|
l2 = {0, ib, elev=dem[i], i=i, is_y=true}
|
||||||
|
basin_links[0] = l2
|
||||||
|
elseif l2.elev > dem[i] then
|
||||||
|
l2.elev = dem[i]
|
||||||
|
l2.i = i
|
||||||
|
l2.is_y = true
|
||||||
|
l2[1] = 0
|
||||||
|
l2[2] = ib
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
dirs2 = nil
|
dirs2 = nil
|
||||||
|
|
||||||
@ -223,26 +289,31 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
-- Mareš' optimizations mainly consist in skipping elements that have over 8 links, until extra links are removed when other elements are merged.
|
-- Mareš' optimizations mainly consist in skipping elements that have over 8 links, until extra links are removed when other elements are merged.
|
||||||
-- Note that for this step we are only working on basins, not grid nodes.
|
-- Note that for this step we are only working on basins, not grid nodes.
|
||||||
local lowlevel = {}
|
local lowlevel = {}
|
||||||
for i, n in pairs(nlinks) do
|
cur = 0
|
||||||
if n <= 8 then
|
local ref = singular -- Reuse table
|
||||||
lowlevel[i] = links[i]
|
for i=0, nbasins do
|
||||||
|
if nlinks[i] <= 8 then
|
||||||
|
cur = cur + 1
|
||||||
|
lowlevel[cur] = i
|
||||||
|
ref[i] = cur
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local basin_graph = {}
|
local basin_graph = {}
|
||||||
for n=1, nbasins do
|
for i=0, nbasins do
|
||||||
|
basin_graph[i] = {} -- Initialize (to ensure subtables don't go in the hash part)
|
||||||
|
end
|
||||||
|
for i=1, nbasins do
|
||||||
-- Iterate in lowlevel but its contents may change during the loop
|
-- Iterate in lowlevel but its contents may change during the loop
|
||||||
-- 'next' called with only one argument always returns an element if table is not empty
|
local b1 = lowlevel[cur]
|
||||||
local b1, lnk1 = next(lowlevel)
|
cur = cur - 1
|
||||||
lowlevel[b1] = nil
|
local lnk1 = links[b1]
|
||||||
|
|
||||||
local b2
|
local b2
|
||||||
local lowest = math.huge
|
local lowest = math.huge
|
||||||
local lnk1 = links[b1]
|
local lnk1 = links[b1]
|
||||||
local i = 0
|
|
||||||
-- Look for lowest link
|
-- Look for lowest link
|
||||||
for bn, bdata in pairs(lnk1) do
|
for bn, bdata in pairs(lnk1) do
|
||||||
i = i + 1
|
|
||||||
if bdata.elev < lowest then
|
if bdata.elev < lowest then
|
||||||
lowest = bdata.elev
|
lowest = bdata.elev
|
||||||
b2 = bn
|
b2 = bn
|
||||||
@ -252,13 +323,7 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
-- Add link to the graph, in both directions
|
-- Add link to the graph, in both directions
|
||||||
local bound = lnk1[b2]
|
local bound = lnk1[b2]
|
||||||
local bb1, bb2 = bound[1], bound[2]
|
local bb1, bb2 = bound[1], bound[2]
|
||||||
if not basin_graph[bb1] then
|
basin_graph[bb1][bb2] = bound -- Potential non-linear complexity here
|
||||||
basin_graph[bb1] = {}
|
|
||||||
end
|
|
||||||
if not basin_graph[bb2] then
|
|
||||||
basin_graph[bb2] = {}
|
|
||||||
end
|
|
||||||
basin_graph[bb1][bb2] = bound
|
|
||||||
basin_graph[bb2][bb1] = bound
|
basin_graph[bb2][bb1] = bound
|
||||||
|
|
||||||
-- Merge basin b1 into b2
|
-- Merge basin b1 into b2
|
||||||
@ -269,7 +334,9 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
nlinks[b2] = nlinks[b2] - 1
|
nlinks[b2] = nlinks[b2] - 1
|
||||||
-- When the number of links is changing, we need to check whether the basin can be added to / removed from 'lowlevel'
|
-- When the number of links is changing, we need to check whether the basin can be added to / removed from 'lowlevel'
|
||||||
if nlinks[b2] == 8 then
|
if nlinks[b2] == 8 then
|
||||||
lowlevel[b2] = lnk2
|
cur = cur + 1
|
||||||
|
lowlevel[cur] = b2
|
||||||
|
ref[b2] = cur
|
||||||
end
|
end
|
||||||
-- Look for basin 1's neighbours, and add them to basin 2 if they have a lower pass
|
-- Look for basin 1's neighbours, and add them to basin 2 if they have a lower pass
|
||||||
for bn, bdata in pairs(lnk1) do
|
for bn, bdata in pairs(lnk1) do
|
||||||
@ -279,12 +346,16 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
if lnkn[b2] then -- If bassin bn is also linked to b2
|
if lnkn[b2] then -- If bassin bn is also linked to b2
|
||||||
nlinks[bn] = nlinks[bn] - 1 -- Then bassin bn is losing a link because it keeps only one link toward b1/b2 after the merge
|
nlinks[bn] = nlinks[bn] - 1 -- Then bassin bn is losing a link because it keeps only one link toward b1/b2 after the merge
|
||||||
if nlinks[bn] == 8 then
|
if nlinks[bn] == 8 then
|
||||||
lowlevel[bn] = lnkn
|
cur = cur + 1
|
||||||
|
lowlevel[cur] = bn
|
||||||
|
ref[bn] = cur
|
||||||
end
|
end
|
||||||
else -- If bn was linked to b1 but not to b2
|
else -- If bn was linked to b1 but not to b2
|
||||||
nlinks[b2] = nlinks[b2] + 1 -- Then b2 is gaining a link to bn because of the merge
|
nlinks[b2] = nlinks[b2] + 1 -- Then b2 is gaining a link to bn because of the merge
|
||||||
if nlinks[b2] == 9 then
|
if nlinks[b2] == 9 then
|
||||||
lowlevel[b2] = nil
|
lowlevel[ref[b2]] = lowlevel[cur]
|
||||||
|
ref[lowlevel[cur]] = ref[b2]
|
||||||
|
cur = cur - 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -302,15 +373,17 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
-- To orient the basin graph, we will consider that the ultimate basin water should flow into is the map outside (basin #0). We will start from it and recursively walk upstream to the neighbouring basins, using only links that are in the minimal spanning tree. This gives the flow direction of the links, and thus, the outlet of every basin.
|
-- To orient the basin graph, we will consider that the ultimate basin water should flow into is the map outside (basin #0). We will start from it and recursively walk upstream to the neighbouring basins, using only links that are in the minimal spanning tree. This gives the flow direction of the links, and thus, the outlet of every basin.
|
||||||
-- This will also give lake elevation, which is the highest link encountered between map outside and the given basin on the spanning tree.
|
-- This will also give lake elevation, which is the highest link encountered between map outside and the given basin on the spanning tree.
|
||||||
-- And within each basin, we need to modify flow directions to connect the singular node to the outlet.
|
-- And within each basin, we need to modify flow directions to connect the singular node to the outlet.
|
||||||
local queue = {[0] = -math.huge}
|
local queue = {0}
|
||||||
|
local queuevalues = {-math.huge}
|
||||||
|
cur = 1
|
||||||
local basin_lake = {}
|
local basin_lake = {}
|
||||||
for n=1, nbasins do
|
for n=1, nbasins do
|
||||||
basin_lake[n] = 0
|
basin_lake[n] = 0
|
||||||
end
|
end
|
||||||
local reverse = {3, 4, 1, 2, [0]=0}
|
local reverse = {3, 4, 1, 2, [0]=0}
|
||||||
for n=1, nbasins do
|
while cur > 0 do
|
||||||
local b1, elev1 = next(queue) -- Pop from queue
|
local b1, elev1 = queue[cur], queuevalues[cur] -- Pop from queue
|
||||||
queue[b1] = nil
|
cur = cur - 1
|
||||||
basin_lake[b1] = elev1
|
basin_lake[b1] = elev1
|
||||||
-- Iterate through b1's neighbours (according to the spanning tree)
|
-- Iterate through b1's neighbours (according to the spanning tree)
|
||||||
for b2, bound in pairs(basin_graph[b1]) do
|
for b2, bound in pairs(basin_graph[b1]) do
|
||||||
@ -349,7 +422,9 @@ local function flow_routing(dem, dirs, lakes, method) -- 'dirs' and 'lakes' are
|
|||||||
until dir == 0 -- Stop when reaching the singular node
|
until dir == 0 -- Stop when reaching the singular node
|
||||||
|
|
||||||
-- Add basin b2 into the queue, and keep the highest link elevation, that will define the elevation of the lake in b2
|
-- Add basin b2 into the queue, and keep the highest link elevation, that will define the elevation of the lake in b2
|
||||||
queue[b2] = mmax(elev1, bound.elev)
|
cur = cur + 1
|
||||||
|
queue[cur] = b2
|
||||||
|
queuevalues[cur] = mmax(elev1, bound.elev)
|
||||||
-- Remove b1 from b2's neighbours to avoid coming back to b1
|
-- Remove b1 from b2's neighbours to avoid coming back to b1
|
||||||
basin_graph[b2][b1] = nil
|
basin_graph[b2][b1] = nil
|
||||||
end
|
end
|
||||||
@ -372,31 +447,25 @@ local function accumulate(dirs, waterq)
|
|||||||
-- This means: how many nodes will give their water to that given node, directly or indirectly?
|
-- This means: how many nodes will give their water to that given node, directly or indirectly?
|
||||||
-- This is obtained by following rivers downstream and summing up the flow of every tributary, starting with a value of 1 at the sources.
|
-- This is obtained by following rivers downstream and summing up the flow of every tributary, starting with a value of 1 at the sources.
|
||||||
-- This will give non-zero values for every node but only large values will be considered to be rivers.
|
-- This will give non-zero values for every node but only large values will be considered to be rivers.
|
||||||
waterq = waterq or {}
|
|
||||||
local X, Y = dirs.X, dirs.Y
|
local X, Y = dirs.X, dirs.Y
|
||||||
|
waterq = waterq or {X=X, Y=Y}
|
||||||
|
|
||||||
local ndonors = {}
|
local ndonors = {}
|
||||||
local waterq = {X=X, Y=Y}
|
|
||||||
for i=1, X*Y do
|
for i=1, X*Y do
|
||||||
ndonors[i] = 0
|
ndonors[i] = 0
|
||||||
waterq[i] = 1
|
waterq[i] = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Calculate the number of direct donors
|
-- Calculate the number of direct donors
|
||||||
for i1=1, X*Y do
|
for i=1, X*Y do
|
||||||
local i2
|
if dirs[i] == 1 then
|
||||||
local dir = dirs[i1]
|
ndonors[i+X] = ndonors[i+X] + 1
|
||||||
if dir == 1 then
|
elseif dirs[i] == 2 then
|
||||||
i2 = i1+X
|
ndonors[i+1] = ndonors[i+1] + 1
|
||||||
elseif dir == 2 then
|
elseif dirs[i] == 3 then
|
||||||
i2 = i1+1
|
ndonors[i-X] = ndonors[i-X] + 1
|
||||||
elseif dir == 3 then
|
elseif dirs[i] == 4 then
|
||||||
i2 = i1-X
|
ndonors[i-1] = ndonors[i-1] + 1
|
||||||
elseif dir == 4 then
|
|
||||||
i2 = i1-1
|
|
||||||
end
|
|
||||||
if i2 then
|
|
||||||
ndonors[i2] = ndonors[i2] + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -438,5 +507,4 @@ end
|
|||||||
return {
|
return {
|
||||||
flow_routing = flow_routing,
|
flow_routing = flow_routing,
|
||||||
accumulate = accumulate,
|
accumulate = accumulate,
|
||||||
flow_methods = flow_methods,
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user