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')
Example #3
0
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')
Example #7
0
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')