6 Commits

Author SHA1 Message Date
462942cc22 Use iterative finite differences for diffusion, instead of gaussian blur
Allows variable diffusion coefficients
2020-12-27 13:46:25 +01:00
1b96f52e47 Decrease variations of parameter m 2020-12-27 13:45:24 +01:00
3ccb6932ad Implement simple tectonics.
Uplift and subsidence are determined with a noise, at every iteration.
There is no distinctive pattern like tectonic plates, just vertical movements
disturbing rivers from their equilibrium state, and thus creating more diversity.
More lakes and waterfalls especially.
2020-12-24 15:35:27 +01:00
32f3cd9925 Fixed 3D noise map, and removed catchment_reference
Now K and m are determined independently.
Alsoo removed debug plotting.
2020-12-24 15:33:03 +01:00
ae46ada648 Use a class for noise map generation, and add a function for 3D slice of a 2D noise. 2020-12-24 15:33:03 +01:00
4b1d11dd73 Implement variable K and m erosion parameters
For now noise parameters are hardcoded.
2020-12-24 15:30:35 +01:00
2 changed files with 106 additions and 26 deletions

View File

@ -1,30 +1,54 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import numpy as np import numpy as np
from noise import snoise2 from noise import snoise2, snoise3
import os import os
import sys import sys
import terrainlib import terrainlib
def noisemap(X, Y, scale=0.01, vscale=1.0, offset=0.0, log=False, **params): class noisemap:
# Determine noise offset randomly def __init__(self, X, Y, scale=0.01, vscale=1.0, tscale=1.0, offset=0.0, log=None, xbase=None, ybase=None, **params):
xbase = np.random.randint(8192)-4096 # Determine noise offset randomly
ybase = np.random.randint(8192)-4096 if xbase is None:
xbase = np.random.randint(8192)-4096
if ybase is None:
ybase = np.random.randint(8192)-4096
self.xbase = xbase
self.ybase = ybase
self.X = X
self.Y = Y
self.scale = scale
if log:
vscale /= offset
self.vscale = vscale
self.tscale = tscale
self.offset = offset
self.log = log
self.params = params
if log: def get2d(self):
vscale /= offset n = np.zeros((self.X, self.Y))
for x in range(self.X):
for y in range(self.Y):
n[x,y] = snoise2(x/self.scale + self.xbase, y/self.scale + self.ybase, **self.params)
# Generate the noise if self.log:
n = np.zeros((X, Y)) return np.exp(n*self.vscale) * self.offset
for x in range(X): else:
for y in range(Y): return n*self.vscale + self.offset
n[x,y] = snoise2(x/scale + xbase, y/scale + ybase, **params)
if log: def get3d(self, t=0):
return np.exp(n*vscale) * offset t /= self.tscale
else: n = np.zeros((self.X, self.Y))
return n*vscale + offset for x in range(self.X):
for y in range(self.Y):
n[x,y] = snoise3(x/self.scale + self.xbase, y/self.scale + self.ybase, t, **self.params)
if self.log:
return np.exp(n*self.vscale) * self.offset
else:
return n*self.vscale + self.offset
### PARSE COMMAND-LINE ARGUMENTS ### PARSE COMMAND-LINE ARGUMENTS
argc = len(sys.argv) argc = len(sys.argv)
@ -87,6 +111,7 @@ sea_level = float(get_setting('sea_level', 0.0))
sea_level_variations = float(get_setting('sea_level_variations', 0.0)) sea_level_variations = float(get_setting('sea_level_variations', 0.0))
sea_level_variations_time = float(get_setting('sea_level_variations_time', 1.0)) sea_level_variations_time = float(get_setting('sea_level_variations_time', 1.0))
flex_radius = float(get_setting('flex_radius', 20.0)) flex_radius = float(get_setting('flex_radius', 20.0))
tectonics_time = float(get_setting('tectonics_time', 0.0))
flow_method = get_setting('flow_method', 'semirandom') flow_method = get_setting('flow_method', 'semirandom')
time = float(get_setting('time', 10.0)) time = float(get_setting('time', 10.0))
@ -111,17 +136,43 @@ params_sealevel = {
"lacunarity" : 2, "lacunarity" : 2,
} }
params_K = {
"offset" : K,
"vscale" : K,
"scale" : 400,
"octaves" : 1,
"persistence" : 0.5,
"lacunarity" : 2,
"log" : True,
}
params_m = {
"offset" : m,
"vscale" : m*0.2,
"scale" : 400,
"octaves" : 1,
"persistence" : 0.5,
"lacunarity" : 2,
"log" : False,
}
if sea_level_variations != 0.0: if sea_level_variations != 0.0:
sea_ybase = np.random.randint(8192)-4096 sea_ybase = np.random.randint(8192)-4096
sea_level_ref = snoise2(time * (1-1/niter) / sea_level_variations, sea_ybase, **params_sealevel) * sea_level_variations sea_level_ref = snoise2(time * (1-1/niter) / sea_level_variations, sea_ybase, **params_sealevel) * sea_level_variations
params['offset'] -= (sea_level_ref + sea_level) params['offset'] -= (sea_level_ref + sea_level)
n = noisemap(mapsize+1, mapsize+1, **params) if tectonics_time == 0.0:
n = noisemap(mapsize+1, mapsize+1, **params).get2d()
else:
terrain_noisemap = noisemap(mapsize+1, mapsize+1, tscale=tectonics_time, **params)
n = terrain_noisemap.get3d()
m_map = noisemap(mapsize+1, mapsize+1, **params_m).get2d()
K_map = noisemap(mapsize+1, mapsize+1, **params_K).get2d()
### COMPUTE LANDSCAPE EVOLUTION ### COMPUTE LANDSCAPE EVOLUTION
# Initialize landscape evolution model # Initialize landscape evolution model
print('Initializing model') print('Initializing model')
model = terrainlib.EvolutionModel(n, K=K, m=m, d=d, sea_level=sea_level, flex_radius=flex_radius, flow_method=flow_method) model = terrainlib.EvolutionModel(n, K=K_map, m=m_map, d=K_map, sea_level=sea_level, flex_radius=flex_radius, flow_method=flow_method)
terrainlib.update(model.dem, model.lakes, t=5, sea_level=model.sea_level, title='Initializing...') terrainlib.update(model.dem, model.lakes, t=5, sea_level=model.sea_level, title='Initializing...')
dt = time/niter dt = time/niter
@ -141,6 +192,9 @@ for i in range(niter):
terrainlib.update(model.dem, model.lakes, sea_level=model.sea_level, title=disp_niter) terrainlib.update(model.dem, model.lakes, sea_level=model.sea_level, title=disp_niter)
print('Advection') print('Advection')
model.advection(dt) model.advection(dt)
if tectonics_time != 0.0:
print('Isostasy reference redefinition')
model.define_isostasy(terrain_noisemap.get3d((i+1)*dt))
print('Isostatic equilibration') print('Isostatic equilibration')
model.adjust_isostasy() model.adjust_isostasy()

