diff --git a/terrainlib_lua/erosion.lua b/terrainlib_lua/erosion.lua index 355249d..446547c 100644 --- a/terrainlib_lua/erosion.lua +++ b/terrainlib_lua/erosion.lua @@ -134,7 +134,7 @@ local rivermapper = dofile(modpath .. "rivermapper.lua") local gaussian = dofile(modpath .. "gaussian.lua") 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) end diff --git a/terrainlib_lua/rivermapper.lua b/terrainlib_lua/rivermapper.lua index 5ccbf5e..0b1fe4c 100644 --- a/terrainlib_lua/rivermapper.lua +++ b/terrainlib_lua/rivermapper.lua @@ -10,50 +10,18 @@ -- 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. --- 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. -- 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. - method = method or 'semirandom' - local flow_local = flow_methods[method] or flow_local_semirandom - +local function flow_routing(dem, dirs, lakes) -- 'dirs' and 'lakes' are optional tables to reuse for memory optimization, they may contain any data. dirs = dirs or {} lakes = lakes or {} -- Localize for performance local tremove = table.remove local mmax = math.max + local mrand = math.random local X, Y = dem.X, dem.Y 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 x=1, X do local zi = dem[i] - local plist = { -- Get the height difference of the 4 neighbours (and 0 if uphill) - y1 and mmax(zi-dem[i-X], 0) or 0, -- Northward - x>1 and mmax(zi-dem[i-1], 0) or 0, -- Westward - } + -- Determine 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, with probability depending 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. + local pSouth = y1 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 -- 'dirs2': Directions from which water comes dirs[i] = d @@ -438,5 +421,4 @@ end return { flow_routing = flow_routing, accumulate = accumulate, - flow_methods = flow_methods, }