some documentations

This commit is contained in:
Robin Appelman 2020-05-01 00:24:44 +02:00
commit 4c48efc5cc
5 changed files with 178 additions and 26 deletions

View file

@ -10,4 +10,7 @@ edition = "2018"
bitbuffer = "0.7.1" bitbuffer = "0.7.1"
err-derive = "0.2.4" err-derive = "0.2.4"
aabb-quadtree = "0.2.0" aabb-quadtree = "0.2.0"
euclid = "0.19" euclid = "0.19"
[dev-dependencies]
doc-comment = "0.3.3"

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# SourceNav
parsing of SourceEngine `.nav` files
## Usage
This library is currently focused on getting the z-height from an x/y coordinate in a map and the api is tailored towards
that usage. For other usages the raw navigation areas are exposed.
```rust
use sourcenav::get_area_tree;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::read("data/pl_badwater.nav")?;
let tree = get_area_tree(file)?;
assert_eq!(220.83125, tree.find_best_height(320.0, -1030.0, 0.0));
Ok(())
}
```

View file

@ -1,4 +1,8 @@
use crate::navmesh::HammerUnit; use crate::navmesh::HammerUnit;
pub use crate::navmesh::{
ApproachArea, Connections, EncounterPath, LadderConnections, LadderDirection, LightIntensity,
NavDirection, NavHidingSpot, Vector3, VisibleArea,
};
use crate::parser::read_areas; use crate::parser::read_areas;
pub use crate::parser::{NavArea, ParseError}; pub use crate::parser::{NavArea, ParseError};
use aabb_quadtree::{ItemId, QuadTree}; use aabb_quadtree::{ItemId, QuadTree};
@ -7,8 +11,22 @@ use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
mod navmesh; mod navmesh;
mod parser; mod parser;
/// A tree of all navigation areas
pub struct NavTree(QuadTree<NavArea, HammerUnit, [(ItemId, TypedRect<f32, HammerUnit>); 4]>); pub struct NavTree(QuadTree<NavArea, HammerUnit, [(ItemId, TypedRect<f32, HammerUnit>); 4]>);
/// Parse all navigation areas from a nav file
///
/// ## Examples
///
/// ```no_run
/// use sourcenav::get_area_tree;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = std::fs::read("path/to/navfile.nav")?;
/// let tree = get_area_tree(file)?;
/// # Ok(())
/// # }
/// ```
pub fn get_area_tree(data: Vec<u8>) -> Result<NavTree, ParseError> { pub fn get_area_tree(data: Vec<u8>) -> Result<NavTree, ParseError> {
let areas = read_areas(data)?; let areas = read_areas(data)?;
@ -40,19 +58,60 @@ pub fn get_area_tree(data: Vec<u8>) -> Result<NavTree, ParseError> {
} }
impl NavTree { impl NavTree {
pub fn query( /// Find the navigation areas at a x/y cooordinate
&self, ///
x: f32, /// ## Examples
y: f32, ///
) -> impl Iterator<Item = (&NavArea, TypedRect<f32, HammerUnit>, ItemId)> { /// ```no_run
/// use sourcenav::get_area_tree;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = std::fs::read("path/to/navfile.nav")?;
/// let tree = get_area_tree(file)?;
/// let areas = tree.query(150.0, -312.0);
/// # Ok(())
/// # }
/// ```
pub fn query(&self, x: f32, y: f32) -> impl Iterator<Item = &NavArea> {
let query_box = TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(1.0, 1.0)); let query_box = TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(1.0, 1.0));
self.0.query(query_box).into_iter() self.0.query(query_box).into_iter().map(|(area, ..)| area)
} }
/// Find the z-height of a specfic x/y cooordinate
///
/// Note that multiple heights might exist for a given x/y coooridnate
///
/// ## Examples
///
/// ```no_run
/// use sourcenav::get_area_tree;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = std::fs::read("path/to/navfile.nav")?;
/// let tree = get_area_tree(file)?;
/// let heights = tree.find_z_height(150.0, -312.0);
/// # Ok(())
/// # }
/// ```
pub fn find_z_height<'a>(&'a self, x: f32, y: f32) -> impl Iterator<Item = f32> + 'a { pub fn find_z_height<'a>(&'a self, x: f32, y: f32) -> impl Iterator<Item = f32> + 'a {
self.query(x, y) self.query(x, y).map(move |area| area.get_z_height(x, y))
.map(move |(area, ..)| area.get_z_height(x, y)) }
/// Get the z height at a point
///
/// A z-guess should be provided to resolve cases where multiple z values are possible
pub fn find_best_height(&self, x: f32, y: f32, z_guess: f32) -> f32 {
let found_heights = self.find_z_height(x, y);
let best_z = f32::MIN;
found_heights.fold(best_z, |best_z, found_z| {
if (found_z - z_guess).abs() < (best_z - z_guess).abs() {
found_z
} else {
best_z
}
})
} }
} }
@ -94,11 +153,10 @@ fn test_tree() {
); );
assert_eq!( assert_eq!(
tree.query(point3.0, point3.1) tree.query(point3.0, point3.1).next().map(|area| area.id),
.next() tree.query(point4.0, point4.1).next().map(|area| area.id)
.map(|(area, ..)| area.id),
tree.query(point4.0, point4.1)
.next()
.map(|(area, ..)| area.id)
); );
} }
#[cfg(doctest)]
doc_comment::doctest!("../README.md");

