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}