def main(argv): parser = argparse.ArgumentParser( description="Generate domain-warped fBm noise.") parser.add_argument("-o", "--output", help="Output file name (without file \ extension). If not specified then the default file name will be used.") parser.add_argument("--png", action="store_true", help="Automatically save \ a png of the noise.") args = parser.parse_args() my_dir = os.path.dirname(argv[0]) output_dir = os.path.join(my_dir, 'output') if args.output: output_path = os.path.join(output_dir, args.output) else: output_path = os.path.join(output_dir, 'domain_warping') shape = (512,) * 2 values = util.fbm(shape, -2, lower=2.0) offsets = 150 * (util.fbm(shape, -2, lower=1.5) + 1j * util.fbm(shape, -2, lower=1.5)) result = util.sample(values, offsets) np.save(output_path, result) # Optionally save out an image as well. if args.png: util.save_as_png(result, output_path + '_gray.png') util.save_as_png(util.hillshaded(result), output_path + '_hill.png')
def main(argv): shape = (512,) * 2 values = util.fbm(shape, -2, lower=2.0) offsets = 150 * (util.fbm(shape, -2, lower=1.5) + 1j * util.fbm(shape, -2, lower=1.5)) result = util.sample(values, offsets) np.save('domain_warping', result)
def main(argv): dim = 512 shape = (dim, ) * 2 disc_radius = 1.0 max_delta = 0.05 river_downcutting_constant = 1.3 directional_inertia = 0.4 default_water_level = 1.0 evaporation_rate = 0.2 print('Generating...') print(' ...initial terrain shape') land_mask = remove_lakes( (util.fbm(shape, -2, lower=2.0) + bump(shape, 0.2 * dim) - 1.1) > 0) coastal_dropoff = np.tanh(util.dist_to_mask(land_mask) / 80.0) * land_mask mountain_shapes = util.fbm(shape, -2, lower=2.0, upper=np.inf) initial_height = ((util.gaussian_blur( np.maximum(mountain_shapes - 0.40, 0.0), sigma=5.0) + 0.1) * coastal_dropoff) deltas = util.normalize(np.abs(util.gaussian_gradient(initial_height))) print(' ...sampling points') points = util.poisson_disc_sampling(shape, disc_radius) coords = np.floor(points).astype(int) print(' ...delaunay triangulation') tri = sp.spatial.Delaunay(points) (indices, indptr) = tri.vertex_neighbor_vertices neighbors = [indptr[indices[k]:indices[k + 1]] for k in range(len(points))] points_land = land_mask[coords[:, 0], coords[:, 1]] points_deltas = deltas[coords[:, 0], coords[:, 1]] print(' ...initial height map') points_height = compute_height(points, neighbors, points_deltas) print(' ...river network') (upstream, downstream, volume) = compute_river_network(points, neighbors, points_height, points_land, directional_inertia, default_water_level, evaporation_rate) print(' ...final terrain height') new_height = compute_final_height(points, neighbors, points_deltas, volume, upstream, max_delta, river_downcutting_constant) terrain_height = render_triangulation(shape, tri, new_height) np.savez('river_network', height=terrain_height, land_mask=land_mask)
def main(argv): parser = argparse.ArgumentParser( description="Generate fractional Brownian motion (fBm) noise.") parser.add_argument( "-s", "--seed", type=int, help="Noise generator seed. If not specified then a random seed will be \ used. SEED MUST BE AN INTEGER.") parser.add_argument( "-o", "--output", help="Output noise file name (without file extension). If not specified \ then the default file name will be used.") parser.add_argument("--png", action="store_true", help="Automatically save a png of the noise.") args = parser.parse_args() my_dir = os.path.dirname(argv[0]) output_dir = os.path.join(my_dir, 'output') if args.output: output_path = os.path.join(output_dir, args.output) else: output_path = os.path.join(output_dir, 'plain_fbm') shape = (512, ) * 2 if args.seed: input_seed = args.seed else: input_seed = None # Generate the noise. fbm_noise = util.fbm(shape, -2, lower=2.0, seed=input_seed) np.save(output_path, fbm_noise) # Optionally save out an image as well. if args.png: util.save_as_png(fbm_noise, output_path + '_gray.png') util.save_as_png(util.hillshaded(fbm_noise), output_path + '_hill.png')
def main(argv): parser = argparse.ArgumentParser( description="Run a terrain erosion simulation.") group = parser.add_mutually_exclusive_group() group.add_argument( "-f", "--file", help="Run simulation using an input image file instead of generating a \ new fBm noise. (Only works with a square image.) If not specified then \ noise will be generated.") group.add_argument( "-s", "--seed", type=int, help="Noise generator seed. If not specified then a random seed will be \ used. SEED MUST BE AN INTEGER.") parser.add_argument( "-o", "--output", help="Output simulation file name (without file extension). If not \ specified then the default file name will be used.") parser.add_argument("--snapshot", action="store_true", help="Save a numbered image of every iteration.") parser.add_argument("--png", action="store_true", help="Automatically save a png of the simulation.") args = parser.parse_args() my_dir = os.path.dirname(argv[0]) output_dir = os.path.join(my_dir, 'output') try: os.mkdir(output_dir) except: pass if args.output: output_path = os.path.join(output_dir, args.output) else: output_path = os.path.join(output_dir, 'simulation') if args.seed: input_seed = args.seed else: input_seed = None # Grid dimension constants if using fBm noise full_width = 200 dim = 512 shape = [dim] * 2 cell_width = full_width / dim cell_area = cell_width**2 # `terrain` represents the actual terrain height we're interested in if not args.file: terrain = util.fbm(shape, -2.0, seed=input_seed) else: terrain = util.image_to_array(args.file) dim = terrain.shape[0] shape = terrain.shape cell_width = full_width / dim cell_area = cell_width**2 # Snapshotting parameters. Only needed for generating the simulation # timelapse. if args.snapshot: snapshot_dir = os.path.join(output_dir, 'sim_snaps') snapshot_file_template = 'sim-%05d.png' try: os.mkdir(snapshot_dir) except: pass # Water-related constants rain_rate = 0.0008 * cell_area evaporation_rate = 0.0005 # Slope constants min_height_delta = 0.05 repose_slope = 0.03 gravity = 30.0 gradient_sigma = 0.5 # Sediment constants sediment_capacity_constant = 50.0 dissolving_rate = 0.25 deposition_rate = 0.001 # The numer of iterations is proportional to the grid dimension. This is to # allow changes on one side of the grid to affect the other side. iterations = int(1.4 * dim) # `sediment` is the amount of suspended "dirt" in the water. Terrain will be # transfered to/from sediment depending on a number of different factors. sediment = np.zeros_like(terrain) # The amount of water. Responsible for carrying sediment. water = np.zeros_like(terrain) # The water velocity. velocity = np.zeros_like(terrain) # Optionally save the unmodified starting noise if we're not using file input. if args.snapshot and args.png and not args.file: fbm_path = output_path + '_fbm.png' util.save_as_png(terrain, fbm_path) for i in range(0, iterations): print('Iteration: %d / %d' % (i + 1, iterations)) # Set a deterministic seed for our random number generator rng = np.random.default_rng(i) # Add precipitation. This is done via simple uniform random distribution, # although other models use a raindrop model water += rng.random(shape) * rain_rate # Use a different RNG seed for the next step rng = np.random.default_rng(i + 3) # Compute the normalized gradient of the terrain height to determine where # water and sediment will be moving. gradient = np.zeros_like(terrain, dtype='complex') gradient = util.simple_gradient(terrain) gradient = np.select([np.abs(gradient) < 1e-10], [np.exp(2j * np.pi * rng.random(shape))], gradient) gradient /= np.abs(gradient) # Compute the difference between the current height the height offset by # `gradient`. neighbor_height = util.sample(terrain, -gradient) height_delta = terrain - neighbor_height # The sediment capacity represents how much sediment can be suspended in # water. If the sediment exceeds the quantity, then it is deposited, # otherwise terrain is eroded. sediment_capacity = ( (np.maximum(height_delta, min_height_delta) / cell_width) * velocity * water * sediment_capacity_constant) deposited_sediment = np.select( [ height_delta < 0, sediment > sediment_capacity, ], [ np.minimum(height_delta, sediment), deposition_rate * (sediment - sediment_capacity), ], # If sediment <= sediment_capacity dissolving_rate * (sediment - sediment_capacity)) # Don't erode more sediment than the current terrain height. deposited_sediment = np.maximum(-height_delta, deposited_sediment) # Update terrain and sediment quantities. sediment -= deposited_sediment terrain += deposited_sediment sediment = util.displace(sediment, gradient) water = util.displace(water, gradient) # Smooth out steep slopes. terrain = apply_slippage(terrain, repose_slope, cell_width) # Update velocity velocity = gravity * height_delta / cell_width # Apply evaporation water *= 1 - evaporation_rate # Snapshot, if applicable. if args.snapshot: snapshot_path = os.path.join(snapshot_dir, snapshot_file_template % i) util.save_as_png(terrain, snapshot_path) # Normalize terrain values before saving. result = util.normalize(terrain) np.save(output_path, result) # Optionally save out an image as well. if args.png: util.save_as_png(result, output_path + '_gray.png') util.save_as_png(util.hillshaded(result), output_path + '_hill.png')
def noise_octave(shape, f): return util.fbm(shape, -1, lower=f, upper=(2 * f))
def main(argv): shape = (512, ) * 2 np.save('fbm', util.fbm(shape, -2, lower=2.0))
def main(argv): parser = argparse.ArgumentParser( description="Generate terrain from a river network.") parser.add_argument("-o", "--output", help="Output file name (without file extension). If not specified then \ the default file name will be used.") parser.add_argument("--png", action="store_true", help="Automatically save a png of the terrain.") args = parser.parse_args() my_dir = os.path.dirname(argv[0]) output_dir = os.path.join(my_dir, 'output') if args.output: output_path = os.path.join(output_dir, args.output) else: output_path = os.path.join(output_dir, 'river_network') dim = 512 shape = (dim,) * 2 disc_radius = 1.0 max_delta = 0.05 river_downcutting_constant = 1.3 directional_inertia = 0.4 default_water_level = 1.0 evaporation_rate = 0.2 print ('Generating...') print(' ...initial terrain shape') land_mask = remove_lakes( (util.fbm(shape, -2, lower=2.0) + bump(shape, 0.2 * dim) - 1.1) > 0) coastal_dropoff = np.tanh(util.dist_to_mask(land_mask) / 80.0) * land_mask mountain_shapes = util.fbm(shape, -2, lower=2.0, upper=np.inf) initial_height = ( (util.gaussian_blur(np.maximum(mountain_shapes - 0.40, 0.0), sigma=5.0) + 0.1) * coastal_dropoff) deltas = util.normalize(np.abs(util.gaussian_gradient(initial_height))) print(' ...sampling points') points = util.poisson_disc_sampling(shape, disc_radius) coords = np.floor(points).astype(int) print(' ...delaunay triangulation') tri = sp.spatial.Delaunay(points) (indices, indptr) = tri.vertex_neighbor_vertices neighbors = [indptr[indices[k]:indices[k + 1]] for k in range(len(points))] points_land = land_mask[coords[:, 0], coords[:, 1]] points_deltas = deltas[coords[:, 0], coords[:, 1]] print(' ...initial height map') points_height = compute_height(points, neighbors, points_deltas) print(' ...river network') (upstream, downstream, volume) = compute_river_network( points, neighbors, points_height, points_land, directional_inertia, default_water_level, evaporation_rate) print(' ...final terrain height') new_height = compute_final_height( points, neighbors, points_deltas, volume, upstream, max_delta, river_downcutting_constant) terrain_height = render_triangulation(shape, tri, new_height) np.savez(output_path, height=terrain_height, land_mask=land_mask) # Optionally save out an image as well. if args.png: util.save_as_png(terrain_height, output_path + '_gray.png') util.save_as_png(util.hillshaded( terrain_height, land_mask=land_mask), output_path + '_hill.png')
def main(argv): # Grid dimension constants full_width = 200 dim = 512 shape = [dim] * 2 cell_width = full_width / dim cell_area = cell_width ** 2 # Snapshotting parameters. Only needed for generating the simulation # timelapse. enable_snapshotting = False my_dir = os.path.dirname(argv[0]) snapshot_dir = os.path.join(my_dir, 'sim_snaps') snapshot_file_template = 'sim-%05d.png' if enable_snapshotting: try: os.mkdir(snapshot_dir) except: pass # Water-related constants rain_rate = 0.0008 * cell_area evaporation_rate = 0.0005 # Slope constants min_height_delta = 0.05 repose_slope = 0.03 gravity = 30.0 gradient_sigma = 0.5 # Sediment constants sediment_capacity_constant = 50.0 dissolving_rate = 0.25 deposition_rate = 0.001 # The numer of iterations is proportional to the grid dimension. This is to # allow changes on one side of the grid to affect the other side. iterations = int(1.4 * dim) # `terrain` represents the actual terrain height we're interested in terrain = util.fbm(shape, -2.0) # `sediment` is the amount of suspended "dirt" in the water. Terrain will be # transfered to/from sediment depending on a number of different factors. sediment = np.zeros_like(terrain) # The amount of water. Responsible for carrying sediment. water = np.zeros_like(terrain) # The water velocity. velocity = np.zeros_like(terrain) for i in range(0, iterations): print('%d / %d' % (i + 1, iterations)) # Add precipitation. This is done by via simple uniform random distribution, # although other models use a raindrop model water += np.random.rand(*shape) * rain_rate # Compute the normalized gradient of the terrain height to determine where # water and sediment will be moving. gradient = np.zeros_like(terrain, dtype='complex') gradient = util.simple_gradient(terrain) gradient = np.select([np.abs(gradient) < 1e-10], [np.exp(2j * np.pi * np.random.rand(*shape))], gradient) gradient /= np.abs(gradient) # Compute the difference between teh current height the height offset by # `gradient`. neighbor_height = util.sample(terrain, -gradient) height_delta = terrain - neighbor_height # The sediment capacity represents how much sediment can be suspended in # water. If the sediment exceeds the quantity, then it is deposited, # otherwise terrain is eroded. sediment_capacity = ( (np.maximum(height_delta, min_height_delta) / cell_width) * velocity * water * sediment_capacity_constant) deposited_sediment = np.select( [ height_delta < 0, sediment > sediment_capacity, ], [ np.minimum(height_delta, sediment), deposition_rate * (sediment - sediment_capacity), ], # If sediment <= sediment_capacity dissolving_rate * (sediment - sediment_capacity)) # Don't erode more sediment than the current terrain height. deposited_sediment = np.maximum(-height_delta, deposited_sediment) # Update terrain and sediment quantities. sediment -= deposited_sediment terrain += deposited_sediment sediment = util.displace(sediment, gradient) water = util.displace(water, gradient) # Smooth out steep slopes. terrain = apply_slippage(terrain, repose_slope, cell_width) # Update velocity velocity = gravity * height_delta / cell_width # Apply evaporation water *= 1 - evaporation_rate # Snapshot, if applicable. if enable_snapshotting: output_path = os.path.join(snapshot_dir, snapshot_file_template % i) util.save_as_png(terrain, output_path) np.save('simulation', util.normalize(terrain))