#terrain #procedural-generation #model #evolution #graph #simulating #simulation

fastlem

A Rust library for generating procedural terrains based on simplified landscape evolution model (LEM)

4 releases

0.1.4 Jan 26, 2024
0.1.3 Jan 24, 2024
0.1.2 Dec 6, 2023
0.1.0 Nov 10, 2023

#234 in Algorithms


Used in street-engine

MPL-2.0 license

1MB
911 lines

fastlem

terrain

Crates.io Documentation

fastlem [/ˈfæstlɛm/] is a rust library that provides methods for simulating landscape evolution processes to create realistic terrain data with plausible relief.

Given a planar graph with topographic parameters assigned to each node, fastlem runs simulations and assigns elevations to the graph as output.

The fastlem web simulator is available to try out manually creating virtual terrain with fastlem. fastlemweb2

Installation

[!WARNING] This project is currently under development. During 0.1.* the interface may change a lot.

It is recommended to specify the version in detail.

[dependencies]
fastlem = "0.1.4"

About the simulation

fastlem follows the Salève model[1], which is an analytical model for simulating landscape evolution in 2D. The algorithm for creating the drainage network follows the method of Guillaume Cordonnier, Jean Braun, Marie-Paule Cani, Bedrich Benes, Eric Galin, et al[2].

Build a graph

Terrain data is represented as a planar graph. This graph structure is used to compute all topographic simulations for terrain generation.

For fastlem with 2D model, TerrainModel2DBulider is required to construct the graph from given sites as TerrainModel2D by computing a Delaunay triangulation. You can optionally use relaxate_sites to move the sites to approximately equal positions using Lloyd's algorithm.

let model = TerrainModel2DBulider::from_random_sites(num, bound_min, bound_max) // generate sites randomly
    .relaxate_sites(1) // relaxate sites using Lloyd's algorithm
    .unwrap()
    .build() // build
    .unwrap();

Create terrain generator

Before starting terrain generation, TerrainGenerator is required to use the planer graph and assign the topographic parameters to each site.

let terrain_generator = TerrainGenerator::default()
    .set_model(model)
    .set_parameters(
        (0..num)
            .map(|_| TopographicalParameters::default().set_erodibility(1.0))
            .collect::<_>(),
    );
  • is_outlet is whether the site can be an outlet (including oceans) or not. If is_outlet, the elevation is always set to 0.0.
  • erodibility is the erodibility of the site. This is the main parameter that determines the shape of the terrain.

Generate terrain

let terrain = terrain_generator.generate().unwrap();

Terrains are generated by calling TerrainGenerator::generate().

Get elevations

In general you can get the sites and elevations as vectors by calling [Terrain]::sites() and [Terrain]::elevations().

The indices of each vector correspond to each other.

let sites = terrain.sites();
let elevations = terrain.elevations();
// print
for i in 0..sites.len() {
    println!(
        "Site: ({}, {}), Elevation: {}",
        sites[i].x, sites[i].y, elevations[i]
    );
}

You can also query the elevation of a specific site in the 2D surface model by calling Terrain2D::get_elevation(Site2D).

let site = Site2D { x, y };
let elevation = terrain.get_elevation(&site);

Note that this method uses natural neighbour interpolation (with the naturalneighbor package) to interpolate the specific elevation between sites and takes approximately O(logN).

Below is an example of using get_elevation to create an image of the generated terrain:

let img_width = 500;
let img_height = 500;

let mut image_buf = image::RgbImage::new(img_width, img_height);
// calculate the max elevation
let max_elevation = terrain
    .elevations()
    .iter()
    .fold(std::f64::MIN, |acc, &n| n.max(acc));

for imgx in 0..img_width {
    for imgy in 0..img_height {
        let x = bound_max.x * (imgx as f64 / img_width as f64);
        let y = bound_max.y * (imgy as f64 / img_height as f64);
        let site = Site2D { x, y };
        let elevation = terrain.get_elevation(&site);
        if let Some(elevation) = elevation {
            let color = ((elevation / max_elevation) * 255.0) as u8;
            // put grayscale pixel
            image_buf.put_pixel(imgx as u32, imgy as u32, image::Rgb([color, color, color]));
        }
    }
}

image_buf.save("image.png").unwrap();

Reference

[1] Steer, P.: Short communication: Analytical models for 2D landscape evolution, Earth Surf. Dynam., 9, 1239–1250, https://doi.org/10.5194/esurf-9-1239-2021, 2021.

[2] Guillaume Cordonnier, Jean Braun, Marie-Paule Cani, Bedrich Benes, Eric Galin, et al.. Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion. Computer Graphics Forum, 2016, Proc. EUROGRAPHICS 2016, 35 (2), pp.165-175. ⟨10.1111/cgf.12820⟩. ⟨hal-01262376⟩

Contributing

Contributions are welcome. Feel free to open an issue or pull request if you have any problems or suggestions.

Documentation

The API reference is available for this project.

The examples are also useful. Reading the landscape_evolution.rs is recommended as a first step.

License

MPL-2.0

Preview of Examples

Simple Landscape Evolution Simple Terrain Generation
Simple Landscape Evolution Simple Terrain Generation
$ cargo run --example landscape_evolution --release $ cargo run --example terrain_generation --release
Advanced Terrain Generation Terrain Generation from Given Parameters
Advanced Terrain Generation Terrain Generation from Given Parameters
$ cargo run --example terrain_generation_advanced --release $ cargo run --example sample_terrain --release

Dependencies

~4MB
~57K SLoC