mirror of
https://gitlab.com/gaelysam/mapgen_rivers.git
synced 2024-11-14 23:00:22 +01:00
74733549df
Now working in pure Lua! Some parts of the code are very hacky (e.g. noise) and the way new and old codes have been glued together is sometimes to be rewritten. But at least it works.
403 lines
7.9 KiB
Lua
403 lines
7.9 KiB
Lua
-- rivermapper.lua
|
|
|
|
local function flow_local_semirandom(plist)
|
|
local sum = 0
|
|
for i=1, #plist do
|
|
sum = sum + plist[i]
|
|
end
|
|
--for _, p in ipairs(plist) do
|
|
--sum = sum + p
|
|
--end
|
|
if sum == 0 then
|
|
return 0
|
|
end
|
|
local r = math.random() * sum
|
|
for i=1, #plist do
|
|
local p = plist[i]
|
|
--for i, p in ipairs(plist) do
|
|
if r < p then
|
|
return i
|
|
end
|
|
r = r - p
|
|
end
|
|
return 0
|
|
end
|
|
|
|
local flow_methods = {
|
|
semirandom = flow_local_semirandom,
|
|
}
|
|
|
|
local function flow_routing(dem, dirs, lakes, method)
|
|
method = method or 'semirandom'
|
|
local flow_local = flow_methods[method] or flow_local_semirandom
|
|
|
|
dirs = dirs or {}
|
|
lakes = lakes or {}
|
|
|
|
-- Localize for performance
|
|
--local tinsert = table.insert
|
|
local tremove = table.remove
|
|
local mmax = math.max
|
|
|
|
local X, Y = dem.X, dem.Y
|
|
dirs.X = X
|
|
dirs.Y = Y
|
|
lakes.X = X
|
|
lakes.Y = Y
|
|
--print(X, Y)
|
|
local i = 1
|
|
local dirs2 = {}
|
|
for i=1, X*Y do
|
|
dirs2[i] = 0
|
|
end
|
|
|
|
local singular = {}
|
|
for y=1, Y do
|
|
for x=1, X do
|
|
local zi = dem[i]
|
|
local plist = {
|
|
x<X and mmax(zi-dem[i+1], 0) or 0, -- Eastward
|
|
y<Y and mmax(zi-dem[i+X], 0) or 0, -- Southward
|
|
x>1 and mmax(zi-dem[i-1], 0) or 0, -- Westward
|
|
y>1 and mmax(zi-dem[i-X], 0) or 0, -- Northward
|
|
}
|
|
|
|
local d = flow_local(plist)
|
|
dirs[i] = d
|
|
if d == 0 then
|
|
singular[#singular+1] = i
|
|
elseif d == 1 then
|
|
dirs2[i+1] = dirs2[i+1] + 1
|
|
elseif d == 2 then
|
|
dirs2[i+X] = dirs2[i+X] + 2
|
|
elseif d == 3 then
|
|
dirs2[i-1] = dirs2[i-1] + 4
|
|
elseif d == 4 then
|
|
dirs2[i-X] = dirs2[i-X] + 8
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
-- Compute basins and links
|
|
local nbasins = #singular
|
|
print(nbasins)
|
|
local basin_id = {}
|
|
local links = {}
|
|
local basin_links
|
|
|
|
local function add_link(i1, i2, b1, isY)
|
|
local b2
|
|
if i2 == 0 then
|
|
b2 = 0
|
|
else
|
|
b2 = basin_id[i2]
|
|
if b2 == 0 then
|
|
return
|
|
end
|
|
end
|
|
if b2 ~= b1 then
|
|
local elev = i2 == 0 and dem[i1] or mmax(dem[i1], dem[i2])
|
|
local l2 = basin_links[b2]
|
|
if not l2 then
|
|
l2 = {}
|
|
basin_links[b2] = l2
|
|
end
|
|
if not l2.elev or l2.elev > elev then
|
|
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
|
|
basin_id[i] = 0
|
|
end
|
|
--for ib, s in ipairs(singular) do
|
|
for ib=1, nbasins do
|
|
--local s = singular[ib]
|
|
local queue = {singular[ib]}
|
|
basin_links = {}
|
|
links[#links+1] = basin_links
|
|
--tinsert(links, basin_links)
|
|
while #queue > 0 do
|
|
local i = tremove(queue)
|
|
basin_id[i] = ib
|
|
local d = dirs2[i]
|
|
|
|
if d >= 8 then -- River coming from South
|
|
d = d - 8
|
|
queue[#queue+1] = i+X
|
|
--tinsert(queue, i+X)
|
|
elseif i <= X*(Y-1) then
|
|
add_link(i, i+X, ib, true)
|
|
else
|
|
add_link(i, 0, ib, true)
|
|
end
|
|
|
|
if d >= 4 then -- River coming from East
|
|
d = d - 4
|
|
queue[#queue+1] = i+1
|
|
--tinsert(queue, i+1)
|
|
elseif i%X > 0 then
|
|
add_link(i, i+1, ib, false)
|
|
else
|
|
add_link(i, 0, ib, false)
|
|
end
|
|
|
|
if d >= 2 then -- River coming from North
|
|
d = d - 2
|
|
queue[#queue+1] = i-X
|
|
--tinsert(queue, i-X)
|
|
elseif i > X then
|
|
add_link(i, i-X, ib, true)
|
|
else
|
|
add_link(i, 0, ib, true)
|
|
end
|
|
|
|
if d >= 1 then -- River coming from West
|
|
queue[#queue+1] = i-1
|
|
--tinsert(queue, i-1)
|
|
elseif i%X ~= 1 then
|
|
add_link(i, i-1, ib, false)
|
|
else
|
|
add_link(i, 0, ib, false)
|
|
end
|
|
end
|
|
end
|
|
dirs2 = nil
|
|
|
|
links[0] = {}
|
|
local nlinks = {}
|
|
for i=0, nbasins do
|
|
nlinks[i] = 0
|
|
end
|
|
|
|
--for ib1, blinks in ipairs(links) do
|
|
for ib1=1, #links do
|
|
for ib2, link in pairs(links[ib1]) do
|
|
if ib2 < ib1 then
|
|
links[ib2][ib1] = link
|
|
nlinks[ib1] = nlinks[ib1] + 1
|
|
nlinks[ib2] = nlinks[ib2] + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local lowlevel = {}
|
|
for i, n in pairs(nlinks) do
|
|
if n <= 8 then
|
|
lowlevel[i] = links[i]
|
|
end
|
|
end
|
|
|
|
local basin_graph = {}
|
|
for n=1, nbasins do
|
|
--print(n, nbasins)
|
|
local b1, lnk1 = next(lowlevel)
|
|
lowlevel[b1] = nil
|
|
|
|
local b2
|
|
local lowest = math.huge
|
|
local lnk1 = links[b1]
|
|
local i = 0
|
|
--print('Scanning basin '..b1)
|
|
for bn, bdata in pairs(lnk1) do
|
|
--print('- Link '..bn)
|
|
i = i + 1
|
|
if bdata.elev < lowest then
|
|
lowest = bdata.elev
|
|
b2 = bn
|
|
end
|
|
end
|
|
--print('Number of links: '..i..' vs '..nlinks[b1])
|
|
|
|
-- Add link to the graph
|
|
local bound = lnk1[b2]
|
|
local bb1, bb2 = bound[1], bound[2]
|
|
if not basin_graph[bb1] then
|
|
basin_graph[bb1] = {}
|
|
end
|
|
if not basin_graph[bb2] then
|
|
basin_graph[bb2] = {}
|
|
end
|
|
basin_graph[bb1][bb2] = bound
|
|
basin_graph[bb2][bb1] = bound
|
|
--if bb1 == 0 then
|
|
-- print(bb2)
|
|
--elseif bb2 == 0 then
|
|
-- print(bb1)
|
|
--end
|
|
|
|
-- Merge basin b1 into b2
|
|
--print("Merging "..b1.." into "..b2)
|
|
local lnk2 = links[b2]
|
|
-- First, remove the link between b1 and b2
|
|
lnk1[b2] = nil
|
|
lnk2[b1] = nil
|
|
nlinks[b2] = nlinks[b2] - 1
|
|
--print('Decreasing link count of '..b2..' ('..nlinks[b2]..')')
|
|
if nlinks[b2] == 8 then
|
|
--print('Added to lowlevel')
|
|
lowlevel[b2] = lnk2
|
|
end
|
|
--print('Scanning neighbourg of '..b1..' to fix links')
|
|
-- 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
|
|
--print('- Neighbour '..bn)
|
|
local lnkn = links[bn]
|
|
lnkn[b1] = nil
|
|
|
|
if lnkn[b2] then
|
|
nlinks[bn] = nlinks[bn] - 1
|
|
--print('Decreasing link count of '..bn..' ('..nlinks[bn]..')')
|
|
if nlinks[bn] == 8 then
|
|
--print('Added to lowlevel')
|
|
lowlevel[bn] = lnkn
|
|
end
|
|
else
|
|
nlinks[b2] = nlinks[b2] + 1
|
|
--print('Increasing link count of '..b2..' ('..nlinks[b2]..')')
|
|
if nlinks[b2] == 9 then
|
|
--print('Removed from lowlevel')
|
|
lowlevel[b2] = nil
|
|
end
|
|
end
|
|
|
|
if not lnkn[b2] or lnkn[b2].elev > bdata.elev then
|
|
--print(' - Redirecting link')
|
|
lnkn[b2] = bdata
|
|
lnk2[bn] = bdata
|
|
end
|
|
end
|
|
end
|
|
|
|
local queue = {[0] = -math.huge}
|
|
local basin_lake = {}
|
|
for n=1, nbasins do
|
|
basin_lake[n] = 0
|
|
end
|
|
local reverse = {3, 4, 1, 2, [0]=0}
|
|
for n=1, nbasins do
|
|
--print(n, nbasins)
|
|
local b1, elev1 = next(queue)
|
|
queue[b1] = nil
|
|
basin_lake[b1] = elev1
|
|
--print('Scanning basin '..b1)
|
|
for b2, bound in pairs(basin_graph[b1]) do
|
|
--print('Flow '..b2..' into '..b1)
|
|
-- Make b2 flow into b1
|
|
local i = bound.i
|
|
local dir = bound.is_y and 4 or 3
|
|
--print(basin_id[i])
|
|
if basin_id[i] ~= b2 then
|
|
dir = dir - 2
|
|
if bound.is_y then
|
|
i = i - X
|
|
else
|
|
i = i - 1
|
|
end
|
|
elseif b1 == 0 then
|
|
dir = 0
|
|
end
|
|
--print(basin_id[i])
|
|
--print('Reversing directions')
|
|
repeat
|
|
dir, dirs[i] = dirs[i], dir
|
|
if dir == 1 then
|
|
i = i + 1
|
|
elseif dir == 2 then
|
|
i = i + X
|
|
elseif dir == 3 then
|
|
i = i - 1
|
|
elseif dir == 4 then
|
|
i = i - X
|
|
end
|
|
dir = reverse[dir]
|
|
until dir == 0
|
|
-- Add b2 into the queue
|
|
queue[b2] = mmax(elev1, bound.elev)
|
|
basin_graph[b2][b1] = nil
|
|
end
|
|
basin_graph[b1] = nil
|
|
end
|
|
|
|
for i=1, X*Y do
|
|
lakes[i] = basin_lake[basin_id[i]]
|
|
end
|
|
|
|
return dirs, lakes
|
|
end
|
|
|
|
local function accumulate(dirs, waterq)
|
|
waterq = waterq or {}
|
|
local X, Y = dirs.X, dirs.Y
|
|
--local tinsert = table.insert
|
|
|
|
local ndonors = {}
|
|
local waterq = {X=X, Y=Y}
|
|
for i=1, X*Y do
|
|
ndonors[i] = 0
|
|
waterq[i] = 1
|
|
end
|
|
|
|
--for i1, dir in ipairs(dirs) do
|
|
for i1=1, X*Y do
|
|
local i2
|
|
local dir = dirs[i1]
|
|
if dir == 1 then
|
|
i2 = i1+1
|
|
elseif dir == 2 then
|
|
i2 = i1+X
|
|
elseif dir == 3 then
|
|
i2 = i1-1
|
|
elseif dir == 4 then
|
|
i2 = i1-X
|
|
end
|
|
if i2 then
|
|
ndonors[i2] = ndonors[i2] + 1
|
|
end
|
|
end
|
|
|
|
for i1=1, X*Y do
|
|
--print(i1, ndonors[i1])
|
|
if ndonors[i1] == 0 then
|
|
local i2 = i1
|
|
local dir = dirs[i2]
|
|
local w = waterq[i2]
|
|
--print(dir)
|
|
while dir > 0 do
|
|
if dir == 1 then
|
|
i2 = i2 + 1
|
|
elseif dir == 2 then
|
|
i2 = i2 + X
|
|
elseif dir == 3 then
|
|
i2 = i2 - 1
|
|
elseif dir == 4 then
|
|
i2 = i2 - X
|
|
end
|
|
--print('Incrementing '..i2)
|
|
w = w + waterq[i2]
|
|
waterq[i2] = w
|
|
|
|
if ndonors[i2] > 1 then
|
|
ndonors[i2] = ndonors[i2] - 1
|
|
break
|
|
end
|
|
dir = dirs[i2]
|
|
end
|
|
end
|
|
end
|
|
|
|
return waterq
|
|
end
|
|
|
|
return {
|
|
flow_routing = flow_routing,
|
|
accumulate = accumulate,
|
|
flow_methods = flow_methods,
|
|
}
|