mirror of
https://codeberg.org/icewind/sourcenav.git
synced 2026-06-04 02:34:11 +02:00
finish binrw rework
This commit is contained in:
parent
3adbbe761c
commit
048a2e7dbe
7 changed files with 703 additions and 271 deletions
23
src/lib.rs
23
src/lib.rs
|
|
@ -3,10 +3,9 @@ pub use crate::navmesh::{
|
|||
ApproachArea, Connections, EncounterPath, LadderConnections, LadderDirection, LightIntensity,
|
||||
NavDirection, NavHidingSpot, NavQuad, Vector3, VisibleArea,
|
||||
};
|
||||
use crate::parser::read_quads;
|
||||
pub use crate::parser::{read_areas, NavArea, ParseError};
|
||||
use aabb_quadtree::{ItemId, QuadTree};
|
||||
use binread::io::{Read, Seek};
|
||||
use binrw::io::{Read, Seek};
|
||||
use euclid::{TypedPoint2D, TypedRect, TypedSize2D};
|
||||
|
||||
mod navmesh;
|
||||
|
|
@ -23,17 +22,18 @@ pub struct NavQuadTree(QuadTree<NavQuad, HammerUnit, [(ItemId, Rect); 4]>);
|
|||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// let tree = get_quad_tree(&mut Cursor::new(file))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_quad_tree<R: Read + Seek>(data: &mut R) -> Result<NavQuadTree, ParseError> {
|
||||
let areas = read_quads(data)?;
|
||||
let areas = read_areas(data)?;
|
||||
|
||||
let (min_x, min_y, max_x, max_y) = areas.iter().fold(
|
||||
let (min_x, min_y, max_x, max_y) = areas.iter().map(|area| &area.quad).fold(
|
||||
(f32::MAX, f32::MAX, f32::MIN, f32::MIN),
|
||||
|(min_x, min_y, max_x, max_y), area| {
|
||||
(
|
||||
|
|
@ -54,7 +54,7 @@ pub fn get_quad_tree<R: Read + Seek>(data: &mut R) -> Result<NavQuadTree, ParseE
|
|||
);
|
||||
|
||||
for area in areas {
|
||||
tree.insert(area);
|
||||
tree.insert(area.quad);
|
||||
}
|
||||
|
||||
Ok(NavQuadTree(tree))
|
||||
|
|
@ -67,10 +67,11 @@ impl NavQuadTree {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// let tree = get_quad_tree(&mut Cursor::new(file))?;
|
||||
/// let areas = tree.query(150.0, -312.0);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
|
@ -89,15 +90,16 @@ impl NavQuadTree {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// let tree = get_quad_tree(&mut Cursor::new(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(&self, x: f32, y: f32) -> impl Iterator<Item = f32> + '_ {
|
||||
self.query(x, y).map(move |area| area.get_z_height(x, y))
|
||||
}
|
||||
|
||||
|
|
@ -123,10 +125,11 @@ impl NavQuadTree {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// let tree = get_quad_tree(&mut Cursor::new(file))?;
|
||||
/// for quad in tree.quads() {
|
||||
/// println!("area: {:?}", quad)
|
||||
/// }
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use crate::Rect;
|
||||
use aabb_quadtree::Spatial;
|
||||
use binread::io::{Read, Seek};
|
||||
use binread::{BinRead, BinReaderExt, BinResult, ReadOptions};
|
||||
use binrw::io::{Read, Seek};
|
||||
use binrw::{BinRead, BinReaderExt, BinResult, Endian};
|
||||
use euclid::{TypedPoint2D, TypedSize2D};
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Index;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// A 3 dimensional coordinate
|
||||
#[derive(Debug, BinRead)]
|
||||
|
|
@ -21,12 +22,29 @@ impl fmt::Display for NavAreaId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NavAreaFlags(pub u32);
|
||||
|
||||
impl BinRead for NavAreaFlags {
|
||||
type Args<'a> = (u32,);
|
||||
|
||||
fn read_options<R: Read + Seek>(reader: &mut R, endian: Endian, args: Self::Args<'static>) -> BinResult<Self> {
|
||||
let flags = match args.0 {
|
||||
0..=8 => reader.read_type::<u8>(endian)? as u32,
|
||||
9..=12 => reader.read_type::<u16>(endian)? as u32,
|
||||
13.. => reader.read_type::<u32>(endian)?,
|
||||
};
|
||||
Ok(NavAreaFlags(flags))
|
||||
}
|
||||
}
|
||||
|
||||
/// A navigation area from the nav file
|
||||
#[derive(Debug, BinRead)]
|
||||
#[br(import(major_version: u32))]
|
||||
pub struct NavArea {
|
||||
pub id: NavAreaId,
|
||||
pub flags: u32,
|
||||
#[br(args(major_version))]
|
||||
pub flags: NavAreaFlags,
|
||||
pub quad: NavQuad,
|
||||
pub connections: Connections,
|
||||
pub hiding_spots_count: u8,
|
||||
|
|
@ -77,12 +95,12 @@ pub(crate) struct HammerUnit;
|
|||
pub struct Connections([Vec<NavAreaId>; 4]);
|
||||
|
||||
impl BinRead for Connections {
|
||||
type Args = ();
|
||||
type Args<'a> = ();
|
||||
|
||||
fn read_options<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
options: &ReadOptions,
|
||||
args: Self::Args,
|
||||
_endian: Endian,
|
||||
_args: Self::Args<'static>,
|
||||
) -> BinResult<Self> {
|
||||
let mut connections = [Vec::new(), Vec::new(), Vec::new(), Vec::new()];
|
||||
|
||||
|
|
@ -128,12 +146,12 @@ impl Index<NavDirection> for Connections {
|
|||
pub struct LadderConnections([Vec<NavAreaId>; 2]);
|
||||
|
||||
impl BinRead for LadderConnections {
|
||||
type Args = ();
|
||||
type Args<'a> = ();
|
||||
|
||||
fn read_options<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
options: &ReadOptions,
|
||||
args: Self::Args,
|
||||
_endian: Endian,
|
||||
_args: Self::Args<'static>,
|
||||
) -> BinResult<Self> {
|
||||
let mut connections = [Vec::new(), Vec::new()];
|
||||
|
||||
|
|
@ -160,6 +178,7 @@ impl Index<LadderDirection> for LadderConnections {
|
|||
/// The directions in which two areas can be connected
|
||||
#[derive(Debug, BinRead)]
|
||||
#[repr(u8)]
|
||||
#[br(repr =u8)]
|
||||
pub enum NavDirection {
|
||||
North,
|
||||
East,
|
||||
|
|
@ -170,6 +189,7 @@ pub enum NavDirection {
|
|||
/// The directions in which two areas can be connected by ladder
|
||||
#[derive(Debug, BinRead)]
|
||||
#[repr(u8)]
|
||||
#[br(repr =u8)]
|
||||
pub enum LadderDirection {
|
||||
Up,
|
||||
Down,
|
||||
|
|
@ -178,37 +198,37 @@ pub enum LadderDirection {
|
|||
/// A hiding spot within an area
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct NavHidingSpot {
|
||||
id: u32,
|
||||
location: Vector3,
|
||||
flags: u8,
|
||||
pub id: u32,
|
||||
pub location: Vector3,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
/// An area that can be used for approach, no longer used in newer nav files
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct ApproachArea {
|
||||
approach_here: u32,
|
||||
approach_pre: u32,
|
||||
approach_type: u8,
|
||||
approach_next: u32,
|
||||
approach_how: u8,
|
||||
pub approach_here: u32,
|
||||
pub approach_pre: u32,
|
||||
pub approach_type: u8,
|
||||
pub approach_next: u32,
|
||||
pub approach_how: u8,
|
||||
}
|
||||
|
||||
/// A path that can be used to approach an area
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct EncounterPath {
|
||||
from_area_id: NavAreaId,
|
||||
from_direction: u8,
|
||||
to_area_id: NavAreaId,
|
||||
to_direction: u8,
|
||||
spot_count: u8,
|
||||
pub from_area_id: NavAreaId,
|
||||
pub from_direction: u8,
|
||||
pub to_area_id: NavAreaId,
|
||||
pub to_direction: u8,
|
||||
pub spot_count: u8,
|
||||
#[br(count = spot_count)]
|
||||
spots: Vec<EncounterSpot>,
|
||||
pub spots: Vec<EncounterSpot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct EncounterSpot {
|
||||
order: u32,
|
||||
distance: u8, // divide by 255
|
||||
pub order: u32,
|
||||
pub distance: u8, // divide by 255
|
||||
}
|
||||
|
||||
/// The light intensity at the four corners of an area
|
||||
|
|
@ -223,14 +243,15 @@ pub struct LightIntensity {
|
|||
/// An area that is visible
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct VisibleArea {
|
||||
id: u32,
|
||||
attributes: u8,
|
||||
pub id: u32,
|
||||
pub attributes: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct NavPlace {
|
||||
id: u32,
|
||||
name: String,
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// A navigation area from the nav file
|
||||
|
|
@ -256,10 +277,11 @@ impl NavQuad {
|
|||
///
|
||||
/// ```no_run
|
||||
/// use sourcenav::get_quad_tree;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = get_quad_tree(file)?;
|
||||
/// let tree = get_quad_tree(&mut Cursor::new(file))?;
|
||||
/// let area = tree.query(150.0, -312.0).next().unwrap();
|
||||
///
|
||||
/// let height = area.get_z_height(150.0, -312.0);
|
||||
|
|
|
|||
154
src/parser.rs
154
src/parser.rs
|
|
@ -1,28 +1,34 @@
|
|||
pub use crate::navmesh::NavArea;
|
||||
use crate::navmesh::NavQuad;
|
||||
use crate::{Connections, EncounterPath, LadderConnections, NavHidingSpot, VisibleArea};
|
||||
use binread::io::{Read, Seek, SeekFrom};
|
||||
use binread::{BinRead, BinReaderExt, ReadOptions};
|
||||
use err_derive::Error;
|
||||
use std::mem::size_of;
|
||||
use binrw::io::{Read, Seek};
|
||||
use binrw::{BinRead, BinReaderExt};
|
||||
use thiserror::Error;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// 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)] binread::Error),
|
||||
#[error(
|
||||
display = "Invalid magic number ({:#8X}), not a nav file or corrupted",
|
||||
_0
|
||||
)]
|
||||
/// An error occurred when reading from the source binary data
|
||||
#[error("Error while reading from data: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// An error occurred when reading from the source binary data
|
||||
#[error("Error while reading from data: {0}")]
|
||||
ReadError(#[from] binrw::Error),
|
||||
/// The binary data contained an invalid magic number and is probably not a nav file
|
||||
#[error("Invalid magic number ({0:#8X}), not a nav file or corrupted")]
|
||||
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("The major version for this nav ({0}), is not supported")]
|
||||
UnsupportedVersion(u32),
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
#[allow(dead_code)]
|
||||
struct FixedString {
|
||||
string_len: u16,
|
||||
#[br(count = string_len)]
|
||||
string: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Parse all navigation areas from a nav file
|
||||
///
|
||||
/// ## Examples
|
||||
|
|
@ -31,12 +37,14 @@ pub enum ParseError {
|
|||
/// use sourcenav::read_areas;
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use std::io::Cursor;
|
||||
/// let file = std::fs::read("path/to/navfile.nav")?;
|
||||
/// let tree = read_areas(file)?;
|
||||
/// let tree = read_areas(&mut Cursor::new(file))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn read_areas<R: Read + Seek>(data: &mut R) -> Result<Vec<NavArea>, ParseError> {
|
||||
|
||||
let magic = data.read_le()?;
|
||||
if magic != 0xFEED_FACE {
|
||||
return Err(ParseError::InvalidMagicNumber(magic));
|
||||
|
|
@ -44,7 +52,7 @@ pub fn read_areas<R: Read + Seek>(data: &mut R) -> Result<Vec<NavArea>, ParseErr
|
|||
|
||||
let major_version: u32 = data.read_le()?;
|
||||
|
||||
if major_version < 6 || major_version > 16 {
|
||||
if !(6..=16).contains(&major_version) {
|
||||
return Err(ParseError::UnsupportedVersion(major_version));
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +65,7 @@ pub fn read_areas<R: Read + Seek>(data: &mut R) -> Result<Vec<NavArea>, ParseErr
|
|||
let _size: u32 = data.read_le()?;
|
||||
|
||||
let _is_analysed = if major_version >= 14 {
|
||||
data.read_le::<u8>(8)? == 1
|
||||
data.read_le::<u8>()? == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
@ -66,13 +74,12 @@ pub fn read_areas<R: Read + Seek>(data: &mut R) -> Result<Vec<NavArea>, ParseErr
|
|||
|
||||
// let places = Vec::with_capacity(place_count as usize);
|
||||
for _id in 1..=place_count {
|
||||
let name_length: u16 = data.read_le()?;
|
||||
let _name = data.read_string(Some(name_length as usize))?;
|
||||
let _name: FixedString = data.read_le()?;
|
||||
// TODO
|
||||
}
|
||||
|
||||
let _has_unnamed_areas = if major_version >= 12 {
|
||||
data.read_le::<u8>(8)? == 1
|
||||
data.read_le::<u8>()? == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
@ -82,117 +89,16 @@ pub fn read_areas<R: Read + Seek>(data: &mut R) -> Result<Vec<NavArea>, ParseErr
|
|||
let mut areas = Vec::with_capacity(area_count as usize);
|
||||
|
||||
for _ in 0..area_count {
|
||||
areas.push(data.read_le()?);
|
||||
areas.push(data.read_le_args((major_version,))?);
|
||||
}
|
||||
|
||||
debug_assert!(data.bits_left() <= 32);
|
||||
|
||||
Ok(areas)
|
||||
}
|
||||
|
||||
pub(crate) fn read_quads<R: Read + Seek>(data: &mut R) -> Result<Vec<NavQuad>, ParseError> {
|
||||
let magic = data.read_le()?;
|
||||
if magic != 0xFEED_FACE {
|
||||
return Err(ParseError::InvalidMagicNumber(magic));
|
||||
}
|
||||
|
||||
let major_version: u32 = data.read_le()?;
|
||||
|
||||
if major_version != 16 {
|
||||
return Err(ParseError::UnsupportedVersion(major_version));
|
||||
}
|
||||
|
||||
let _minor_version: u32 = if major_version >= 10 {
|
||||
data.read_le()?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let _size: u32 = data.read_le()?;
|
||||
|
||||
let _is_analysed = if major_version >= 14 {
|
||||
data.read_le::<u8>(8)? == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let place_count: u16 = data.read_le()?;
|
||||
|
||||
// let places = Vec::with_capacity(place_count as usize);
|
||||
for _id in 1..=place_count {
|
||||
let name_length: u16 = data.read_le()?;
|
||||
let _name = data.read_string(Some(name_length as usize))?;
|
||||
// TODO
|
||||
}
|
||||
|
||||
let _has_unnamed_areas = if major_version >= 12 {
|
||||
data.read_le::<u8>(8)? == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let area_count: u32 = data.read_le()?;
|
||||
|
||||
let mut areas = Vec::with_capacity(area_count as usize);
|
||||
|
||||
for _ in 0..area_count {
|
||||
data.seek(SeekFrom::Current(4 * 2))?; // id and flags
|
||||
|
||||
let north_west = data.read_le()?;
|
||||
let south_east = data.read_le()?;
|
||||
let north_east_z = data.read_le()?;
|
||||
let south_west_z = data.read_le()?;
|
||||
|
||||
Connections::skip(&mut data)?;
|
||||
|
||||
let hiding_spots_count: u8 = data.read_le()?;
|
||||
data.seek(SeekFrom::Current(
|
||||
size_of::<NavHidingSpot>() as i64 * hiding_spots_count as i64,
|
||||
))?;
|
||||
|
||||
let encounter_paths_count: u32 = data.read_le()?;
|
||||
for _ in 0..encounter_paths_count {
|
||||
EncounterPath::skip(&mut data)?;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Current(2))?;
|
||||
|
||||
let _: LadderConnections = data.read_le()?;
|
||||
|
||||
data.seek(SeekFrom::Current((4 * 2) + (4 * 4)))?; // occupy time, light intensity
|
||||
|
||||
let visible_areas_count: u32 = data.read_le()?;
|
||||
data.seek(SeekFrom::Current(
|
||||
size_of::<VisibleArea>() as i64 * visible_areas_count as i64,
|
||||
))?;
|
||||
|
||||
data.seek(SeekFrom::Current(4 * 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() {
|
||||
use std::io::Cursor;
|
||||
let file = std::fs::read("data/pl_badwater.nav").unwrap();
|
||||
let data = BitReadStream::new(bitbuffer::BitReadBuffer::new(file, LittleEndian));
|
||||
let areas = read_areas(data).unwrap();
|
||||
let areas = read_areas(&mut Cursor::new(file)).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