def main(argv): parser = argparse.ArgumentParser( description="Generate ridge-like 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, 'ridge') shape = (512, ) * 2 values = np.zeros(shape) for p in range(1, 10): a = 2**p values += np.abs(noise_octave(shape, a) - 0.5) / a result = (1.0 - util.normalize(values))**2 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): 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): my_dir = os.path.dirname(argv[0]) source_array_dir = os.path.join(my_dir, 'array_files') training_samples_dir = os.path.join(my_dir, 'training_samples') sample_dim = 512 sample_shape = (sample_dim, ) * 2 sample_area = np.prod(sample_shape) # Create the training sample directory, if it doesn't already exist. try: os.mkdir(training_samples_dir) except: pass source_array_paths = [ os.path.join(source_array_dir, path) for path in os.listdir(source_array_dir) ] training_id = 0 for (index, source_array_path) in enumerate(source_array_paths): print('(%d / %d) Created %d samples so far' % (index + 1, len(source_array_paths), training_id)) data = np.load(source_array_path) # Load heightmap and correct for latitude (to an approximation) source_array_raw = data['height'] latitude_deg = (data['minY'] + data['maxY']) / 2 latitude_correction = np.cos(np.radians(latitude_deg)) source_array_shape = (int( np.round(source_array_raw.shape[0] * latitude_correction)), source_array_raw.shape[1]) source_array = cv2.resize(source_array_raw, source_array_shape) # Determine the number of samples to use per source array. sampleable_area = np.subtract(source_array_shape, sample_shape).prod() samples_per_array = int(np.ceil(sampleable_area / sample_area)) if len(source_array.shape) == 0: print('Invalid array at %s' % source_array_path) continue for _ in range(samples_per_array): # Select a sample from the source array. row = np.random.randint(source_array.shape[0] - sample_shape[0]) col = np.random.randint(source_array.shape[1] - sample_shape[1]) sample = source_array[row:(row + sample_shape[0]), col:(col + sample_shape[1])] # Scale and clean the sample sample = clean_sample(sample) # Write the sample to a file if sample is not None: for variant in get_variants(sample): output_path = os.path.join(training_samples_dir, str(training_id) + '.png') util.save_as_png(variant, output_path) training_id += 1
def main(argv): if len(argv) != 3: print('Usage: %s <input_array.np[yz]> <output_image.png>' % (argv[0],)) sys.exit(-1) input_path = argv[1] output_path = argv[2] height, land_mask = util.load_from_file(input_path) util.save_as_png(util.hillshaded(height, land_mask=land_mask), output_path)
def main(argv): parser = argparse.ArgumentParser( description= "Generates a PNG containing the terrain height in grayscale.") parser.add_argument("input_array", help="<input_array.np[yz]> (include file extension)") parser.add_argument("output_image", help="<output_image.png> (include file extension)") args = parser.parse_args() my_dir = os.path.dirname(argv[0]) output_dir = os.path.join(my_dir, 'output') input_path = args.input_array output_path = os.path.join(output_dir, args.output_image) height, _ = util.load_from_file(input_path) util.save_as_png(height, output_path)
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 erosion(terrain, enable_snapshotting=False, dirname='snapshots'): # Grid dimension constants full_width = 200 shape = terrain.shape dim = shape[1] 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(dirname) 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) return terrain
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')