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}