mirror of
https://codeberg.org/icewind/sourcenav.git
synced 2026-06-03 18:24:10 +02:00
allow parsing just the nav quads
This commit is contained in:
parent
d3765674d4
commit
99e24bc9b8
5 changed files with 239 additions and 96 deletions
|
|
@ -8,11 +8,11 @@ This library is currently focused on getting the z-height from an x/y coordinate
|
|||
that usage. For other usages the raw navigation areas are exposed.
|
||||
|
||||
```rust
|
||||
use sourcenav::get_area_tree;
|
||||
use sourcenav::get_quad_tree;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = std::fs::read("data/pl_badwater.nav")?;
|
||||
let tree = get_area_tree(file)?;
|
||||
let tree = get_quad_tree(file)?;
|
||||
|
||||
assert_eq!(220.83125, tree.find_best_height(320.0, -1030.0, 0.0));
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,26 @@ extern crate test;
|
|||
use std::fs;
|
||||
|
||||
use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian};
|
||||
use sourcenav::get_area_tree;
|
||||
use sourcenav::{get_quad_tree, read_areas};
|
||||
use std::fs::read;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_badwater(b: &mut Bencher) {
|
||||
fn bench_badwater_areas(b: &mut Bencher) {
|
||||
let file = read("data/pl_badwater.nav").unwrap();
|
||||
let data = BitReadStream::new(BitReadBuffer::new(file, LittleEndian));
|
||||
|
||||
b.iter(|| {
|
||||
test::black_box(get_area_tree(data.clone()));
|
||||
test::black_box(read_areas(data.clone()));
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_badwater_quads(b: &mut Bencher) {
|
||||
let file = read("data/pl_badwater.nav").unwrap();
|
||||
let data = BitReadStream::new(BitReadBuffer::new(file, LittleEndian));
|
||||
|
||||
b.iter(|| {
|
||||
test::black_box(get_quad_tree(data.clone()));
|
||||
})
|
||||
}
|
||||
|
|
|
|||
51
src/lib.rs
51
src/lib.rs
|
|
@ -1,10 +1,10 @@
|
|||
use crate::navmesh::HammerUnit;
|
||||
pub use crate::navmesh::{
|
||||
ApproachArea, Connections, EncounterPath, LadderConnections, LadderDirection, LightIntensity,
|
||||
NavDirection, NavHidingSpot, Vector3, VisibleArea,
|
||||
NavDirection, NavHidingSpot, NavQuad, Vector3, VisibleArea,
|
||||
};
|
||||
use crate::parser::read_areas;
|
||||
pub use crate::parser::{NavArea, ParseError};
|
||||
use crate::parser::read_quads;
|
||||
pub use crate::parser::{read_areas, NavArea, ParseError};
|
||||
use aabb_quadtree::{ItemId, QuadTree};
|
||||
use bitbuffer::{BitReadStream, LittleEndian};
|
||||
use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
||||
|
|
@ -15,23 +15,25 @@ mod parser;
|
|||
type Rect = TypedRect<f32, HammerUnit>;
|
||||
|
||||
/// A tree of all navigation areas
|
||||
pub struct NavTree(QuadTree<NavArea, HammerUnit, [(ItemId, Rect); 4]>);
|
||||
pub struct NavQuadTree(QuadTree<NavQuad, HammerUnit, [(ItemId, Rect); 4]>);
|
||||
|
||||
/// Parse all navigation areas from a nav file
|
||||
/// Parse all navigation quads from a nav file
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_area_tree;
|
||||
/// use sourcenav::get_quad_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 tree = get_quad_tree(file)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_area_tree(data: impl Into<BitReadStream<LittleEndian>>) -> Result<NavTree, ParseError> {
|
||||
let areas = read_areas(data.into())?;
|
||||
pub fn get_quad_tree(
|
||||
data: impl Into<BitReadStream<LittleEndian>>,
|
||||
) -> Result<NavQuadTree, ParseError> {
|
||||
let areas = read_quads(data.into())?;
|
||||
|
||||
let (min_x, min_y, max_x, max_y) = areas.iter().fold(
|
||||
(f32::MAX, f32::MAX, f32::MIN, f32::MIN),
|
||||
|
|
@ -57,25 +59,25 @@ pub fn get_area_tree(data: impl Into<BitReadStream<LittleEndian>>) -> Result<Nav
|
|||
tree.insert(area);
|
||||
}
|
||||
|
||||
Ok(NavTree(tree))
|
||||
Ok(NavQuadTree(tree))
|
||||
}
|
||||
|
||||
impl NavTree {
|
||||
impl NavQuadTree {
|
||||
/// Find the navigation areas at a x/y cooordinate
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_area_tree;
|
||||
/// use sourcenav::get_quad_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 tree = get_quad_tree(file)?;
|
||||
/// let areas = tree.query(150.0, -312.0);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn query(&self, x: f32, y: f32) -> impl Iterator<Item = &NavArea> {
|
||||
pub fn query(&self, x: f32, y: f32) -> impl Iterator<Item = &NavQuad> {
|
||||
let query_box = Rect::new(TypedPoint2D::new(x, y), TypedSize2D::new(1.0, 1.0));
|
||||
|
||||
self.0.query(query_box).into_iter().map(|(area, ..)| area)
|
||||
|
|
@ -88,11 +90,11 @@ impl NavTree {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_area_tree;
|
||||
/// use sourcenav::get_quad_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 tree = get_quad_tree(file)?;
|
||||
/// let heights = tree.find_z_height(150.0, -312.0);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
|
@ -122,18 +124,18 @@ impl NavTree {
|
|||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_area_tree;
|
||||
/// use sourcenav::get_quad_tree;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_area_tree(file)?;
|
||||
/// for area in tree.areas() {
|
||||
/// println!("area: {}", area.id)
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// for quad in tree.quads() {
|
||||
/// println!("area: {:?}", quad)
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn areas(&self) -> impl Iterator<Item = &NavArea> {
|
||||
pub fn quads(&self) -> impl Iterator<Item = &NavQuad> {
|
||||
self.0.iter().map(|(_, (area, _))| area)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +143,7 @@ impl NavTree {
|
|||
#[test]
|
||||
fn test_tree() {
|
||||
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||
let tree = get_area_tree(file).unwrap();
|
||||
let tree = get_quad_tree(file).unwrap();
|
||||
|
||||
// single flat plane
|
||||
let point1 = (1600.0, -1300.0);
|
||||
|
|
@ -174,11 +176,6 @@ fn test_tree() {
|
|||
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)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(doctest)]
|
||||
|
|
|
|||
133
src/navmesh.rs
133
src/navmesh.rs
|
|
@ -24,10 +24,7 @@ impl fmt::Display for NavAreaId {
|
|||
#[derive(Debug)]
|
||||
pub struct NavArea {
|
||||
pub id: NavAreaId,
|
||||
pub north_west: Vector3,
|
||||
pub south_east: Vector3,
|
||||
pub north_east_z: f32,
|
||||
pub south_west_z: f32,
|
||||
pub quad: NavQuad,
|
||||
pub flags: u32,
|
||||
pub connections: Connections,
|
||||
pub hiding_spots: Vec<NavHidingSpot>,
|
||||
|
|
@ -42,60 +39,8 @@ pub struct NavArea {
|
|||
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
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
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(crate) struct HammerUnit;
|
||||
|
||||
impl Spatial<HammerUnit> for NavArea {
|
||||
fn aabb(&self) -> Rect {
|
||||
Rect {
|
||||
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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The connections from a navigation area into it's neighbours
|
||||
///
|
||||
/// Contains a list of area id's for every [`NavDirection`]
|
||||
|
|
@ -131,6 +76,14 @@ impl<E: Endianness> BitRead<E> for Connections {
|
|||
|
||||
Ok(Connections(connections))
|
||||
}
|
||||
|
||||
fn skip(stream: &mut BitReadStream<E>) -> Result<(), ReadError> {
|
||||
for _ in 0..4 {
|
||||
let connection_count: u32 = stream.read()?;
|
||||
stream.skip_bits(connection_count as usize * 32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<NavDirection> for Connections {
|
||||
|
|
@ -176,6 +129,14 @@ impl<E: Endianness> BitRead<E> for LadderConnections {
|
|||
|
||||
Ok(LadderConnections(connections))
|
||||
}
|
||||
|
||||
fn skip(stream: &mut BitReadStream<E>) -> Result<(), ReadError> {
|
||||
for _ in 0..2 {
|
||||
let connection_count: u32 = stream.read()?;
|
||||
stream.skip_bits(connection_count as usize * 32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<LadderDirection> for LadderConnections {
|
||||
|
|
@ -263,5 +224,63 @@ pub struct NavPlace {
|
|||
name: String,
|
||||
}
|
||||
|
||||
/// A navigation area from the nav file
|
||||
#[derive(Debug)]
|
||||
pub struct NavMesh {}
|
||||
pub struct NavQuad {
|
||||
pub north_west: Vector3,
|
||||
pub south_east: Vector3,
|
||||
pub north_east_z: f32,
|
||||
pub south_west_z: f32,
|
||||
}
|
||||
|
||||
impl NavQuad {
|
||||
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
|
||||
}
|
||||
|
||||
/// Get the z height of a x/y point inside the navigation area
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_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;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl Spatial<HammerUnit> for NavQuad {
|
||||
fn aabb(&self) -> Rect {
|
||||
Rect {
|
||||
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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
src/parser.rs
131
src/parser.rs
|
|
@ -1,5 +1,7 @@
|
|||
pub use crate::navmesh::NavArea;
|
||||
use bitbuffer::{BitReadStream, LittleEndian};
|
||||
use crate::navmesh::NavQuad;
|
||||
use crate::{Connections, EncounterPath, LadderConnections, NavHidingSpot, VisibleArea};
|
||||
use bitbuffer::{BitRead, BitReadStream, LittleEndian};
|
||||
use err_derive::Error;
|
||||
|
||||
/// Errors that can occur when parsing the binary nav file
|
||||
|
|
@ -19,9 +21,23 @@ pub enum ParseError {
|
|||
UnsupportedVersion(u32),
|
||||
}
|
||||
|
||||
pub(crate) fn read_areas(
|
||||
mut data: BitReadStream<LittleEndian>,
|
||||
/// Parse all navigation areas from a nav file
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::read_areas;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = read_areas(file)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn read_areas(
|
||||
data: impl Into<BitReadStream<LittleEndian>>,
|
||||
) -> Result<Vec<NavArea>, ParseError> {
|
||||
let mut data = data.into();
|
||||
let magic = data.read()?;
|
||||
if magic != 0xFEED_FACE {
|
||||
return Err(ParseError::InvalidMagicNumber(magic));
|
||||
|
|
@ -120,10 +136,12 @@ pub(crate) fn read_areas(
|
|||
|
||||
areas.push(NavArea {
|
||||
id,
|
||||
north_west,
|
||||
south_east,
|
||||
north_east_z,
|
||||
south_west_z,
|
||||
quad: NavQuad {
|
||||
north_west,
|
||||
south_east,
|
||||
north_east_z,
|
||||
south_west_z,
|
||||
},
|
||||
flags,
|
||||
connections,
|
||||
hiding_spots,
|
||||
|
|
@ -144,6 +162,97 @@ pub(crate) fn read_areas(
|
|||
Ok(areas)
|
||||
}
|
||||
|
||||
pub(crate) fn read_quads(
|
||||
mut data: BitReadStream<LittleEndian>,
|
||||
) -> Result<Vec<NavQuad>, ParseError> {
|
||||
let magic = data.read()?;
|
||||
if magic != 0xFEED_FACE {
|
||||
return Err(ParseError::InvalidMagicNumber(magic));
|
||||
}
|
||||
|
||||
let major_version: u32 = data.read()?;
|
||||
|
||||
if 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 {
|
||||
data.skip_bits(32 * 2)?; // id and flags
|
||||
|
||||
let north_west = data.read()?;
|
||||
let south_east = data.read()?;
|
||||
let north_east_z = data.read()?;
|
||||
let south_west_z = data.read()?;
|
||||
|
||||
Connections::skip(&mut data)?;
|
||||
|
||||
let hiding_spots_count: u8 = data.read()?;
|
||||
data.skip_bits(
|
||||
<NavHidingSpot as BitRead<LittleEndian>>::bit_size().unwrap()
|
||||
* hiding_spots_count as usize,
|
||||
)?;
|
||||
|
||||
let encounter_paths_count: u32 = data.read()?;
|
||||
for _ in 0..encounter_paths_count {
|
||||
EncounterPath::skip(&mut data)?;
|
||||
}
|
||||
|
||||
data.skip_bits(16)?; // place
|
||||
|
||||
LadderConnections::skip(&mut data)?;
|
||||
|
||||
data.skip_bits((32 * 2) + (32 * 4))?; // occupy time, light intensity
|
||||
|
||||
let visible_areas_count: u32 = data.read()?;
|
||||
data.skip_bits(
|
||||
<VisibleArea as BitRead<LittleEndian>>::bit_size().unwrap()
|
||||
* visible_areas_count as usize,
|
||||
)?;
|
||||
|
||||
data.skip_bits(32 * 2)?; // inherit visible, garbage
|
||||
|
||||
areas.push(NavQuad {
|
||||
north_west,
|
||||
south_east,
|
||||
north_east_z,
|
||||
south_west_z,
|
||||
});
|
||||
}
|
||||
|
||||
debug_assert!(data.bits_left() <= 32);
|
||||
|
||||
Ok(areas)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||
|
|
@ -151,3 +260,11 @@ fn test() {
|
|||
let areas = read_areas(data).unwrap();
|
||||
assert_eq!(1930, areas.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quads() {
|
||||
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||
let data = BitReadStream::new(bitbuffer::BitReadBuffer::new(file, LittleEndian));
|
||||
let quads = read_quads(data).unwrap();
|
||||
assert_eq!(1930, quads.len());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue