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}