Skip to main content

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