Skip to main content

banished_terrain/
mesh.rs

1//! The terrain mesh that the renderer draws.
2//!
3//! The map is split into 32×32-cell tiles, each with its own 33×33 vertices; all tiles share one
4//! index list. Vertices are 16 bytes and match the terrain vertex shader: `i16 x, y` (cell
5//! coordinates centred on the map), `i16 height` (the saved `grid × 256` value), `i16 0xff`, `i16
6//! u, v` (map position × 16384, sampling the surface image), and `u8 normal xyz` (`(n + 1) ×
7//! 127.5`) plus a zero byte.
8//!
9//! Original: `FUN_1400b16b0` (the mesh), `FUN_1400b1d50` (a vertex), `FUN_1400b3740` (a normal).
10
11// MapComponent +0xc8
12/// The number of cells on each side of a tile.
13pub const TILE_CELLS: usize = 32;
14// MapComponent +0xcc
15/// The number of vertices on each side of a tile.
16pub const TILE_VERTS: usize = TILE_CELLS + 1;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19#[repr(C)]
20/// One vertex of the terrain mesh, 16 bytes, as the terrain shader reads it.
21pub struct TerrainVertex {
22    /// `x`, `y` (cells from the centre of the terrain), the height (the saved value, height ×
23    /// 256), and `0xff`.
24    pub pos: [i16; 4],
25    /// The position on the map × 16384, to sample the surface image.
26    pub uv: [i16; 2],
27    /// The normal as bytes, `(n + 1) × 127.5`, and a zero byte.
28    pub normal: [u8; 4],
29}
30
31/// The terrain mesh: tiles of [`TILE_CELLS`] × [`TILE_CELLS`] cells, each with its own vertices,
32/// and one index list that every tile uses.
33pub struct TerrainMesh {
34    // MapComponent +0xc0
35    /// The terrain's size in cells, `2n`.
36    pub size: usize,
37    /// The number of tiles on each side.
38    pub tiles_per_side: usize,
39    /// The vertices: `tiles_per_side²` blocks of `TILE_VERTS²` vertices, tile by tile, row by row.
40    pub vertices: Vec<TerrainVertex>,
41    /// Indices for one tile (two triangles per cell).
42    pub indices: Vec<u16>,
43}
44
45/// The heights of a map as a save stores them, for queries and for the mesh: `width²` values, row
46/// by row (see [`Generator::heights`](crate::Generator::heights)).
47pub struct HeightGrid<'a> {
48    /// The number of samples on each side, `2n + 1`.
49    pub width: usize,
50    /// The heights, height × 256.
51    pub heights: &'a [i16],
52}
53
54impl HeightGrid<'_> {
55    fn at(&self, x: i32, y: i32) -> i32 {
56        let hi = self.width as i32 - 1;
57        self.heights[(y.clamp(0, hi) as usize) * self.width + x.clamp(0, hi) as usize] as i32
58    }
59
60    /// Normal from integer central differences.
61    ///
62    /// Original: `FUN_1400b3740`.
63    pub fn normal(&self, x: i32, y: i32) -> [f32; 3] {
64        let scale = -1.0 / 256.0;
65        let v = [
66            (self.at(x + 1, y) - self.at(x - 1, y)) as f32 * scale,
67            (self.at(x, y + 1) - self.at(x, y - 1)) as f32 * scale,
68            2.0,
69        ];
70        let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
71        let inv = 1.0 / len;
72        [v[0] * inv, v[1] * inv, v[2] * inv]
73    }
74
75    /// The vertex at grid point `(x, y)` of a terrain of `size` cells.
76    ///
77    /// Original: `FUN_1400b1d50`.
78    pub fn vertex(&self, x: i32, y: i32, size: i32) -> TerrainVertex {
79        let half = (size >> 1) as i16;
80        let n = self.normal(x, y);
81        let byte = |v: f32| ((v + 1.0) * 127.5) as i64 as u8;
82        TerrainVertex {
83            pos: [x as i16 - half, y as i16 - half, self.at(x, y) as i16, 0xff],
84            uv: [
85                ((x as f32 / size as f32) * 16384.0) as i32 as i16,
86                ((y as f32 / size as f32) * 16384.0) as i32 as i16,
87            ],
88            normal: [byte(n[0]), byte(n[1]), byte(n[2]), 0],
89        }
90    }
91}
92
93impl TerrainMesh {
94    /// The mesh of a terrain of `size` cells.
95    pub fn build(grid: &HeightGrid, size: usize) -> TerrainMesh {
96        let tiles = size / TILE_CELLS;
97        let mut vertices = Vec::with_capacity(tiles * tiles * TILE_VERTS * TILE_VERTS);
98        for ty in 0..tiles {
99            for tx in 0..tiles {
100                for y in 0..TILE_VERTS {
101                    for x in 0..TILE_VERTS {
102                        vertices.push(grid.vertex(
103                            (tx * TILE_CELLS + x) as i32,
104                            (ty * TILE_CELLS + y) as i32,
105                            size as i32,
106                        ));
107                    }
108                }
109            }
110        }
111        TerrainMesh {
112            size,
113            tiles_per_side: tiles,
114            vertices,
115            indices: tile_indices(),
116        }
117    }
118
119    /// The vertices as bytes, ready for a GPU vertex buffer.
120    pub fn vertex_bytes(&self) -> Vec<u8> {
121        let mut out = Vec::with_capacity(self.vertices.len() * 16);
122        for v in &self.vertices {
123            for p in v.pos.iter().chain(&v.uv) {
124                out.extend_from_slice(&p.to_le_bytes());
125            }
126            out.extend_from_slice(&v.normal);
127        }
128        out
129    }
130}
131
132/// Two triangles per cell; the diagonal alternates with `(x + y) & 1`.
133pub fn tile_indices() -> Vec<u16> {
134    let w = TILE_VERTS as u16;
135    let mut out = Vec::with_capacity(TILE_CELLS * TILE_CELLS * 6);
136    for y in 0..TILE_CELLS as u16 {
137        for x in 0..TILE_CELLS as u16 {
138            let a = y * w + x;
139            let b = a + 1;
140            let c = (y + 1) * w + x;
141            if (x + y) & 1 == 0 {
142                out.extend_from_slice(&[a, c, b, b, c, c + 1]);
143            } else {
144                out.extend_from_slice(&[a, c + 1, b, a, c, c + 1]);
145            }
146        }
147    }
148    out
149}