mod bspfile; pub mod data; mod reader; use crate::bspfile::LumpType; pub use crate::data::TextureFlags; pub use crate::data::Vector; use crate::data::*; use binread::io::Cursor; use binread::BinRead; use bspfile::BspFile; use itertools::{GroupBy, Itertools}; use reader::LumpReader; use std::{io::Read, ops::Deref}; use thiserror::Error; #[derive(Debug, Error)] pub enum BspError { #[error("unexpected magic numbers or version, is this a valve bsp?")] UnexpectedHeader(Header), #[error("bsp lump is out of bounds of the bsp file")] LumpOutOfBounds(LumpEntry), #[error("unexpected length of uncompressed lump, got {got} but expected {expected}")] UnexpectedUncompressedLumpSize { got: u32, expected: u32 }, #[error("error while decompressing lump")] LumpDecompressError(lzma_rs::error::Error), #[error("malformed utf8 data")] Utf8Error(#[from] std::string::FromUtf8Error), #[error("Directory entry length isn't a multiple of element size")] MalformedLump, #[error("invalid surface flag in {0}")] InvalidSurfaceFlag(Name), #[error("invalid content flag in {0}")] InvalidContentFlag(Name), #[error("non null-terminated name")] InvalidName, #[error("unexpected eof while reading data")] UnexpectedEOF, #[error("extra data at the end of the lump")] UnexpectedExtraData, #[error("error while reading data: {0}")] ReadError(#[from] std::io::Error), #[error("error while reading data: {0}")] BinReadError(#[from] binread::Error), } pub type BspResult = Result; #[derive(Debug)] pub struct Handle<'a, T> { bsp: &'a Bsp, data: &'a T, } impl Clone for Handle<'_, T> { fn clone(&self) -> Self { Handle { ..*self } } } impl<'a, T> Handle<'a, T> { pub fn as_ref(&self) -> &'a T { self.data } } impl Deref for Handle<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { self.data } } #[derive(Debug, Clone)] pub struct Leaves { leaves: Vec, } impl Leaves { pub fn new(mut leaves: Vec) -> Self { leaves.sort_unstable_by_key(|leaf| leaf.cluster); Leaves { leaves } } pub fn iter(&self) -> impl Iterator { self.into_iter() } pub fn iter_mut(&mut self) -> impl Iterator { self.into_iter() } pub fn into_inner(self) -> Vec { self.leaves } // TODO: There's no syntax for `-> T where &T: IntoIterator<...>` and `GroupBy` // doesn't implement `IntoIterator` directly, only `&GroupBy`, so we have // to explicitly specify the type. pub fn clusters<'this>( &'this self, ) -> GroupBy, impl FnMut(&&'this Leaf) -> i16> { self.leaves.iter().group_by(|leaf: &&Leaf| leaf.cluster) } } impl From> for Leaves { fn from(other: Vec) -> Self { Self::new(other) } } impl Deref for Leaves { type Target = [Leaf]; fn deref(&self) -> &Self::Target { &self.leaves } } impl IntoIterator for Leaves { type IntoIter = as IntoIterator>::IntoIter; type Item = Leaf; fn into_iter(self) -> Self::IntoIter { self.leaves.into_iter() } } impl<'a> IntoIterator for &'a Leaves { type IntoIter = <&'a [Leaf] as IntoIterator>::IntoIter; type Item = &'a Leaf; fn into_iter(self) -> Self::IntoIter { (&self.leaves[..]).into_iter() } } impl<'a> IntoIterator for &'a mut Leaves { type IntoIter = <&'a mut [Leaf] as IntoIterator>::IntoIter; type Item = &'a mut Leaf; fn into_iter(self) -> Self::IntoIter { (&mut self.leaves[..]).into_iter() } } // TODO: Store all the allocated objects inline to improve cache usage #[derive(Debug)] pub struct Bsp { pub header: Header, pub entities: Entities, pub textures_data: Vec, pub textures_info: Vec, pub planes: Vec, pub nodes: Vec, pub leaves: Leaves, pub leaf_faces: Vec, pub leaf_brushes: Vec, pub models: Vec, pub brushes: Vec, pub brush_sides: Vec, pub vertices: Vec, pub edges: Vec, pub surface_edges: Vec, pub faces: Vec, pub original_faces: Vec, pub vis_data: VisData, } impl Bsp { pub fn read(data: &[u8]) -> BspResult { let bsp_file = BspFile::new(data)?; let entities = bsp_file.lump_reader(LumpType::Entities)?.read_entities()?; let textures_data = bsp_file .lump_reader(LumpType::TextureData)? .read_vec(|r| r.read())?; let textures_info = bsp_file .lump_reader(LumpType::TextureInfo)? .read_vec(|r| r.read())?; let planes = bsp_file .lump_reader(LumpType::Planes)? .read_vec(|r| r.read())?; let nodes = bsp_file .lump_reader(LumpType::Nodes)? .read_vec(|r| r.read())?; let leaves = bsp_file .lump_reader(LumpType::Leaves)? .read_vec(|r| r.read())? .into(); let leaf_faces = bsp_file .lump_reader(LumpType::LeafFaces)? .read_vec(|r| r.read())?; let leaf_brushes = bsp_file .lump_reader(LumpType::LeafBrushes)? .read_vec(|r| r.read())?; let models = bsp_file .lump_reader(LumpType::Models)? .read_vec(|r| r.read())?; let brushes = bsp_file .lump_reader(LumpType::Brushes)? .read_vec(|r| r.read())?; let brush_sides = bsp_file .lump_reader(LumpType::BrushSides)? .read_vec(|r| r.read())?; let vertices = bsp_file .lump_reader(LumpType::Vertices)? .read_vec(|r| r.read())?; let edges = bsp_file .lump_reader(LumpType::Edges)? .read_vec(|r| r.read())?; let surface_edges = bsp_file .lump_reader(LumpType::SurfaceEdges)? .read_vec(|r| r.read())?; let faces = bsp_file .lump_reader(LumpType::Faces)? .read_vec(|r| r.read())?; let original_faces = bsp_file .lump_reader(LumpType::OriginalFaces)? .read_vec(|r| r.read())?; let vis_data = bsp_file.lump_reader(LumpType::Visibility)?.read_visdata()?; Ok({ Bsp { header: bsp_file.header().clone(), entities, textures_data, textures_info, planes, nodes, leaves, leaf_faces, leaf_brushes, models, brushes, brush_sides, vertices, edges, surface_edges, faces, original_faces, vis_data, } }) } pub fn leaf(&self, n: usize) -> Option> { self.leaves.get(n).map(|leaf| Handle { bsp: self, data: leaf, }) } pub fn plane(&self, n: usize) -> Option> { self.planes.get(n).map(|plane| Handle { bsp: self, data: plane, }) } pub fn face(&self, n: usize) -> Option> { self.faces.get(n).map(|face| Handle { bsp: self, data: face, }) } pub fn node(&self, n: usize) -> Option> { self.nodes.get(n).map(|node| Handle { bsp: self, data: node, }) } pub fn root_node(&self) -> Option> { self.node(0) } pub fn models(&self) -> impl Iterator> { self.models.iter().map(move |m| Handle::new(self, m)) } pub fn leaf_at(&self, point: Vector) -> Option> { let mut current = self.root_node()?; loop { let plane = current.plane()?; let dot: f32 = point .iter() .zip(plane.normal.iter()) .map(|(a, b)| a * b) .sum(); let [front, back] = current.children; let next = if dot < plane.dist { back } else { front }; if next < 0 { return self.leaf((!next) as usize); } else { current = self.node(next as usize)?; } } } pub fn original_faces(&self) -> impl Iterator> { self.faces.iter().map(move |face| Handle { bsp: self, data: face, }) } } impl<'a, T> Handle<'a, T> { pub fn new(bsp: &'a Bsp, data: &'a T) -> Self { Handle { bsp, data } } } impl<'a> Handle<'a, Model> { pub fn faces(&self) -> impl Iterator> { let start = self.first_face as usize; let end = start + self.face_count as usize; let bsp = self.bsp; bsp.faces[start..end] .iter() .map(move |face| Handle::new(bsp, face)) } } impl<'a> Handle<'a, TextureInfo> { pub fn texture(&self) -> Option<&TextureData> { self.bsp .textures_data .get(self.data.texture_data_index as usize) } } impl<'a> Handle<'a, Face> { pub fn texture(&self) -> Option> { self.bsp .textures_info .get(self.texture_info as usize) .map(|texture_info| Handle { bsp: self.bsp, data: texture_info, }) } pub fn vertices(&self) -> impl Iterator + 'a { let bsp = self.bsp; self.vertex_indexes() .flat_map(move |vert_index| bsp.vertices.get(vert_index as usize)) } pub fn vertex_indexes(&self) -> impl Iterator + 'a { let bsp = self.bsp; (self.data.first_edge..(self.data.first_edge + self.data.num_edges as i32)) .flat_map(move |surface_edge| bsp.surface_edges.get(surface_edge as usize)) .flat_map(move |surface_edge| { bsp.edges .get(surface_edge.edge_index()) .map(|edge| (edge, surface_edge.direction())) }) .map(|(edge, direction)| match direction { EdgeDirection::FirstToLast => edge.start_index, EdgeDirection::LastToFirst => edge.end_index, }) } pub fn is_visible(&self) -> bool { self.texture() .map(|texture| { !texture.flags.intersects( TextureFlags::LIGHT | TextureFlags::SKY2D | TextureFlags::SKY | TextureFlags::WARP | TextureFlags::TRANS | TextureFlags::TRIGGER | TextureFlags::HINT | TextureFlags::SKIP | TextureFlags::NODRAW | TextureFlags::HITBOX, ) }) .unwrap_or_default() } } impl Handle<'_, Node> { pub fn plane(&self) -> Option> { self.bsp.plane(self.plane_index as _) } } impl<'a> Handle<'a, Leaf> { pub fn visible_set(&self) -> Option>> { // TODO: Use `itertools::Either`? let cluster = self.cluster; let bsp = self.bsp; if cluster < 0 { None } else { let visible_clusters = bsp.vis_data.visible_clusters(cluster); Some( bsp.leaves .iter() .filter(move |leaf| { if leaf.cluster == cluster { true } else if leaf.cluster > 0 { visible_clusters[leaf.cluster as u64] } else { false } }) .map(move |leaf| Handle { bsp, data: leaf }), ) } } pub fn faces(&self) -> impl Iterator> { let start = self.first_leaf_face as usize; let end = start + self.leaf_face_count as usize; let bsp = self.bsp; bsp.leaf_faces[start..end] .iter() .filter_map(move |leaf_face| bsp.face(leaf_face.face as usize)) } } #[cfg(test)] mod tests { use super::Bsp; #[test] fn tf2_file() { use std::fs::read; let data = read("koth_bagel_rc2a.bsp").unwrap(); Bsp::read(&data).unwrap(); } }