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')
示例#2
0
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)
示例#3
0
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')
示例#5
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 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))