Skip to main content

banished_game/world/
floor.rs

1//! Floors: the deck of a bridge part. When it is activated, it counts one more floor on every cell
2//! under it and adds its picking mesh to the map's floor scene; when it is removed, it takes both
3//! back. The ground under a point is on the floor where a cell has one and the floor is above the
4//! terrain.
5//!
6//! The ground queries ask the floors as the original's do ([`crate::town::floor_ground`]): the
7//! characters' walking height, piles, storage drop points, work points, needs, boats' routes,
8//! seeding, fences, bridges and the cell checks. Only the tornado and the natural cell types skip
9//! them, as here.
10//!
11//! Original: `FloorComponent`: `FUN_140124810`, `FUN_140124860`, `FUN_140124770`, `FUN_1400a2070`;
12//! the ground under a point `FUN_1400a48b0`; the callers that skip floors `FUN_140058630`,
13//! `FUN_1400a3790`.
14// vtable 0x140169810
15// +0x2d
16
17use std::collections::BTreeMap;
18
19use crate::terrain::HeightGrid;
20
21/// `FUN_1400a48b0`'s floor ray: from 50 above, straight down, at most 100 long.
22const RAY_TOP: f32 = 50.0;
23const RAY_LENGTH: f32 = 100.0;
24
25// +0x2d; +0x08
26/// The map's floors: per cell how many floors cover it and every floor's mesh with its transform
27/// (the map's picking scene, map).
28#[derive(Debug, Clone, Default)]
29pub struct Floors {
30    /// How many floors cover each cell.
31    pub counts: Vec<u8>,
32    /// Every floor's mesh with its transform, by entity.
33    pub meshes: BTreeMap<usize, Floor>,
34    /// Each floor's world-space footprint (`x0, y0, x1, y1`, widened by [`BOUNDS_MARGIN`]),
35    /// set by [`Floors::add`]. The downward ray can hit a floor only inside it, so the ground
36    /// query skips the others without changing its result. A floor without bounds is tested.
37    bounds: BTreeMap<usize, [f32; 4]>,
38}
39
40/// Slack around a floor's footprint, far above the float error of turning its triangles into
41/// world space.
42const BOUNDS_MARGIN: f32 = 0.05;
43
44/// A bridge's floor: its triangles and the cells it covers.
45#[derive(Debug, Clone, Default)]
46pub struct Floor {
47    /// The MapComponent's rectangle (`x, y, w, h`) the floor counts on.
48    pub rect: (i32, i32, i32, i32),
49    // +0x148
50    /// The entity transform AddMesh keeps for the mesh's root (every shipped floor mesh is one root
51    /// submesh).
52    pub transform: crate::math::Transform,
53    /// Model space: `Picking::Scene::Intersect` takes the ray into the mesh's space.
54    pub triangles: Vec<[[f32; 3]; 3]>,
55}
56
57impl Floor {
58    /// A floor mesh placed as the entity's model (its transform), over the entity's map cells.
59    ///
60    /// Original: `FUN_140124860`.
61    pub fn placed(mesh: &[[[f32; 3]; 3]], p: &crate::nature::seeding::Placed) -> Floor {
62        Floor {
63            rect: (p.cell.0, p.cell.1, p.footprint.0, p.footprint.1),
64            transform: crate::math::Transform::of_model(p.yaw, p.position),
65            triangles: mesh.to_vec(),
66        }
67    }
68}
69
70impl Floors {
71    /// No floors, for a map of `cells` cells.
72    pub fn new(cells: usize) -> Floors {
73        Floors {
74            counts: vec![0; cells],
75            meshes: BTreeMap::new(),
76            bounds: BTreeMap::new(),
77        }
78    }
79
80    /// A floor that was added goes first; every cell of the rectangle counts one more and the mesh
81    /// joins the scene.
82    ///
83    /// Original: `FUN_140124860`.
84    pub fn add(&mut self, size: usize, entity: usize, floor: Floor) {
85        self.remove(size, entity);
86        self.count(size, floor.rect, 1);
87        let mut b = [f32::MAX, f32::MAX, f32::MIN, f32::MIN];
88        for v in floor.triangles.iter().flatten() {
89            let w = floor.transform.apply(*v);
90            b = [
91                b[0].min(w[0]),
92                b[1].min(w[1]),
93                b[2].max(w[0]),
94                b[3].max(w[1]),
95            ];
96        }
97        let m = BOUNDS_MARGIN;
98        self.bounds
99            .insert(entity, [b[0] - m, b[1] - m, b[2] + m, b[3] + m]);
100        self.meshes.insert(entity, floor);
101    }
102
103    /// The rectangle counts one less and the mesh leaves.
104    ///
105    /// Original: `FUN_140124770`, `FUN_1400a2070`.
106    pub fn remove(&mut self, size: usize, entity: usize) {
107        self.bounds.remove(&entity);
108        if let Some(floor) = self.meshes.remove(&entity) {
109            self.count(size, floor.rect, -1);
110        }
111    }
112
113    fn count(&mut self, size: usize, rect: (i32, i32, i32, i32), by: i8) {
114        let n = size as i32;
115        let (x0, y0, w, h) = rect;
116        for y in y0..y0 + h {
117            for x in x0..x0 + w {
118                if let Some(c) = self.counts.get_mut((y * n + x) as usize) {
119                    *c = c.wrapping_add_signed(by);
120                }
121            }
122        }
123    }
124
125    /// The ground under `(x, y)`: the terrain, or, on a cell with a floor, the nearest floor that
126    /// a ray from 50 above hits, when it is above the terrain.
127    ///
128    /// Original: `FUN_1400a48b0(point, skip floors = false)`.
129    pub fn ground(&self, size: usize, terrain: &HeightGrid, x: f32, y: f32) -> f32 {
130        self.ground_normal(size, terrain, x, y, &mut [0.0; 3])
131    }
132
133    /// As [`Floors::ground`], with the normal there (`param_5`): the terrain's, or on a floor
134    /// the hit triangle's turned back into the map (`Intersect`'s last accepted hit).
135    pub fn ground_normal(
136        &self,
137        size: usize,
138        terrain: &HeightGrid,
139        x: f32,
140        y: f32,
141        normal: &mut [f32; 3],
142    ) -> f32 {
143        let mut terrain_normal = [0.0; 3];
144        let ground = terrain.ground(x, y, &mut terrain_normal);
145        let n = size as i32;
146        let half = (n >> 1) as f32;
147        let cx = ((x + half) as i32).clamp(0, n - 1);
148        let cy = ((y + half) as i32).clamp(0, n - 1);
149        if self
150            .counts
151            .get((cy * n + cx) as usize)
152            .copied()
153            .unwrap_or(0) as i8
154            > 0
155        {
156            let origin = [x, y, RAY_TOP];
157            let direction = [0.0, 0.0, -1.0];
158            let mut nearest = RAY_LENGTH;
159            let mut hit = None;
160            for (id, floor) in &self.meshes {
161                if let Some(b) = self.bounds.get(id) {
162                    if x < b[0] || y < b[1] || b[2] < x || b[3] < y {
163                        continue;
164                    }
165                }
166                // `Picking::Scene::Intersect`: the ray in the mesh's space
167                // (`Transform::Inverse`); the distance holds, the rotation keeping the
168                // direction's length.
169                let inverse = floor.transform.inverse();
170                let (o, dir) = (inverse.apply(origin), inverse.rotate(direction));
171                for &t in &floor.triangles {
172                    if let Some(d) = crate::picking::triangle(o, dir, t, nearest) {
173                        nearest = d;
174                        hit = Some((d, floor.transform.rotate(crate::picking::face_normal(t))));
175                    }
176                }
177            }
178            if let Some((d, n)) = hit {
179                let z = crate::picking::point(origin, direction, d)[2];
180                if ground < z {
181                    *normal = n;
182                    return z;
183                }
184            }
185        }
186        *normal = terrain_normal;
187        ground
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    /// A deck 3 above flat terrain over one cell: that cell's ground is the deck, its
196    /// neighbour's the terrain, and a floor below the terrain is ignored.
197    #[test]
198    fn the_ground_stands_on_a_floor_above_the_terrain() {
199        let size = 8;
200        let heights = vec![0i16; (size + 1) * (size + 1)];
201        let terrain = HeightGrid {
202            width: size + 1,
203            heights: &heights,
204        };
205        let mut floors = Floors::new(size * size);
206        // Cell (4, 4) spans −0..1 around the map centre: x, y in [0, 1].
207        let deck = |z: f32| Floor {
208            rect: (4, 4, 1, 1),
209            transform: crate::math::Transform::of_model(0.0, [0.0; 3]),
210            triangles: vec![
211                [[0.0, 0.0, z], [1.0, 0.0, z], [1.0, 1.0, z]],
212                [[0.0, 0.0, z], [1.0, 1.0, z], [0.0, 1.0, z]],
213            ],
214        };
215        floors.add(size, 0, deck(3.0));
216        let on = crate::town::floor_ground(&floors, size, &terrain, 4 * 8 + 4);
217        let off = crate::town::floor_ground(&floors, size, &terrain, 4 * 8 + 3);
218        assert!((on[2] - 3.0).abs() < 1e-4, "{on:?}");
219        assert!(off[2].abs() < 1e-4, "{off:?}");
220        floors.add(size, 0, deck(-3.0));
221        let below = crate::town::floor_ground(&floors, size, &terrain, 4 * 8 + 4);
222        assert!(below[2].abs() < 1e-4, "{below:?}");
223    }
224}