Skip to main content

banished_terrain/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://tanepiper.github.io/unbanished/assets/un-banished-icon.svg",
4    html_favicon_url = "https://tanepiper.github.io/unbanished/favicon.svg"
5)]
6
7// Original: terrain generation FUN_1400d4860 and its stages (Application-steam-x64.exe).
8// `Generator` mirrors the generation context: the float height grid at +0x08, the record
9// collections at +0x18..+0x48, the Random at +0x58 and the size record pointer at +0xa28. Each
10// stage is checked against emulated ground truth (tools/emu/terrain_stages.py) and saved maps.
11
12mod mesh;
13mod query;
14mod river;
15mod stages;
16mod surface;
17
18pub use mesh::{tile_indices, HeightGrid, TerrainMesh, TerrainVertex, TILE_CELLS, TILE_VERTS};
19pub use query::CellType;
20pub use river::{river_strip, water_mesh, WaterMesh};
21
22use banished_core::random::Random;
23
24// The .crs wire order is +0x00..+0x20, then +0x28..+0x40, then +0x24 (FUN_1400211d0).
25/// The settings of one map size in a terrain profile: 17 values, in the order the game keeps them
26/// in memory. The first value is `n`, half the terrain's size in cells. The resource stores them
27/// in another order; [`SizeRecord::from_wire`] converts.
28///
29/// [`banished_formats::game::TerrainGenerator`] reads a profile, with one size record for each
30/// map size (small, medium, large).
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct SizeRecord(pub [i32; 17]);
33
34impl SizeRecord {
35    /// A size record from the 17 values in the order the resource stores them.
36    pub fn from_wire(w: [i32; 17]) -> SizeRecord {
37        let mut s = [0; 17];
38        s[..9].copy_from_slice(&w[..9]);
39        s[9] = w[16];
40        s[10..].copy_from_slice(&w[9..16]);
41        SizeRecord(s)
42    }
43
44    /// The value at byte offset `off` of the record, as the original reads it.
45    pub fn at(&self, off: usize) -> i32 {
46        self.0[off / 4]
47    }
48
49    /// Half the terrain's size in cells, `n`: 256, 384 or 512. The height grid has `2n + 1`
50    /// samples on each side, and the playable map is the central `n × n` cells.
51    pub fn half_size(&self) -> i32 {
52        self.0[0]
53    }
54}
55
56// 28 bytes; the parameters are FUN_1400d1d20's arguments 4..8.
57/// One point of a river path: a 2D position and five parameters from the size record (for
58/// example the river's width).
59#[derive(Debug, Clone, Copy, PartialEq)]
60pub struct PathPoint {
61    /// The position, in cells from the centre of the terrain.
62    pub pos: [f32; 2],
63    /// The parameters copied from the size record when the path was made.
64    pub params: [f32; 5],
65}
66
67/// The map generator: it makes a terrain from a size record and a seed, in five stages.
68///
69/// ```no_run
70/// # fn record() -> banished_terrain::SizeRecord { unimplemented!() }
71/// use banished_terrain::Generator;
72///
73/// let mut generator = Generator::new(record(), 290);
74/// generator.run(|_stage, _g| {});
75/// let heights = generator.heights(); // as a save stores them
76/// ```
77///
78/// Original: `FUN_1400d4860`.
79pub struct Generator {
80    /// The terrain's size in cells, `2n`. The grid has `(size + 1)²` samples.
81    pub size: i32,
82    /// The float heights, row by row.
83    pub grid: Vec<f32>,
84    /// The river paths. The map draws each one with the River material.
85    pub paths: Vec<Vec<PathPoint>>,
86    /// The hill circles of stage 3 (x, y, radius), including the bridges that join them.
87    pub vec28: Vec<[f32; 3]>,
88    /// The lake points: circles (x, y, radius) around each lake, and its centre.
89    pub points: Vec<[f32; 3]>,
90    /// The lakes (x, y, depth). The map draws them with the Lake material.
91    pub lakes: Vec<[f32; 3]>,
92    /// The random number generator, seeded with the map seed. The stages draw from it in order.
93    pub rng: Random,
94    /// The size record.
95    pub record: SizeRecord,
96}
97
98impl Generator {
99    /// A generator for a size record and a seed, ready to [`run`](Self::run).
100    ///
101    /// Original: `FUN_1400b5700`.
102    pub fn new(record: SizeRecord, seed: u32) -> Generator {
103        Generator {
104            size: record.half_size() * 2,
105            grid: Vec::new(),
106            paths: Vec::new(),
107            vec28: Vec::new(),
108            points: Vec::new(),
109            lakes: Vec::new(),
110            rng: Random::new(seed),
111            record,
112        }
113    }
114
115    /// The number of samples on each side of the grid, `size + 1`.
116    pub fn width(&self) -> usize {
117        (self.size + 1) as usize
118    }
119
120    /// The heights as a save stores them: each float height × 256, as a 16-bit integer.
121    ///
122    /// Original: `FUN_1400b1980`.
123    pub fn heights(&self) -> Vec<i16> {
124        self.grid
125            .iter()
126            .map(|&v| (v * 256.0) as i32 as i16)
127            .collect()
128    }
129
130    /// Run the five stages. `after_stage` is called after each one, with its number (1 to 5).
131    ///
132    /// Original: `FUN_1400d4860`.
133    pub fn run(&mut self, mut after_stage: impl FnMut(u32, &Generator)) {
134        self.size = self.record.half_size() * 2;
135        self.stage1_noise();
136        after_stage(1, self);
137        self.stage2_records();
138        after_stage(2, self);
139        self.stage3_shape();
140        after_stage(3, self);
141        self.stage4_downhill();
142        after_stage(4, self);
143        self.stage5_smooth();
144        after_stage(5, self);
145    }
146}