mirror of
https://codeberg.org/icewind/sourcenav.git
synced 2026-06-03 10:14:11 +02:00
some documentations
This commit is contained in:
parent
ddcf105394
commit
4c48efc5cc
5 changed files with 178 additions and 26 deletions
|
|
@ -10,4 +10,7 @@ edition = "2018"
|
|||
bitbuffer = "0.7.1"
|
||||
err-derive = "0.2.4"
|
||||
aabb-quadtree = "0.2.0"
|
||||
euclid = "0.19"
|
||||
euclid = "0.19"
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3.3"
|
||||
22
README.md
Normal file
22
README.md
Normal 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(())
|
||||
}
|
||||
|
||||
```
|
||||
86
src/lib.rs
86
src/lib.rs
|
|
@ -1,4 +1,8 @@
|
|||
use crate::navmesh::HammerUnit;
|
||||
pub use crate::navmesh::{
|
||||
ApproachArea, Connections, EncounterPath, LadderConnections, LadderDirection, LightIntensity,
|
||||
NavDirection, NavHidingSpot, Vector3, VisibleArea,
|
||||
};
|
||||
use crate::parser::read_areas;
|
||||
pub use crate::parser::{NavArea, ParseError};
|
||||
use aabb_quadtree::{ItemId, QuadTree};
|
||||
|
|
@ -7,8 +11,22 @@ use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
|||
mod navmesh;
|
||||
mod parser;
|
||||
|
||||
/// A tree of all navigation areas
|
||||
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> {
|
||||
let areas = read_areas(data)?;
|
||||
|
||||
|
|
@ -40,19 +58,60 @@ pub fn get_area_tree(data: Vec<u8>) -> Result<NavTree, ParseError> {
|
|||
}
|
||||
|
||||
impl NavTree {
|
||||
pub fn query(
|
||||
&self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
) -> impl Iterator<Item = (&NavArea, TypedRect<f32, HammerUnit>, ItemId)> {
|
||||
/// Find the navigation areas at a x/y cooordinate
|
||||
///
|
||||
/// ## 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 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));
|
||||
|
||||
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 {
|
||||
self.query(x, y)
|
||||
.map(move |(area, ..)| area.get_z_height(x, y))
|
||||
self.query(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!(
|
||||
tree.query(point3.0, point3.1)
|
||||
.next()
|
||||
.map(|(area, ..)| area.id),
|
||||
tree.query(point4.0, point4.1)
|
||||
.next()
|
||||
.map(|(area, ..)| area.id)
|
||||
tree.query(point3.0, point3.1).next().map(|area| area.id),
|
||||
tree.query(point4.0, point4.1).next().map(|area| area.id)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(doctest)]
|
||||
doc_comment::doctest!("../README.md");
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@ use bitbuffer::{BitRead, BitReadStream, Endianness, ReadError};
|
|||
use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
||||
use std::ops::Index;
|
||||
|
||||
/// A 3 dimensional coordinate
|
||||
#[derive(Debug, BitRead)]
|
||||
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)]
|
||||
pub struct NavArea {
|
||||
pub id: u32,
|
||||
pub id: NavAreaId,
|
||||
pub north_west: Vector3,
|
||||
pub south_east: Vector3,
|
||||
pub north_east_z: f32,
|
||||
|
|
@ -35,6 +41,22 @@ impl NavArea {
|
|||
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 {
|
||||
let from_east = self.south_east.0 - x;
|
||||
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 {
|
||||
fn aabb(&self) -> TypedRect<f32, HammerUnit> {
|
||||
|
|
@ -65,8 +87,26 @@ impl Spatial<HammerUnit> for NavArea {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connections([Vec<u32>; 4]);
|
||||
/// The connections from a navigation area into it's neighbours
|
||||
///
|
||||
/// 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 {
|
||||
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 {
|
||||
type Output = Vec<u32>;
|
||||
type Output = Vec<NavAreaId>;
|
||||
|
||||
fn index(&self, index: NavDirection) -> &Self::Output {
|
||||
&self.0[index as u8 as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LadderConnections([Vec<u32>; 2]);
|
||||
/// The connections from a navigation area into it's neighbours
|
||||
///
|
||||
/// 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 {
|
||||
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 {
|
||||
type Output = Vec<u32>;
|
||||
type Output = Vec<NavAreaId>;
|
||||
|
||||
fn index(&self, index: LadderDirection) -> &Self::Output {
|
||||
&self.0[index as u8 as usize]
|
||||
}
|
||||
}
|
||||
|
||||
/// The directions in which two areas can be connected
|
||||
#[derive(Debug, BitRead)]
|
||||
#[repr(u8)]
|
||||
#[discriminant_bits = 8]
|
||||
|
|
@ -129,6 +188,7 @@ pub enum NavDirection {
|
|||
West,
|
||||
}
|
||||
|
||||
/// The directions in which two areas can be connected by ladder
|
||||
#[derive(Debug, BitRead)]
|
||||
#[repr(u8)]
|
||||
#[discriminant_bits = 8]
|
||||
|
|
@ -137,6 +197,7 @@ pub enum LadderDirection {
|
|||
Down,
|
||||
}
|
||||
|
||||
/// A hiding spot within an area
|
||||
#[derive(Debug, BitRead)]
|
||||
pub struct NavHidingSpot {
|
||||
id: u32,
|
||||
|
|
@ -144,6 +205,7 @@ pub struct NavHidingSpot {
|
|||
flags: u8,
|
||||
}
|
||||
|
||||
/// An area that can be used for approach, no longer used in newer nav files
|
||||
#[derive(Debug, BitRead)]
|
||||
pub struct ApproachArea {
|
||||
approach_here: u32,
|
||||
|
|
@ -153,11 +215,12 @@ pub struct ApproachArea {
|
|||
approach_how: u8,
|
||||
}
|
||||
|
||||
/// A path that can be used to approach an area
|
||||
#[derive(Debug, BitRead)]
|
||||
pub struct EncounterPath {
|
||||
from_area_id: u32,
|
||||
from_area_id: NavAreaId,
|
||||
from_direction: u8,
|
||||
to_area_id: u32,
|
||||
to_area_id: NavAreaId,
|
||||
to_direction: u8,
|
||||
#[size_bits = 8]
|
||||
spots: Vec<EncounterSpot>,
|
||||
|
|
@ -169,6 +232,7 @@ pub struct EncounterSpot {
|
|||
distance: u8, // divide by 255
|
||||
}
|
||||
|
||||
/// The light intensity at the four corners of an area
|
||||
#[derive(Debug, BitRead, Default)]
|
||||
pub struct LightIntensity {
|
||||
pub north_west: f32,
|
||||
|
|
@ -177,6 +241,7 @@ pub struct LightIntensity {
|
|||
pub south_east: f32,
|
||||
}
|
||||
|
||||
/// An area that is visible
|
||||
#[derive(Debug, BitRead)]
|
||||
pub struct VisibleArea {
|
||||
id: u32,
|
||||
|
|
|
|||
|
|
@ -2,20 +2,24 @@ pub use crate::navmesh::NavArea;
|
|||
use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
|
||||
use err_derive::Error;
|
||||
|
||||
/// Errors that can occur when parsing the binary nav file
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseError {
|
||||
/// An error ocured when reading from the source binary data
|
||||
#[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
|
||||
)]
|
||||
/// The binary data contained an invalid magic number and is probably not a nav file
|
||||
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)]
|
||||
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 magic = data.read()?;
|
||||
if magic != 0xFEEDFACE {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue