1use crate::navmesh::HammerUnit;
2pub use crate::navmesh::{
3 ApproachArea, Connections, EncounterPath, LadderConnections, LadderDirection, LightIntensity,
4 NavDirection, NavHidingSpot, NavQuad, Vector3, VisibleArea,
5};
6pub use crate::parser::{read_areas, NavArea, ParseError};
7use aabb_quadtree::{ItemId, QuadTree};
8use binrw::io::{Read, Seek};
9use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
10
11mod navmesh;
12mod parser;
13
14type Rect = TypedRect<f32, HammerUnit>;
15
16pub struct NavQuadTree(QuadTree<NavQuad, HammerUnit, [(ItemId, Rect); 4]>);
18
19pub fn get_quad_tree<R: Read + Seek>(data: &mut R) -> Result<NavQuadTree, ParseError> {
35 let areas = read_areas(data)?;
36
37 let (min_x, min_y, max_x, max_y) = areas.iter().map(|area| &area.quad).fold(
38 (f32::MAX, f32::MAX, f32::MIN, f32::MIN),
39 |(min_x, min_y, max_x, max_y), area| {
40 (
41 f32::min(min_x, area.north_west.0),
42 f32::min(min_y, area.north_west.1),
43 f32::max(max_x, area.south_east.0),
44 f32::max(max_y, area.south_east.1),
45 )
46 },
47 );
48
49 let mut tree = QuadTree::default(
50 Rect::new(
51 TypedPoint2D::new(min_x - 1.0, min_y - 1.0),
52 TypedSize2D::new(max_x - min_x + 2.0, max_y - min_y + 2.0),
53 ),
54 areas.len(),
55 );
56
57 for area in areas {
58 tree.insert(area.quad);
59 }
60
61 Ok(NavQuadTree(tree))
62}
63
64impl NavQuadTree {
65 pub fn query(&self, x: f32, y: f32) -> impl Iterator<Item = &NavQuad> {
82 let query_box = Rect::new(TypedPoint2D::new(x, y), TypedSize2D::new(1.0, 1.0));
83
84 self.0.query(query_box).into_iter().map(|(area, ..)| area)
85 }
86
87 pub fn find_z_height(&self, x: f32, y: f32) -> impl Iterator<Item = f32> + '_ {
106 self.query(x, y).map(move |area| area.get_z_height(x, y))
107 }
108
109 pub fn find_best_height(&self, x: f32, y: f32, z_guess: f32) -> Option<f32> {
113 self.find_z_height(x, y).min_by(|a, b| {
114 let a_diff = (a - z_guess).abs();
115 let b_diff = (b - z_guess).abs();
116 a_diff.total_cmp(&b_diff)
117 })
118 }
119
120 pub fn quads(&self) -> impl Iterator<Item = &NavQuad> {
139 self.0.iter().map(|(_, (area, _))| area)
140 }
141}
142
143#[test]
144fn test_tree() {
145 use std::fs::File;
146 use std::io::BufReader;
147
148 let mut file = BufReader::new(File::open("data/pl_badwater.nav").unwrap());
149 let tree = get_quad_tree(&mut file).unwrap();
150
151 let point1 = (1600.0, -1300.0);
153
154 assert_eq!(
155 vec![375.21506],
156 tree.find_z_height(point1.0, point1.1).collect::<Vec<f32>>()
157 );
158
159 let point2 = (360.0, -1200.0);
161
162 assert_eq!(
163 vec![290.2907, 108.144775],
164 tree.find_z_height(point2.0, point2.1).collect::<Vec<f32>>()
165 );
166
167 let point3 = (320.0, -1030.0);
169
170 assert_eq!(
171 vec![220.83125],
172 tree.find_z_height(point3.0, point3.1).collect::<Vec<f32>>()
173 );
174
175 let point4 = (205.0, -1030.0);
177
178 assert_eq!(
179 vec![147.23126],
180 tree.find_z_height(point4.0, point4.1).collect::<Vec<f32>>()
181 );
182}
183
184#[cfg(doctest)]
185doc_comment::doctest!("../README.md");