View file

@ -3,12 +3,18 @@ use bitbuffer::{BitRead, BitReadStream, Endianness, ReadError};
use euclid::{TypedPoint2D, TypedRect, TypedSize2D}; use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
use std::ops::Index; use std::ops::Index;
/// A 3 dimensional coordinate
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
pub struct Vector3(pub f32, pub f32, pub f32); pub struct Vector3(pub f32, pub f32, pub f32);
/// A unique identifier for a navigation area
#[derive(Debug, BitRead, Clone, Copy, Eq, PartialEq)]
pub struct NavAreaId(u32);
/// A navigation area from the nav file
#[derive(Debug)] #[derive(Debug)]
pub struct NavArea { pub struct NavArea {
pub id: u32, pub id: NavAreaId,
pub north_west: Vector3, pub north_west: Vector3,
pub south_east: Vector3, pub south_east: Vector3,
pub north_east_z: f32, pub north_east_z: f32,
@ -35,6 +41,22 @@ impl NavArea {
self.south_east.1 - self.north_west.1 self.south_east.1 - self.north_west.1
} }
/// Get the z height of a x/y point inside the navigation area
///
/// # Examples
///
/// ```no_run
/// use sourcenav::get_area_tree;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = std::fs::read("path/to/navfile.nav")?;
/// let tree = get_area_tree(file)?;
/// let area = tree.query(150.0, -312.0).next().unwrap();
///
/// let height = area.get_z_height(150.0, -312.0);
/// # Ok(())
/// # }
/// ```
pub fn get_z_height(&self, x: f32, y: f32) -> f32 { pub fn get_z_height(&self, x: f32, y: f32) -> f32 {
let from_east = self.south_east.0 - x; let from_east = self.south_east.0 - x;
let from_south = self.south_east.0 - y; let from_south = self.south_east.0 - y;
@ -51,7 +73,7 @@ impl NavArea {
} }
} }
pub struct HammerUnit; pub(crate) struct HammerUnit;
impl Spatial<HammerUnit> for NavArea { impl Spatial<HammerUnit> for NavArea {
fn aabb(&self) -> TypedRect<f32, HammerUnit> { fn aabb(&self) -> TypedRect<f32, HammerUnit> {
@ -65,8 +87,26 @@ impl Spatial<HammerUnit> for NavArea {
} }
} }
#[derive(Debug)] /// The connections from a navigation area into it's neighbours
pub struct Connections([Vec<u32>; 4]); ///
/// Contains a list of area id's for every [`NavDirection`]
///
/// # Examples
///
/// ```no_run
/// # fn get_connections_from_somewhere() -> sourcenav::Connections {
/// # Default::default()
/// # }
/// use sourcenav::NavDirection;
///
/// let connections = get_connections_from_somewhere();
///
/// let north_connections = &connections[NavDirection::North];
/// ```
///
/// [`NavDirection`]: ./enum.NavDirection.html
#[derive(Debug, Default)]
pub struct Connections([Vec<NavAreaId>; 4]);
impl<E: Endianness> BitRead<E> for Connections { impl<E: Endianness> BitRead<E> for Connections {
fn read(stream: &mut BitReadStream<E>) -> Result<Self, ReadError> { fn read(stream: &mut BitReadStream<E>) -> Result<Self, ReadError> {
@ -85,15 +125,33 @@ impl<E: Endianness> BitRead<E> for Connections {
} }
impl Index<NavDirection> for Connections { impl Index<NavDirection> for Connections {
type Output = Vec<u32>; type Output = Vec<NavAreaId>;
fn index(&self, index: NavDirection) -> &Self::Output { fn index(&self, index: NavDirection) -> &Self::Output {
&self.0[index as u8 as usize] &self.0[index as u8 as usize]
} }
} }
#[derive(Debug)] /// The connections from a navigation area into it's neighbours
pub struct LadderConnections([Vec<u32>; 2]); ///
/// Contains a list of area id's for every [`NavDirection`]
///
/// # Examples
///
/// ```no_run
/// # fn get_ladder_connections_from_somewhere() -> sourcenav::LadderConnections {
/// # Default::default()
/// # }
/// use sourcenav::LadderDirection;
///
/// let connections = get_ladder_connections_from_somewhere();
///
/// let down_connections = &connections[LadderDirection::Down];
/// ```
///
/// [`NavDirection`]: ./enum.NavDirection.html
#[derive(Debug, Default)]
pub struct LadderConnections([Vec<NavAreaId>; 2]);
impl<E: Endianness> BitRead<E> for LadderConnections { impl<E: Endianness> BitRead<E> for LadderConnections {
fn read(stream: &mut BitReadStream<E>) -> Result<Self, ReadError> { fn read(stream: &mut BitReadStream<E>) -> Result<Self, ReadError> {
@ -112,13 +170,14 @@ impl<E: Endianness> BitRead<E> for LadderConnections {
} }
impl Index<LadderDirection> for LadderConnections { impl Index<LadderDirection> for LadderConnections {
type Output = Vec<u32>; type Output = Vec<NavAreaId>;
fn index(&self, index: LadderDirection) -> &Self::Output { fn index(&self, index: LadderDirection) -> &Self::Output {
&self.0[index as u8 as usize] &self.0[index as u8 as usize]
} }
} }
/// The directions in which two areas can be connected
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
#[repr(u8)] #[repr(u8)]
#[discriminant_bits = 8] #[discriminant_bits = 8]
@ -129,6 +188,7 @@ pub enum NavDirection {
West, West,
} }
/// The directions in which two areas can be connected by ladder
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
#[repr(u8)] #[repr(u8)]
#[discriminant_bits = 8] #[discriminant_bits = 8]
@ -137,6 +197,7 @@ pub enum LadderDirection {
Down, Down,
} }
/// A hiding spot within an area
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
pub struct NavHidingSpot { pub struct NavHidingSpot {
id: u32, id: u32,
@ -144,6 +205,7 @@ pub struct NavHidingSpot {
flags: u8, flags: u8,
} }
/// An area that can be used for approach, no longer used in newer nav files
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
pub struct ApproachArea { pub struct ApproachArea {
approach_here: u32, approach_here: u32,
@ -153,11 +215,12 @@ pub struct ApproachArea {
approach_how: u8, approach_how: u8,
} }
/// A path that can be used to approach an area
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
pub struct EncounterPath { pub struct EncounterPath {
from_area_id: u32, from_area_id: NavAreaId,
from_direction: u8, from_direction: u8,
to_area_id: u32, to_area_id: NavAreaId,
to_direction: u8, to_direction: u8,
#[size_bits = 8] #[size_bits = 8]
spots: Vec<EncounterSpot>, spots: Vec<EncounterSpot>,
@ -169,6 +232,7 @@ pub struct EncounterSpot {
distance: u8, // divide by 255 distance: u8, // divide by 255
} }
/// The light intensity at the four corners of an area
#[derive(Debug, BitRead, Default)] #[derive(Debug, BitRead, Default)]
pub struct LightIntensity { pub struct LightIntensity {
pub north_west: f32, pub north_west: f32,
@ -177,6 +241,7 @@ pub struct LightIntensity {
pub south_east: f32, pub south_east: f32,
} }
/// An area that is visible
#[derive(Debug, BitRead)] #[derive(Debug, BitRead)]
pub struct VisibleArea { pub struct VisibleArea {
id: u32, id: u32,

View file

@ -2,20 +2,24 @@ pub use crate::navmesh::NavArea;
use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian}; use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
use err_derive::Error; use err_derive::Error;
/// Errors that can occur when parsing the binary nav file
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ParseError { pub enum ParseError {
/// An error ocured when reading from the source binary data
#[error(display = "Error while reading from data: {}", _0)] #[error(display = "Error while reading from data: {}", _0)]
ReadError(#[error(source)] bitbuffer::ReadError), ReadError(#[error(source)] bitbuffer::ReadError),
#[error( #[error(
display = "Invalid magic number ({:#8X}), not a nav file or corrupted", display = "Invalid magic number ({:#8X}), not a nav file or corrupted",
_0 _0
)] )]
/// The binary data contained an invalid magic number and is probably not a nav file
InvalidMagicNumber(u32), InvalidMagicNumber(u32),
/// The version of the nav file is not supported by this parser
#[error(display = "The major version for this nav ({}), is not supported", _0)] #[error(display = "The major version for this nav ({}), is not supported", _0)]
UnsupportedVersion(u32), UnsupportedVersion(u32),
} }
pub fn read_areas(data: Vec<u8>) -> Result<Vec<NavArea>, ParseError> { pub(crate) fn read_areas(data: Vec<u8>) -> Result<Vec<NavArea>, ParseError> {
let mut data = BitReadStream::new(BitReadBuffer::new(data, LittleEndian)); let mut data = BitReadStream::new(BitReadBuffer::new(data, LittleEndian));
let magic = data.read()?; let magic = data.read()?;
if magic != 0xFEEDFACE { if magic != 0xFEEDFACE {