1use super::tile_cache::{Obstacle, ObstacleData, ObstacleState, TileCache};
7use super::tile_cache_data::{TileCacheBuilderConfig, TileCacheLayer};
8use crate::error::TileCacheError;
9use glam::Vec3;
10use landmark::{
11 CompactHeightfield, ContourSet, Heightfield, MESH_NULL_IDX, PolyMesh, PolyMeshDetail,
12 RecastConfig,
13};
14use waymark::nav_mesh::{MeshTile, Poly, PolyDetail, TileHeader};
15use waymark::{MAX_VERTS_PER_POLY, PolyFlags, PolyType};
16
17#[derive(Debug)]
19pub struct TileCacheBuilder {
20 config: TileCacheBuilderConfig,
22 recast_config: RecastConfig,
24}
25
26impl TileCacheBuilder {
27 pub fn new(config: TileCacheBuilderConfig) -> Self {
29 let recast_config = {
31 let mut rc = RecastConfig::default();
32 rc.cs = config.cs;
33 rc.ch = config.ch;
34 rc.walkable_height = config.walkable_height;
35 rc.walkable_climb = config.walkable_climb;
36 rc.walkable_radius = config.walkable_radius;
37 rc.max_edge_len = config.max_edge_len as i32;
38 rc.max_simplification_error = config.max_simplification_error;
39 rc.min_region_area = config.min_region_area;
40 rc.merge_region_area = config.merge_region_area;
41 rc.max_vertices_per_polygon = config.max_verts_per_poly;
42 rc
43 };
44
45 Self {
46 config,
47 recast_config,
48 }
49 }
50
51 pub fn build_tile_from_layer(
53 &self,
54 layer: &TileCacheLayer,
55 obstacles: &[Obstacle],
56 _tile_cache: &TileCache,
57 ) -> Result<MeshTile, TileCacheError> {
58 let mut chf = self.layer_to_compact_heightfield(layer)?;
60
61 self.rasterize_obstacles(&mut chf, obstacles, &layer.header)?;
63
64 let cset = ContourSet::build_from_compact_heightfield(
66 &chf,
67 self.recast_config.max_simplification_error,
68 self.recast_config.max_edge_len,
69 self.recast_config.min_region_area,
70 self.recast_config.merge_region_area,
71 )?;
72
73 let pmesh = PolyMesh::build_from_contour_set(
75 &cset,
76 self.recast_config.max_vertices_per_polygon as usize,
77 )?;
78
79 let dmesh = PolyMeshDetail::build_from_poly_mesh(
81 &pmesh,
82 &chf,
83 self.recast_config.detail_sample_dist,
84 self.recast_config.detail_sample_max_error,
85 )?;
86
87 let tile = self.create_mesh_tile(&pmesh, &dmesh, &layer.header)?;
89
90 Ok(tile)
91 }
92
93 fn layer_to_compact_heightfield(
95 &self,
96 layer: &TileCacheLayer,
97 ) -> Result<CompactHeightfield, TileCacheError> {
98 let header = &layer.header;
99
100 let width = header.width as usize;
102 let height = header.height as usize;
103
104 let mut hf = Heightfield::new(
106 width as i32,
107 height as i32,
108 Vec3::new(header.bmin[0], header.bmin[1], header.bmin[2]),
109 Vec3::new(header.bmax[0], header.bmax[1], header.bmax[2]),
110 self.config.cs,
111 self.config.ch,
112 );
113
114 self.decode_layer_to_heightfield(&mut hf, layer)?;
118
119 let chf = CompactHeightfield::build_from_heightfield(
121 &hf,
122 self.config.walkable_height,
123 self.config.walkable_climb,
124 )?;
125
126 Ok(chf)
127 }
128
129 fn decode_layer_to_heightfield(
131 &self,
132 hf: &mut Heightfield,
133 layer: &TileCacheLayer,
134 ) -> Result<(), TileCacheError> {
135 let width = layer.header.width as usize;
136 let height = layer.header.height as usize;
137
138 if layer.regons.len() != width * height {
140 return Err(TileCacheError::InvalidRegionData);
141 }
142 if layer.areas.len() != width * height {
143 return Err(TileCacheError::InvalidAreaData);
144 }
145
146 for y in 0..height {
148 for x in 0..width {
149 let idx = y * width + x;
150 let region = layer.regons[idx];
151 let area = layer.areas[idx];
152
153 if region == 0 {
155 continue;
156 }
157
158 let cell_height = layer.header.hmin as i32;
160
161 hf.add_span(
163 x as i32,
164 y as i32,
165 cell_height as i16,
166 (cell_height + 1) as i16,
167 area,
168 )?;
169 }
170 }
171
172 Ok(())
173 }
174
175 fn rasterize_obstacles(
177 &self,
178 chf: &mut CompactHeightfield,
179 obstacles: &[Obstacle],
180 header: &crate::tile_cache_data::TileCacheLayerHeader,
181 ) -> Result<(), TileCacheError> {
182 for obstacle in obstacles {
183 if obstacle.state == ObstacleState::Empty || obstacle.state == ObstacleState::Removing {
185 continue;
186 }
187
188 let tile_min = Vec3::new(header.bmin[0], header.bmin[1], header.bmin[2]);
190 let tile_max = Vec3::new(header.bmax[0], header.bmax[1], header.bmax[2]);
191
192 let (obs_min, obs_max) = match &obstacle.data {
193 ObstacleData::Cylinder {
194 pos,
195 radius,
196 height,
197 } => {
198 let obs_min = Vec3::new(pos[0] - radius, pos[1], pos[2] - radius);
199 let obs_max = Vec3::new(pos[0] + radius, pos[1] + height, pos[2] + radius);
200 (obs_min, obs_max)
201 }
202 ObstacleData::Box { bmin, bmax } => {
203 let obs_min = Vec3::new(bmin[0], bmin[1], bmin[2]);
204 let obs_max = Vec3::new(bmax[0], bmax[1], bmax[2]);
205 (obs_min, obs_max)
206 }
207 ObstacleData::OrientedBox {
208 center,
209 half_extents,
210 ..
211 } => {
212 let radius = (half_extents[0].powi(2) + half_extents[2].powi(2)).sqrt();
214 let obs_min = Vec3::new(
215 center[0] - radius,
216 center[1] - half_extents[1],
217 center[2] - radius,
218 );
219 let obs_max = Vec3::new(
220 center[0] + radius,
221 center[1] + half_extents[1],
222 center[2] + radius,
223 );
224 (obs_min, obs_max)
225 }
226 };
227
228 if obs_max.x < tile_min.x
230 || obs_min.x > tile_max.x
231 || obs_max.z < tile_min.z
232 || obs_min.z > tile_max.z
233 {
234 continue;
235 }
236
237 match &obstacle.data {
239 ObstacleData::Cylinder { .. } => {
240 self.rasterize_cylinder_obstacle(chf, obstacle)?;
241 }
242 ObstacleData::Box { .. } => {
243 self.rasterize_box_obstacle(chf, obstacle)?;
244 }
245 ObstacleData::OrientedBox { .. } => {
246 self.rasterize_oriented_box_obstacle(chf, obstacle)?;
247 }
248 }
249 }
250
251 Ok(())
252 }
253
254 fn rasterize_cylinder_obstacle(
256 &self,
257 chf: &mut CompactHeightfield,
258 obstacle: &Obstacle,
259 ) -> Result<(), TileCacheError> {
260 let (center, radius, height) = match &obstacle.data {
261 ObstacleData::Cylinder {
262 pos,
263 radius,
264 height,
265 } => (Vec3::new(pos[0], pos[1], pos[2]), *radius, *height),
266 _ => return Ok(()), };
268
269 let chf_bmin = chf.bmin();
271 let chf_width = chf.width();
272 let chf_height = chf.height();
273
274 let min_x = ((center.x - radius - chf_bmin.x) / self.config.cs).floor() as i32;
276 let max_x = ((center.x + radius - chf_bmin.x) / self.config.cs).ceil() as i32;
277 let min_z = ((center.z - radius - chf_bmin.z) / self.config.cs).floor() as i32;
278 let max_z = ((center.z + radius - chf_bmin.z) / self.config.cs).ceil() as i32;
279
280 let min_x = min_x.max(0);
282 let max_x = max_x.min(chf_width - 1);
283 let min_z = min_z.max(0);
284 let max_z = max_z.min(chf_height - 1);
285
286 for z in min_z..=max_z {
288 for x in min_x..=max_x {
289 let cell_x = chf_bmin.x + (x as f32 + 0.5) * self.config.cs;
291 let cell_z = chf_bmin.z + (z as f32 + 0.5) * self.config.cs;
292
293 let dx = cell_x - center.x;
295 let dz = cell_z - center.z;
296 let dist_sq = dx * dx + dz * dz;
297
298 if dist_sq <= radius * radius {
299 let cell_idx = (z * chf_width + x) as usize;
301 let (first_span_idx, span_count) = {
303 match chf.cells().get(cell_idx) {
304 Some(cell) => match cell.index {
305 Some(idx) => (idx, cell.count),
306 None => continue,
307 },
308 None => continue,
309 }
310 };
311
312 for s in 0..span_count {
313 let current_span_idx = first_span_idx + s;
314 if let Some(span) = chf.spans_mut().get_mut(current_span_idx) {
315 let span_min_y = chf_bmin.y + span.y as f32 * self.config.ch;
317 let span_max_y = span_min_y + self.config.ch;
318
319 if span_min_y < center.y + height && span_max_y > center.y {
320 span.area = 0; }
323 }
324 }
325 }
326 }
327 }
328
329 Ok(())
330 }
331
332 fn create_mesh_tile(
334 &self,
335 pmesh: &PolyMesh,
336 dmesh: &PolyMeshDetail,
337 header: &crate::tile_cache_data::TileCacheLayerHeader,
338 ) -> Result<MeshTile, TileCacheError> {
339 let mut tile_header = TileHeader::new(header.tx, header.ty, header.tlayer);
341 tile_header.set_vert_count(pmesh.vert_count() as i32);
342 tile_header.set_poly_count(pmesh.poly_count() as i32);
343 tile_header.set_detail_mesh_count(dmesh.poly_count() as i32);
344 tile_header.set_detail_vert_count(dmesh.vert_count() as i32);
345 tile_header.set_detail_tri_count(dmesh.tri_count() as i32);
346
347 let mut verts = Vec::with_capacity(pmesh.vert_count() * 3);
349 for i in 0..pmesh.vert_count() {
350 verts.push(pmesh.verts()[i * 3] as f32);
351 verts.push(pmesh.verts()[i * 3 + 1] as f32);
352 verts.push(pmesh.verts()[i * 3 + 2] as f32);
353 }
354
355 let mut polys = Vec::with_capacity(pmesh.poly_count());
357 for i in 0..pmesh.poly_count() {
358 let mut vert_count = 0;
360 let mut verts = [0u16; MAX_VERTS_PER_POLY];
361 let mut neighbors = [0u16; MAX_VERTS_PER_POLY];
362
363 for j in 0..pmesh.max_verts_per_poly() {
365 let poly_idx = i * pmesh.max_verts_per_poly() + j;
366 if poly_idx < pmesh.polys().len() {
367 let v = pmesh.polys()[poly_idx];
368 if v != MESH_NULL_IDX {
369 verts[vert_count] = v;
370 neighbors[vert_count] = 0; vert_count += 1;
372 }
373 }
374 }
375
376 let poly = Poly::from_mesh_data(
377 verts,
378 neighbors,
379 vert_count as u8,
380 pmesh.areas()[i],
381 PolyType::Ground,
382 PolyFlags::WALK,
383 );
384
385 polys.push(poly);
386 }
387
388 let mut detail_meshes = Vec::with_capacity(dmesh.poly_count());
390 for i in 0..dmesh.poly_count() {
391 detail_meshes.push(PolyDetail {
392 vert_base: dmesh.poly_start()[i] as u32,
393 tri_base: dmesh.poly_start()[i] as u32,
394 vert_count: 0, tri_count: dmesh.poly_tri_count()[i] as u8,
396 });
397 }
398
399 let detail_verts = dmesh.vertices().to_vec();
401
402 let mut detail_tris = Vec::with_capacity(dmesh.tri_count() * 3);
404 for i in 0..dmesh.tri_count() {
405 detail_tris.push(dmesh.triangles()[i * 3] as u8);
406 detail_tris.push(dmesh.triangles()[i * 3 + 1] as u8);
407 detail_tris.push(dmesh.triangles()[i * 3 + 2] as u8);
408 }
409
410 let tile = MeshTile::from_tile_data(
412 tile_header,
413 polys,
414 verts,
415 detail_meshes,
416 detail_verts,
417 detail_tris,
418 );
419
420 Ok(tile)
421 }
422
423 fn rasterize_box_obstacle(
425 &self,
426 chf: &mut CompactHeightfield,
427 obstacle: &Obstacle,
428 ) -> Result<(), TileCacheError> {
429 let (bmin, bmax) = match &obstacle.data {
430 ObstacleData::Box { bmin, bmax } => (*bmin, *bmax),
431 _ => return Ok(()), };
433
434 let chf_bmin = chf.bmin();
436 let chf_width = chf.width();
437 let chf_height = chf.height();
438
439 let min_x = ((bmin[0] - chf_bmin.x) / self.config.cs).floor() as i32;
441 let max_x = ((bmax[0] - chf_bmin.x) / self.config.cs).ceil() as i32;
442 let min_z = ((bmin[2] - chf_bmin.z) / self.config.cs).floor() as i32;
443 let max_z = ((bmax[2] - chf_bmin.z) / self.config.cs).ceil() as i32;
444
445 let min_x = min_x.max(0);
447 let max_x = max_x.min(chf_width - 1);
448 let min_z = min_z.max(0);
449 let max_z = max_z.min(chf_height - 1);
450
451 for z in min_z..=max_z {
453 for x in min_x..=max_x {
454 let cell_idx = (x + z * chf_width) as usize;
455 let (cell_index, cell_count) = {
457 let cell = &chf.cells()[cell_idx];
458 match cell.index {
459 Some(idx) => (idx, cell.count),
460 None => continue,
461 }
462 };
463
464 for i in cell_index..(cell_index + cell_count) {
466 let span = &mut chf.spans_mut()[i];
467 let span_y_min = span.min as f32 * self.config.ch + chf_bmin.y;
468 let span_y_max = span.max as f32 * self.config.ch + chf_bmin.y;
469
470 if span_y_max > bmin[1] && span_y_min < bmax[1] {
472 span.area = 0; }
474 }
475 }
476 }
477
478 Ok(())
479 }
480
481 fn rasterize_oriented_box_obstacle(
483 &self,
484 chf: &mut CompactHeightfield,
485 obstacle: &Obstacle,
486 ) -> Result<(), TileCacheError> {
487 let (center, half_extents, rot_aux) = match &obstacle.data {
488 ObstacleData::OrientedBox {
489 center,
490 half_extents,
491 rot_aux,
492 } => (*center, *half_extents, *rot_aux),
493 _ => return Ok(()), };
495
496 let cos_half_cos_half = rot_aux[1] + 0.5;
500 let cos_half_sin_half = rot_aux[0];
501
502 let radius = (half_extents[0].powi(2) + half_extents[2].powi(2)).sqrt();
504
505 let chf_bmin = chf.bmin();
507 let chf_width = chf.width();
508 let chf_height = chf.height();
509
510 let min_x = ((center[0] - radius - chf_bmin.x) / self.config.cs).floor() as i32;
512 let max_x = ((center[0] + radius - chf_bmin.x) / self.config.cs).ceil() as i32;
513 let min_z = ((center[2] - radius - chf_bmin.z) / self.config.cs).floor() as i32;
514 let max_z = ((center[2] + radius - chf_bmin.z) / self.config.cs).ceil() as i32;
515
516 let min_x = min_x.max(0);
518 let max_x = max_x.min(chf_width - 1);
519 let min_z = min_z.max(0);
520 let max_z = max_z.min(chf_height - 1);
521
522 for z in min_z..=max_z {
524 for x in min_x..=max_x {
525 let cell_x = x as f32 * self.config.cs + chf_bmin.x + self.config.cs * 0.5;
527 let cell_z = z as f32 * self.config.cs + chf_bmin.z + self.config.cs * 0.5;
528
529 let dx = cell_x - center[0];
531 let dz = cell_z - center[2];
532
533 let cos_angle = 2.0 * cos_half_cos_half - 1.0;
536 let sin_angle = 2.0 * cos_half_sin_half;
537
538 let local_x = cos_angle * dx + sin_angle * dz;
539 let local_z = -sin_angle * dx + cos_angle * dz;
540
541 if local_x.abs() <= half_extents[0] && local_z.abs() <= half_extents[2] {
543 let cell_idx = (x + z * chf_width) as usize;
544 let (cell_index, cell_count) = {
546 let cell = &chf.cells()[cell_idx];
547 match cell.index {
548 Some(idx) => (idx, cell.count),
549 None => continue,
550 }
551 };
552
553 for i in cell_index..(cell_index + cell_count) {
555 let span = &mut chf.spans_mut()[i];
556 let span_y_min = span.min as f32 * self.config.ch + chf_bmin.y;
557 let span_y_max = span.max as f32 * self.config.ch + chf_bmin.y;
558
559 if span_y_max > center[1] - half_extents[1]
561 && span_y_min < center[1] + half_extents[1]
562 {
563 span.area = 0; }
565 }
566 }
567 }
568 }
569
570 Ok(())
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn test_tile_cache_builder_creation() {
580 let config = TileCacheBuilderConfig::default();
581 let builder = TileCacheBuilder::new(config);
582
583 assert_eq!(builder.recast_config.cs, 0.3);
584 assert_eq!(builder.recast_config.ch, 0.2);
585 assert_eq!(builder.recast_config.walkable_height, 20);
586 }
587}