waymark_tilecache/
tile_cache_integration.rs1use super::tile_cache::{Obstacle, TileCache, TileCacheEntry};
7use super::tile_cache_builder::TileCacheBuilder;
8use super::tile_cache_data::{TileCacheBuilderConfig, TileCacheLayer};
9use crate::error::TileCacheError;
10use waymark::PolyRef;
11use waymark::nav_mesh::{MeshTile, NavMesh};
12
13#[derive(Debug)]
15pub struct TileCacheNavMeshIntegration {
16 builder_config: TileCacheBuilderConfig,
18 builder: TileCacheBuilder,
20}
21
22impl TileCacheNavMeshIntegration {
23 pub fn new(builder_config: TileCacheBuilderConfig) -> Self {
25 let builder = TileCacheBuilder::new(builder_config.clone());
26
27 Self {
28 builder_config,
29 builder,
30 }
31 }
32
33 pub fn build_nav_mesh_tile_from_layer(
35 &self,
36 tile_cache: &TileCache,
37 nav_mesh: &mut NavMesh,
38 tile_layer: &TileCacheLayer,
39 tile_idx: usize,
40 ) -> Result<(), TileCacheError> {
41 let tile_entry = tile_cache
43 .get_tile(tile_idx)
44 .ok_or(TileCacheError::InvalidParam)?;
45
46 let obstacles = self.collect_obstacles_for_tile(tile_cache, tile_entry);
48
49 let mesh_tile = self
51 .builder
52 .build_tile_from_layer(tile_layer, &obstacles, tile_cache)?;
53
54 let tile_ref = self.get_tile_ref_for_position(
56 nav_mesh,
57 tile_entry.header.x(),
58 tile_entry.header.y(),
59 tile_entry.header.layer(),
60 );
61
62 if let Some(existing_ref) = tile_ref {
63 nav_mesh
64 .remove_tile(existing_ref)
65 .map_err(|_| TileCacheError::Detour(waymark::DetourError::Failure))?;
66 }
67
68 self.add_mesh_tile_to_nav_mesh(nav_mesh, mesh_tile)?;
70
71 Ok(())
72 }
73
74 pub fn rebuild_tile_in_nav_mesh(
76 &self,
77 tile_cache: &TileCache,
78 nav_mesh: &mut NavMesh,
79 tile_idx: usize,
80 ) -> Result<Option<PolyRef>, TileCacheError> {
81 let tile_entry = tile_cache
83 .get_tile(tile_idx)
84 .ok_or(TileCacheError::InvalidParam)?;
85
86 let compressed_data = tile_cache
88 .get_tile_compressed_data(tile_idx)
89 .ok_or(TileCacheError::InvalidParam)?;
90
91 let tile_data = tile_cache.decompress_tile(compressed_data, None)?;
93
94 let tile_layer = TileCacheLayer::from_bytes(&tile_data)?;
96
97 let obstacles = self.collect_obstacles_for_tile(tile_cache, tile_entry);
99
100 let mesh_tile = self
102 .builder
103 .build_tile_from_layer(&tile_layer, &obstacles, tile_cache)?;
104
105 let tile_ref = self.get_tile_ref_for_position(
107 nav_mesh,
108 tile_entry.header.x(),
109 tile_entry.header.y(),
110 tile_entry.header.layer(),
111 );
112
113 if let Some(existing_ref) = tile_ref {
114 nav_mesh
115 .remove_tile(existing_ref)
116 .map_err(|_| TileCacheError::Detour(waymark::DetourError::Failure))?;
117 }
118
119 let new_tile_ref = self.add_mesh_tile_to_nav_mesh(nav_mesh, mesh_tile)?;
121
122 Ok(Some(new_tile_ref))
123 }
124
125 fn add_mesh_tile_to_nav_mesh(
127 &self,
128 nav_mesh: &mut NavMesh,
129 mesh_tile: MeshTile,
130 ) -> Result<PolyRef, TileCacheError> {
131 let tile_idx = self.find_free_tile_slot(nav_mesh)?;
133
134 self.set_tile_at_index(nav_mesh, tile_idx, mesh_tile)?;
136
137 let tile_ref = self.encode_tile_ref(tile_idx as u32, 1); Ok(tile_ref)
141 }
142
143 fn find_free_tile_slot(&self, nav_mesh: &NavMesh) -> Result<usize, TileCacheError> {
145 let all_tiles = nav_mesh.get_all_tiles();
146 let max_tiles = nav_mesh.get_max_tiles() as usize;
147
148 if all_tiles.len() < max_tiles {
152 Ok(all_tiles.len())
153 } else {
154 Err(TileCacheError::OutOfMemory { resource: "tiles" })
155 }
156 }
157
158 fn set_tile_at_index(
160 &self,
161 nav_mesh: &mut NavMesh,
162 tile_idx: usize,
163 mesh_tile: MeshTile,
164 ) -> Result<(), TileCacheError> {
165 nav_mesh
166 .set_tile_at_index(tile_idx, mesh_tile)
167 .map_err(|_| TileCacheError::Detour(waymark::DetourError::Failure))
168 }
169
170 fn encode_tile_ref(&self, tile_idx: u32, salt: u32) -> PolyRef {
172 PolyRef::new(((salt & 0xFFFF) << 16) | (tile_idx & 0xFFFF))
174 }
175
176 fn get_tile_ref_for_position(
178 &self,
179 nav_mesh: &NavMesh,
180 x: i32,
181 y: i32,
182 layer: i32,
183 ) -> Option<PolyRef> {
184 if let Some(_tile) = nav_mesh.get_tile_at(x, y, layer) {
186 None
190 } else {
191 None
192 }
193 }
194
195 fn collect_obstacles_for_tile(
197 &self,
198 tile_cache: &TileCache,
199 _tile_entry: &TileCacheEntry,
200 ) -> Vec<Obstacle> {
201 let mut affecting_obstacles = Vec::new();
202
203 let obstacle_count = tile_cache.get_obstacle_count();
206
207 for obstacle_idx in 0..obstacle_count {
208 if let Some(obstacle) = tile_cache.get_obstacle(obstacle_idx) {
209 affecting_obstacles.push(obstacle.clone());
212 }
213 }
214
215 affecting_obstacles
216 }
217
218 pub fn update_all_affected_tiles(
220 &self,
221 tile_cache: &mut TileCache,
222 nav_mesh: &mut NavMesh,
223 ) -> Result<Vec<PolyRef>, TileCacheError> {
224 let mut updated_tiles = Vec::new();
225
226 tile_cache.update()?;
228
229 let tiles_to_rebuild = self.find_tiles_needing_rebuild(tile_cache)?;
231
232 for tile_idx in tiles_to_rebuild {
234 if let Some(tile_ref) = self.rebuild_tile_in_nav_mesh(tile_cache, nav_mesh, tile_idx)? {
235 updated_tiles.push(tile_ref);
236 }
237 }
238
239 Ok(updated_tiles)
240 }
241
242 fn find_tiles_needing_rebuild(
244 &self,
245 tile_cache: &TileCache,
246 ) -> Result<Vec<usize>, TileCacheError> {
247 let mut tiles_to_rebuild = Vec::new();
248
249 let obstacle_count = tile_cache.get_obstacle_count();
252
253 for obstacle_idx in 0..obstacle_count {
254 if let Some(_obstacle) = tile_cache.get_obstacle(obstacle_idx) {
255 }
262 }
263
264 for x in -2..3 {
268 for y in -2..3 {
269 for layer in 0..2 {
270 if tile_cache.get_tile_at(x, y, layer).is_some() {
271 let tile_idx = ((y + 2) * 5 + (x + 2)) as usize;
274 if tile_idx < 25 {
275 tiles_to_rebuild.push(tile_idx);
277 }
278 }
279 }
280 }
281 }
282
283 Ok(tiles_to_rebuild)
284 }
285
286 pub fn get_builder_config(&self) -> &TileCacheBuilderConfig {
288 &self.builder_config
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use waymark::NavMeshParams;
296
297 #[test]
298 fn test_integration_creation() {
299 let builder_config = TileCacheBuilderConfig::default();
300 let integration = TileCacheNavMeshIntegration::new(builder_config);
301
302 assert_eq!(integration.get_builder_config().cs, 0.3);
303 assert_eq!(integration.get_builder_config().ch, 0.2);
304 }
305
306 #[test]
307 fn test_find_free_tile_slot() {
308 let params = {
309 let mut p = NavMeshParams::default();
310 p.tile_width = 64.0;
311 p.tile_height = 64.0;
312 p.max_tiles = 1023; p.max_polys_per_tile = 8192;
314 p
315 };
316
317 let nav_mesh = NavMesh::new(params).unwrap();
318 let builder_config = TileCacheBuilderConfig::default();
319 let integration = TileCacheNavMeshIntegration::new(builder_config);
320
321 let free_slot_result = integration.find_free_tile_slot(&nav_mesh);
323 assert!(free_slot_result.is_err()); }
325}