terrain_generator/lib.rs
1//! Procedural terrain generation with fractal noise, hydraulic erosion and
2//! river extraction.
3//!
4//! The pipeline runs in five stages; everything after the first is
5//! optional:
6//!
7//! 1. **Base heightmap** — fractal (fBm) OpenSimplex noise via the
8//! [`noise`] crate.
9//! 2. **Land mask** — either a single radial island
10//! ([`IslandParams`]) or an archipelago of several
11//! ([`ArchipelagoParams`]), both with noise-perturbed coastlines.
12//! 3. **Hydraulic erosion** — a particle/droplet simulation that carves
13//! valleys and drainage networks into the terrain.
14//! 4. **Basin breaching** *(opt-in)* — cuts outlets through the rims of
15//! oversized closed basins so they drain instead of becoming inland
16//! seas. See [`TerrainBuilder::breach_basins`].
17//! 5. **Hydrology** — depressions are filled (priority-flood), then D8
18//! flow routing; cells with enough upstream catchment become rivers,
19//! filled depressions become lakes, and border-connected sea becomes
20//! the [`ocean`](Terrain::ocean) mask.
21//!
22//! # Quick start
23//!
24//! ```no_run
25//! use terrain_generator::{RenderStyle, Terrain};
26//!
27//! let terrain = Terrain::builder()
28//! .size(512, 512)
29//! .seed(42)
30//! .generate();
31//!
32//! terrain.save_png("map.png", RenderStyle::Shaded).unwrap();
33//!
34//! // raw data access for games/simulations:
35//! let h = terrain.heightmap[(100, 100)]; // elevation in [0, 1]
36//! let wet = terrain.flow_at(100, 100); // moisture proxy in [0, 1]
37//! let catchment = terrain.flow_accumulation_at(100, 100); // cells upstream
38//! let river = terrain.is_river(100, 100);
39//! # let _ = (h, wet, catchment, river);
40//! ```
41//!
42//! # What this crate does and does not decide
43//!
44//! The output is a set of raw, independent layers: elevation, flow
45//! accumulation, and river/lake/ocean masks. It deliberately stops short
46//! of gameplay or domain semantics — there is no "drinkable", no biome
47//! enum, no navigability. Anything that is a judgement call is either a
48//! parameter (how dense are rivers? how big may a lake be?) or derivable
49//! from the raw layers, so a consumer can substitute its own rules.
50//!
51//! Consumers that bring their own hydrology should use
52//! [`TerrainBuilder::no_rivers`], which skips flow routing entirely and
53//! leaves [`Terrain::heightmap`] as the single source of truth.
54//!
55//! # Data model
56//!
57//! All per-cell outputs are [`Grid`]s: dense row-major buffers indexed as
58//! `grid[(x, y)]`, with the flat storage available via [`Grid::as_slice`]
59//! for zero-copy hand-off to FFI, GPUs or file formats.
60//!
61//! # Determinism
62//!
63//! The same seed and parameters produce the same terrain, run after run.
64//! Ordering that could otherwise vary — the flow-routing sort and both
65//! priority queues — is broken by cell index, so no result depends on
66//! sort or heap internals.
67//!
68//! Two caveats worth knowing before you persist a world as a seed:
69//!
70//! * **Pin the version.** The base noise comes from the [`noise`] crate,
71//! and tuning inside this crate also changes output. If you regenerate
72//! terrain from a stored seed, depend on an exact tag or revision and
73//! commit your `Cargo.lock`; treat any change to the generated output as
74//! a breaking change on your side.
75//! * **`flow` uses a transcendental.** [`Terrain::flow`] is log-normalised
76//! with `f32::ln`, whose last-bit rounding is not guaranteed identical
77//! across platforms and libm implementations. Every other layer —
78//! heightmap, [`flow_accumulation`](Terrain::flow_accumulation), rivers,
79//! lakes, ocean — is computed with IEEE-exact operations only
80//! (`+ - * /`, `sqrt`, comparisons), so those are reproducible
81//! bit-for-bit everywhere. If you need cross-platform exactness for
82//! moisture, derive it from `flow_accumulation` yourself.
83//!
84//! # Cargo features
85//!
86//! * `png` *(default)* — PNG output ([`Terrain::save_png`],
87//! [`Terrain::save_layer_png`]); pulls in the
88//! [`png`](https://crates.io/crates/png) crate. [`Terrain::to_rgb`]
89//! works without it.
90//! * `cli` *(default)* — the `terrain-generator` command-line binary;
91//! pulls in `clap` and implies `png`. Library consumers should use
92//! `default-features = false`, which leaves `noise` as the only
93//! dependency.
94
95mod base;
96mod breach;
97mod erosion;
98mod grid;
99mod hydrology;
100mod render;
101mod rng;
102mod terrain;
103
104pub use grid::Grid;
105pub use hydrology::ocean_mask;
106#[cfg(feature = "png")]
107pub use render::RenderError;
108pub use render::{Layer, RenderStyle, UnknownLayer};
109pub use terrain::{
110 ArchipelagoParams, BreachParams, ErosionParams, IslandParams, NoiseParams, ParamError,
111 RiverParams, RiverThreshold, Terrain, TerrainBuilder, MAX_CELLS,
112};