mirror of
https://codeberg.org/icewind/sourcenav.git
synced 2026-06-03 10:14:11 +02:00
height finder
This commit is contained in:
parent
b4b32dd6fa
commit
ddcf105394
4 changed files with 280 additions and 136 deletions
|
|
@ -8,4 +8,6 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitbuffer = "0.7.1"
|
bitbuffer = "0.7.1"
|
||||||
err-derive = "0.2.4"
|
err-derive = "0.2.4"
|
||||||
|
aabb-quadtree = "0.2.0"
|
||||||
|
euclid = "0.19"
|
||||||
227
src/lib.rs
227
src/lib.rs
|
|
@ -1,149 +1,104 @@
|
||||||
pub use crate::navmesh::NavArea;
|
use crate::navmesh::HammerUnit;
|
||||||
use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
|
use crate::parser::read_areas;
|
||||||
use err_derive::Error;
|
pub use crate::parser::{NavArea, ParseError};
|
||||||
|
use aabb_quadtree::{ItemId, QuadTree};
|
||||||
|
use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
||||||
|
|
||||||
mod navmesh;
|
mod navmesh;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
pub struct NavTree(QuadTree<NavArea, HammerUnit, [(ItemId, TypedRect<f32, HammerUnit>); 4]>);
|
||||||
pub enum ParseError {
|
|
||||||
#[error(display = "Error while reading from data: {}", _0)]
|
pub fn get_area_tree(data: Vec<u8>) -> Result<NavTree, ParseError> {
|
||||||
ReadError(#[error(source)] bitbuffer::ReadError),
|
let areas = read_areas(data)?;
|
||||||
#[error(
|
|
||||||
display = "Invalid magic number ({:#8X}), not a nav file or corrupted",
|
let (min_x, min_y, max_x, max_y) = areas.iter().fold(
|
||||||
_0
|
(f32::MAX, f32::MAX, f32::MIN, f32::MIN),
|
||||||
)]
|
|(min_x, min_y, max_x, max_y), area| {
|
||||||
InvalidMagicNumber(u32),
|
(
|
||||||
#[error(display = "The major version for this nav ({}), is not supported", _0)]
|
f32::min(min_x, area.north_west.0),
|
||||||
UnsupportedVersion(u32),
|
f32::min(min_y, area.north_west.1),
|
||||||
|
f32::max(max_x, area.south_east.0),
|
||||||
|
f32::max(max_y, area.south_east.1),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut tree = QuadTree::default(
|
||||||
|
TypedRect::new(
|
||||||
|
TypedPoint2D::new(min_x - 1.0, min_y - 1.0),
|
||||||
|
TypedSize2D::new(max_x - min_x + 2.0, max_y - min_y + 2.0),
|
||||||
|
),
|
||||||
|
areas.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for area in areas {
|
||||||
|
tree.insert(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(NavTree(tree))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_areas(data: Vec<u8>) -> Result<Vec<NavArea>, ParseError> {
|
impl NavTree {
|
||||||
let mut data = BitReadStream::new(BitReadBuffer::new(data, LittleEndian));
|
pub fn query(
|
||||||
let magic = data.read()?;
|
&self,
|
||||||
if magic != 0xFEEDFACE {
|
x: f32,
|
||||||
return Err(ParseError::InvalidMagicNumber(magic));
|
y: f32,
|
||||||
|
) -> impl Iterator<Item = (&NavArea, TypedRect<f32, HammerUnit>, ItemId)> {
|
||||||
|
let query_box = TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(1.0, 1.0));
|
||||||
|
|
||||||
|
self.0.query(query_box).into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
let major_version: u32 = data.read()?;
|
pub fn find_z_height<'a>(&'a self, x: f32, y: f32) -> impl Iterator<Item = f32> + 'a {
|
||||||
|
self.query(x, y)
|
||||||
if major_version < 6 || major_version > 16 {
|
.map(move |(area, ..)| area.get_z_height(x, y))
|
||||||
return Err(ParseError::UnsupportedVersion(major_version));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _minor_version: u32 = if major_version >= 10 { data.read()? } else { 0 };
|
|
||||||
|
|
||||||
let _size: u32 = data.read()?;
|
|
||||||
|
|
||||||
let _is_analysed = if major_version >= 14 {
|
|
||||||
data.read_int::<u8>(8)? == 1
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let place_count: u16 = data.read()?;
|
|
||||||
|
|
||||||
// let places = Vec::with_capacity(place_count as usize);
|
|
||||||
for _id in 1..=place_count {
|
|
||||||
let name_length: u16 = data.read()?;
|
|
||||||
let _name = data.read_string(Some(name_length as usize))?;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
let _has_unnamed_areas = if major_version >= 12 {
|
|
||||||
data.read_int::<u8>(8)? == 1
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let area_count: u32 = data.read()?;
|
|
||||||
|
|
||||||
let mut areas = Vec::with_capacity(area_count as usize);
|
|
||||||
|
|
||||||
for _ in 0..area_count {
|
|
||||||
let id = data.read()?;
|
|
||||||
|
|
||||||
let flags = if major_version <= 8 {
|
|
||||||
data.read_int(8)?
|
|
||||||
} else if major_version <= 12 {
|
|
||||||
data.read_int(16)?
|
|
||||||
} else {
|
|
||||||
data.read_int(32)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let north_west = data.read()?;
|
|
||||||
let south_east = data.read()?;
|
|
||||||
let north_east_z = data.read()?;
|
|
||||||
let south_west_z = data.read()?;
|
|
||||||
|
|
||||||
let connections = data.read()?;
|
|
||||||
|
|
||||||
let hiding_spots_count: u8 = data.read()?;
|
|
||||||
let hiding_spots = data.read_sized(hiding_spots_count as usize)?;
|
|
||||||
|
|
||||||
let approach_areas = if major_version < 15 {
|
|
||||||
let approach_area_count: u8 = data.read()?;
|
|
||||||
|
|
||||||
data.read_sized(approach_area_count as usize)?
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let encounter_paths_count: u32 = data.read()?;
|
|
||||||
let encounter_paths = data.read_sized(encounter_paths_count as usize)?;
|
|
||||||
|
|
||||||
let place = data.read()?;
|
|
||||||
|
|
||||||
let ladder_connections = data.read()?;
|
|
||||||
|
|
||||||
let earliest_occupy_first_team = data.read()?;
|
|
||||||
let earliest_occupy_second_team = data.read()?;
|
|
||||||
|
|
||||||
let light_intensity = if major_version >= 11 {
|
|
||||||
data.read()?
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let visible_areas = if major_version >= 16 {
|
|
||||||
let visible_areas_count: u32 = data.read()?;
|
|
||||||
data.read_sized(visible_areas_count as usize)?
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let inherit_visibility_from_area_id = data.read()?;
|
|
||||||
|
|
||||||
data.skip_bits(32)?;
|
|
||||||
|
|
||||||
areas.push(NavArea {
|
|
||||||
id,
|
|
||||||
north_west,
|
|
||||||
south_east,
|
|
||||||
north_east_z,
|
|
||||||
south_west_z,
|
|
||||||
flags,
|
|
||||||
connections,
|
|
||||||
hiding_spots,
|
|
||||||
approach_areas,
|
|
||||||
encounter_paths,
|
|
||||||
place,
|
|
||||||
ladder_connections,
|
|
||||||
earliest_occupy_first_team,
|
|
||||||
earliest_occupy_second_team,
|
|
||||||
light_intensity,
|
|
||||||
visible_areas,
|
|
||||||
inherit_visibility_from_area_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(data.bits_left() <= 32);
|
|
||||||
|
|
||||||
Ok(areas)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test_tree() {
|
||||||
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||||
let areas = read_areas(file).unwrap();
|
let tree = get_area_tree(file).unwrap();
|
||||||
assert_eq!(1930, areas.len());
|
|
||||||
|
// single flat plane
|
||||||
|
let point1 = (1600.0, -1300.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![375.21506],
|
||||||
|
tree.find_z_height(point1.0, point1.1).collect::<Vec<f32>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2 z levels
|
||||||
|
let point2 = (360.0, -1200.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![290.2907, 108.144775],
|
||||||
|
tree.find_z_height(point2.0, point2.1).collect::<Vec<f32>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
// top of slope
|
||||||
|
let point3 = (320.0, -1030.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![220.83125],
|
||||||
|
tree.find_z_height(point3.0, point3.1).collect::<Vec<f32>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
// bottom of same slope
|
||||||
|
let point4 = (205.0, -1030.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![147.23126],
|
||||||
|
tree.find_z_height(point4.0, point4.1).collect::<Vec<f32>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tree.query(point3.0, point3.1)
|
||||||
|
.next()
|
||||||
|
.map(|(area, ..)| area.id),
|
||||||
|
tree.query(point4.0, point4.1)
|
||||||
|
.next()
|
||||||
|
.map(|(area, ..)| area.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
use aabb_quadtree::Spatial;
|
||||||
use bitbuffer::{BitRead, BitReadStream, Endianness, ReadError};
|
use bitbuffer::{BitRead, BitReadStream, Endianness, ReadError};
|
||||||
|
use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
|
||||||
#[derive(Debug, BitRead)]
|
#[derive(Debug, BitRead)]
|
||||||
|
|
@ -25,6 +27,44 @@ pub struct NavArea {
|
||||||
pub inherit_visibility_from_area_id: u32,
|
pub inherit_visibility_from_area_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NavArea {
|
||||||
|
pub fn width(&self) -> f32 {
|
||||||
|
self.south_east.0 - self.north_west.0
|
||||||
|
}
|
||||||
|
pub fn height(&self) -> f32 {
|
||||||
|
self.south_east.1 - self.north_west.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_z_height(&self, x: f32, y: f32) -> f32 {
|
||||||
|
let from_east = self.south_east.0 - x;
|
||||||
|
let from_south = self.south_east.0 - y;
|
||||||
|
|
||||||
|
let north_slope = (self.north_west.2 - self.north_east_z) / self.width();
|
||||||
|
let south_slope = (self.south_west_z - self.south_east.2) / self.width();
|
||||||
|
|
||||||
|
let north_z = self.north_east_z + north_slope * from_east;
|
||||||
|
let south_z = self.south_east.2 + south_slope * from_east;
|
||||||
|
|
||||||
|
let final_slope = (north_z - south_z) / self.height();
|
||||||
|
|
||||||
|
south_z + final_slope * from_south
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HammerUnit;
|
||||||
|
|
||||||
|
impl Spatial<HammerUnit> for NavArea {
|
||||||
|
fn aabb(&self) -> TypedRect<f32, HammerUnit> {
|
||||||
|
TypedRect {
|
||||||
|
origin: TypedPoint2D::new(self.north_west.0, self.north_west.1),
|
||||||
|
size: TypedSize2D::new(
|
||||||
|
self.south_east.0 - self.north_west.0,
|
||||||
|
self.south_east.1 - self.north_west.1,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Connections([Vec<u32>; 4]);
|
pub struct Connections([Vec<u32>; 4]);
|
||||||
|
|
||||||
|
|
|
||||||
147
src/parser.rs
Normal file
147
src/parser.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
pub use crate::navmesh::NavArea;
|
||||||
|
use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
|
||||||
|
use err_derive::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ParseError {
|
||||||
|
#[error(display = "Error while reading from data: {}", _0)]
|
||||||
|
ReadError(#[error(source)] bitbuffer::ReadError),
|
||||||
|
#[error(
|
||||||
|
display = "Invalid magic number ({:#8X}), not a nav file or corrupted",
|
||||||
|
_0
|
||||||
|
)]
|
||||||
|
InvalidMagicNumber(u32),
|
||||||
|
#[error(display = "The major version for this nav ({}), is not supported", _0)]
|
||||||
|
UnsupportedVersion(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_areas(data: Vec<u8>) -> Result<Vec<NavArea>, ParseError> {
|
||||||
|
let mut data = BitReadStream::new(BitReadBuffer::new(data, LittleEndian));
|
||||||
|
let magic = data.read()?;
|
||||||
|
if magic != 0xFEEDFACE {
|
||||||
|
return Err(ParseError::InvalidMagicNumber(magic));
|
||||||
|
}
|
||||||
|
|
||||||
|
let major_version: u32 = data.read()?;
|
||||||
|
|
||||||
|
if major_version < 6 || major_version > 16 {
|
||||||
|
return Err(ParseError::UnsupportedVersion(major_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _minor_version: u32 = if major_version >= 10 { data.read()? } else { 0 };
|
||||||
|
|
||||||
|
let _size: u32 = data.read()?;
|
||||||
|
|
||||||
|
let _is_analysed = if major_version >= 14 {
|
||||||
|
data.read_int::<u8>(8)? == 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let place_count: u16 = data.read()?;
|
||||||
|
|
||||||
|
// let places = Vec::with_capacity(place_count as usize);
|
||||||
|
for _id in 1..=place_count {
|
||||||
|
let name_length: u16 = data.read()?;
|
||||||
|
let _name = data.read_string(Some(name_length as usize))?;
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
let _has_unnamed_areas = if major_version >= 12 {
|
||||||
|
data.read_int::<u8>(8)? == 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let area_count: u32 = data.read()?;
|
||||||
|
|
||||||
|
let mut areas = Vec::with_capacity(area_count as usize);
|
||||||
|
|
||||||
|
for _ in 0..area_count {
|
||||||
|
let id = data.read()?;
|
||||||
|
|
||||||
|
let flags = if major_version <= 8 {
|
||||||
|
data.read_int(8)?
|
||||||
|
} else if major_version <= 12 {
|
||||||
|
data.read_int(16)?
|
||||||
|
} else {
|
||||||
|
data.read_int(32)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let north_west = data.read()?;
|
||||||
|
let south_east = data.read()?;
|
||||||
|
let north_east_z = data.read()?;
|
||||||
|
let south_west_z = data.read()?;
|
||||||
|
|
||||||
|
let connections = data.read()?;
|
||||||
|
|
||||||
|
let hiding_spots_count: u8 = data.read()?;
|
||||||
|
let hiding_spots = data.read_sized(hiding_spots_count as usize)?;
|
||||||
|
|
||||||
|
let approach_areas = if major_version < 15 {
|
||||||
|
let approach_area_count: u8 = data.read()?;
|
||||||
|
|
||||||
|
data.read_sized(approach_area_count as usize)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encounter_paths_count: u32 = data.read()?;
|
||||||
|
let encounter_paths = data.read_sized(encounter_paths_count as usize)?;
|
||||||
|
|
||||||
|
let place = data.read()?;
|
||||||
|
|
||||||
|
let ladder_connections = data.read()?;
|
||||||
|
|
||||||
|
let earliest_occupy_first_team = data.read()?;
|
||||||
|
let earliest_occupy_second_team = data.read()?;
|
||||||
|
|
||||||
|
let light_intensity = if major_version >= 11 {
|
||||||
|
data.read()?
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let visible_areas = if major_version >= 16 {
|
||||||
|
let visible_areas_count: u32 = data.read()?;
|
||||||
|
data.read_sized(visible_areas_count as usize)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inherit_visibility_from_area_id = data.read()?;
|
||||||
|
|
||||||
|
data.skip_bits(32)?;
|
||||||
|
|
||||||
|
areas.push(NavArea {
|
||||||
|
id,
|
||||||
|
north_west,
|
||||||
|
south_east,
|
||||||
|
north_east_z,
|
||||||
|
south_west_z,
|
||||||
|
flags,
|
||||||
|
connections,
|
||||||
|
hiding_spots,
|
||||||
|
approach_areas,
|
||||||
|
encounter_paths,
|
||||||
|
place,
|
||||||
|
ladder_connections,
|
||||||
|
earliest_occupy_first_team,
|
||||||
|
earliest_occupy_second_team,
|
||||||
|
light_intensity,
|
||||||
|
visible_areas,
|
||||||
|
inherit_visibility_from_area_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(data.bits_left() <= 32);
|
||||||
|
|
||||||
|
Ok(areas)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||||
|
let areas = read_areas(file).unwrap();
|
||||||
|
assert_eq!(1930, areas.len());
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue