mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-04 02:54:08 +02:00
450 lines
13 KiB
Rust
450 lines
13 KiB
Rust
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<T> = Result<T, BspError>;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Handle<'a, T> {
|
|
bsp: &'a Bsp,
|
|
data: &'a T,
|
|
}
|
|
|
|
impl<T> 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<T> Deref for Handle<'_, T> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.data
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Leaves {
|
|
leaves: Vec<Leaf>,
|
|
}
|
|
|
|
impl Leaves {
|
|
pub fn new(mut leaves: Vec<Leaf>) -> Self {
|
|
leaves.sort_unstable_by_key(|leaf| leaf.cluster);
|
|
|
|
Leaves { leaves }
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Leaf> {
|
|
self.into_iter()
|
|
}
|
|
|
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Leaf> {
|
|
self.into_iter()
|
|
}
|
|
|
|
pub fn into_inner(self) -> Vec<Leaf> {
|
|
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<i16, impl Iterator<Item = &'this Leaf>, impl FnMut(&&'this Leaf) -> i16> {
|
|
self.leaves.iter().group_by(|leaf: &&Leaf| leaf.cluster)
|
|
}
|
|
}
|
|
|
|
impl From<Vec<Leaf>> for Leaves {
|
|
fn from(other: Vec<Leaf>) -> Self {
|
|
Self::new(other)
|
|
}
|
|
}
|
|
|
|
impl Deref for Leaves {
|
|
type Target = [Leaf];
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.leaves
|
|
}
|
|
}
|
|
|
|
impl IntoIterator for Leaves {
|
|
type IntoIter = <Vec<Leaf> 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<TextureData>,
|
|
pub textures_info: Vec<TextureInfo>,
|
|
pub planes: Vec<Plane>,
|
|
pub nodes: Vec<Node>,
|
|
pub leaves: Leaves,
|
|
pub leaf_faces: Vec<LeafFace>,
|
|
pub leaf_brushes: Vec<LeafBrush>,
|
|
pub models: Vec<Model>,
|
|
pub brushes: Vec<Brush>,
|
|
pub brush_sides: Vec<BrushSide>,
|
|
pub vertices: Vec<Vertex>,
|
|
pub edges: Vec<Edge>,
|
|
pub surface_edges: Vec<SurfaceEdge>,
|
|
pub faces: Vec<Face>,
|
|
pub original_faces: Vec<Face>,
|
|
pub vis_data: VisData,
|
|
}
|
|
|
|
impl Bsp {
|
|
pub fn read(data: &[u8]) -> BspResult<Self> {
|
|
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<Handle<'_, Leaf>> {
|
|
self.leaves.get(n).map(|leaf| Handle {
|
|
bsp: self,
|
|
data: leaf,
|
|
})
|
|
}
|
|
|
|
pub fn plane(&self, n: usize) -> Option<Handle<'_, Plane>> {
|
|
self.planes.get(n).map(|plane| Handle {
|
|
bsp: self,
|
|
data: plane,
|
|
})
|
|
}
|
|
|
|
pub fn face(&self, n: usize) -> Option<Handle<'_, Face>> {
|
|
self.faces.get(n).map(|face| Handle {
|
|
bsp: self,
|
|
data: face,
|
|
})
|
|
}
|
|
|
|
pub fn node(&self, n: usize) -> Option<Handle<'_, Node>> {
|
|
self.nodes.get(n).map(|node| Handle {
|
|
bsp: self,
|
|
data: node,
|
|
})
|
|
}
|
|
|
|
pub fn root_node(&self) -> Option<Handle<'_, Node>> {
|
|
self.node(0)
|
|
}
|
|
|
|
pub fn models(&self) -> impl Iterator<Item = Handle<'_, Model>> {
|
|
self.models.iter().map(move |m| Handle::new(self, m))
|
|
}
|
|
|
|
pub fn leaf_at(&self, point: Vector) -> Option<Handle<'_, Leaf>> {
|
|
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<Item = Handle<Face>> {
|
|
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<Item = Handle<'a, Face>> {
|
|
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<Handle<TextureInfo>> {
|
|
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<Item = &'a Vertex> + '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<Item = u16> + '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<Handle<'_, Plane>> {
|
|
self.bsp.plane(self.plane_index as _)
|
|
}
|
|
}
|
|
|
|
impl<'a> Handle<'a, Leaf> {
|
|
pub fn visible_set(&self) -> Option<impl Iterator<Item = Handle<'a, Leaf>>> {
|
|
// 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<Item = Handle<'a, Face>> {
|
|
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();
|
|
}
|
|
}
|