View File

@ -1,5 +1,6 @@
import numpy as np import numpy as np
import scipy.ndimage as im import scipy.ndimage as im
import scipy.signal as si
from .rivermapper import flow from .rivermapper import flow
def advection(dem, dirs, rivers, time, K=1, m=0.5, sea_level=0): def advection(dem, dirs, rivers, time, K=1, m=0.5, sea_level=0):
@ -47,11 +48,31 @@ def advection(dem, dirs, rivers, time, K=1, m=0.5, sea_level=0):
return dem_new return dem_new
def diffusion(dem, time, d=1): second_derivative_matrix = np.array([
radius = d * time**.5 [0., 0.25, 0.],
if radius == 0: [0.25,-1., 0.25],
return dem [0., 0.25, 0.],
return im.gaussian_filter(dem, radius, mode='reflect') # Diffusive erosion is a simple Gaussian blur ])
diff_max = 1.0
def diffusion(dem, time, d=1.0):
if isinstance(d, np.ndarray):
dmax = d.max()
else:
dmax = d
diff = time * dmax
print(diff)
niter = int(diff//diff_max) + 1
ddiff = d * (time / niter)
#print('{:d} iterations'.format(niter))
for i in range(niter):
dem[1:-1,1:-1] += si.convolve2d(dem, second_derivative_matrix, mode='valid') * ddiff
#print('iteration {:d}'.format(i+1))
return dem
#return im.gaussian_filter(dem, radius, mode='reflect') # Diffusive erosion is a simple Gaussian blur
class EvolutionModel: class EvolutionModel:
def __init__(self, dem, K=1, m=0.5, d=1, sea_level=0, flow=False, flex_radius=100, flow_method='semirandom'): def __init__(self, dem, K=1, m=0.5, d=1, sea_level=0, flow=False, flex_radius=100, flow_method='semirandom'):
@ -59,7 +80,10 @@ class EvolutionModel:
#self.bedrock = dem #self.bedrock = dem
self.K = K self.K = K
self.m = m self.m = m
self.d = d if isinstance(d, np.ndarray):
self.d = d[1:-1,1:-1]
else:
self.d = d
self.sea_level = sea_level self.sea_level = sea_level
self.flex_radius = flex_radius self.flex_radius = flex_radius
self.define_isostasy() self.define_isostasy()
@ -86,8 +110,10 @@ class EvolutionModel:
self.dem = diffusion(self.dem, time, d=self.d) self.dem = diffusion(self.dem, time, d=self.d)
self.flow_uptodate = False self.flow_uptodate = False
def define_isostasy(self): def define_isostasy(self, dem=None):
self.ref_isostasy = im.gaussian_filter(self.dem, self.flex_radius, mode='reflect') # Define a blurred version of the DEM that will be considered as the reference isostatic elevation. if dem is None:
dem = self.dem
self.ref_isostasy = im.gaussian_filter(dem, self.flex_radius, mode='reflect') # Define a blurred version of the DEM that will be considered as the reference isostatic elevation.
def adjust_isostasy(self, rate=1): def adjust_isostasy(self, rate=1):
isostasy = im.gaussian_filter(self.dem, self.flex_radius, mode='reflect') # Calculate blurred DEM isostasy = im.gaussian_filter(self.dem, self.flex_radius, mode='reflect') # Calculate blurred DEM