mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-03 18:54:05 +02:00
Be strict about the number of elements
This commit is contained in:
parent
1023df6228
commit
9b07ca77d6
2 changed files with 324 additions and 134 deletions
|
|
@ -10,6 +10,10 @@ description = "Crate to load BSP files efficiently - currently only works for Qu
|
|||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
byteorder = "0.5"
|
||||
arrayvec = "0.4"
|
||||
bitflags = "1.0"
|
||||
bv = "0.11"
|
||||
byteorder = "0.5"
|
||||
|
||||
[features]
|
||||
bench = []
|
||||
|
|
|
|||
316
src/lib.rs
316
src/lib.rs
|
|
@ -1,9 +1,16 @@
|
|||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
extern crate test;
|
||||
|
||||
use arrayvec::ArrayString;
|
||||
use bitflags::bitflags;
|
||||
use bv::BitVec;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Take},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
trait ElementSize {
|
||||
|
|
@ -165,59 +172,67 @@ impl<'a> Entity<'a> {
|
|||
|
||||
bitflags! {
|
||||
pub struct SurfFlags: u32 {
|
||||
const NODAMAGE = 0b000000000000000001; // Never give falling damage
|
||||
const SLICK = 0b000000000000000010; // Effects game physics
|
||||
const SKY = 0b000000000000000100; // Lighting from environment map
|
||||
const LADDER = 0b000000000000001000; // Climbable ladder
|
||||
const NOIMPACT = 0b000000000000010000; // Don't make missile explosions
|
||||
const NOMARKS = 0b000000000000100000; // Don't leave missile marks
|
||||
const FLESH = 0b000000000001000000; // Make flesh sounds and effects
|
||||
const NODRAW = 0b000000000010000000; // Don't generate a drawsurface at all
|
||||
const HINT = 0b000000000100000000; // Make a primary bsp splitter
|
||||
const SKIP = 0b000000001000000000; // Completely ignore, allowing non-closed brushes
|
||||
const NOLIGHTMAP = 0b000000010000000000; // Surface doesn't need a lightmap
|
||||
const POINTLIGHT = 0b000000100000000000; // Generate lighting info at vertexes
|
||||
const METALSTEPS = 0b000001000000000000; // Clanking footsteps
|
||||
const NOSTEPS = 0b000010000000000000; // No footstep sounds
|
||||
const NONSOLID = 0b000100000000000000; // Don't collide against curves with this set
|
||||
const LIGHTFILTER = 0b001000000000000000; // Act as a light filter during q3map -light
|
||||
const ALPHASHADOW = 0b010000000000000000; // Do per-pixel light shadow casting in q3map
|
||||
const NODLIGHT = 0b100000000000000000; // Never add dynamic lights
|
||||
const NODAMAGE = 0b0000_0000_0000_0000_0001; // Never give falling damage
|
||||
const SLICK = 0b0000_0000_0000_0000_0010; // Effects game physics
|
||||
const SKY = 0b0000_0000_0000_0000_0100; // Lighting from environment map
|
||||
const LADDER = 0b0000_0000_0000_0000_1000; // Climbable ladder
|
||||
const NOIMPACT = 0b0000_0000_0000_0001_0000; // Don't make missile explosions
|
||||
const NOMARKS = 0b0000_0000_0000_0010_0000; // Don't leave missile marks
|
||||
const FLESH = 0b0000_0000_0000_0100_0000; // Make flesh sounds and effects
|
||||
const NODRAW = 0b0000_0000_0000_1000_0000; // Don't generate a drawsurface at all
|
||||
const HINT = 0b0000_0000_0001_0000_0000; // Make a primary bsp splitter
|
||||
const SKIP = 0b0000_0000_0010_0000_0000; // Completely ignore, allowing non-closed brushes
|
||||
const NOLIGHTMAP = 0b0000_0000_0100_0000_0000; // Surface doesn't need a lightmap
|
||||
const POINTLIGHT = 0b0000_0000_1000_0000_0000; // Generate lighting info at vertexes
|
||||
const METALSTEPS = 0b0000_0001_0000_0000_0000; // Clanking footsteps
|
||||
const NOSTEPS = 0b0000_0010_0000_0000_0000; // No footstep sounds
|
||||
const NONSOLID = 0b0000_0100_0000_0000_0000; // Don't collide against curves with this set
|
||||
const LIGHTFILTER = 0b0000_1000_0000_0000_0000; // Act as a light filter during q3map -light
|
||||
const ALPHASHADOW = 0b0001_0000_0000_0000_0000; // Do per-pixel light shadow casting in q3map
|
||||
const NODLIGHT = 0b0010_0000_0000_0000_0000; // Never add dynamic lights
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ContentFlags: u32 {
|
||||
const SOLID = 0b00000000000000000000000000000001; // An eye is never valid in a solid
|
||||
const LAVA = 0b00000000000000000000000000001000;
|
||||
const SLIME = 0b00000000000000000000000000010000;
|
||||
const WATER = 0b00000000000000000000000000100000;
|
||||
const FOG = 0b00000000000000000000000001000000;
|
||||
const NOTTEAM1 = 0b00000000000000000000000010000000;
|
||||
const NOTTEAM2 = 0b00000000000000000000000100000000;
|
||||
const NOBOTCLIP = 0b00000000000000000000001000000000;
|
||||
// An eye is never valid in a solid
|
||||
const SOLID = 0b0000_0000_0000_0000_0000_0000_0000_0001;
|
||||
const LAVA = 0b0000_0000_0000_0000_0000_0000_0000_1000;
|
||||
const SLIME = 0b0000_0000_0000_0000_0000_0000_0001_0000;
|
||||
const WATER = 0b0000_0000_0000_0000_0000_0000_0010_0000;
|
||||
const FOG = 0b0000_0000_0000_0000_0000_0000_0100_0000;
|
||||
const NOTTEAM1 = 0b0000_0000_0000_0000_0000_0000_1000_0000;
|
||||
const NOTTEAM2 = 0b0000_0000_0000_0000_0000_0001_0000_0000;
|
||||
const NOBOTCLIP = 0b0000_0000_0000_0000_0000_0010_0000_0000;
|
||||
|
||||
const AREAPORTAL = 0b00000000000000001000000000000000;
|
||||
const AREAPORTAL = 0b0000_0000_0000_0000_1000_0000_0000_0000;
|
||||
|
||||
const PLAYERCLIP = 0b00000000000000010000000000000000;
|
||||
const MONSTERCLIP = 0b00000000000000100000000000000000;
|
||||
// bot specific contents types
|
||||
const TELEPORTER = 0b00000000000001000000000000000000;
|
||||
const JUMPPAD = 0b00000000000010000000000000000000;
|
||||
const CLUSTERPORTAL = 0b00000000000100000000000000000000;
|
||||
const DONOTENTER = 0b00000000001000000000000000000000;
|
||||
const BOTCLIP = 0b00000000010000000000000000000000;
|
||||
const MOVER = 0b00000000100000000000000000000000;
|
||||
const PLAYERCLIP = 0b0000_0000_0000_0001_0000_0000_0000_0000;
|
||||
const MONSTERCLIP = 0b0000_0000_0000_0010_0000_0000_0000_0000;
|
||||
|
||||
const ORIGIN = 0b00000001000000000000000000000000; // removed before bsping an entity
|
||||
// Bot-specific contents types
|
||||
const TELEPORTER = 0b0000_0000_0000_0100_0000_0000_0000_0000;
|
||||
const JUMPPAD = 0b0000_0000_0000_1000_0000_0000_0000_0000;
|
||||
const CLUSTERPORTAL = 0b0000_0000_0001_0000_0000_0000_0000_0000;
|
||||
const DONOTENTER = 0b0000_0000_0010_0000_0000_0000_0000_0000;
|
||||
const BOTCLIP = 0b0000_0000_0100_0000_0000_0000_0000_0000;
|
||||
const MOVER = 0b0000_0000_1000_0000_0000_0000_0000_0000;
|
||||
|
||||
const BODY = 0b00000010000000000000000000000000; // should never be on a brush, only in game
|
||||
const CORPSE = 0b00000100000000000000000000000000;
|
||||
const DETAIL = 0b00001000000000000000000000000000; // brushes not used for the bsp
|
||||
const STRUCTURAL = 0b00010000000000000000000000000000; // brushes used for the bsp
|
||||
const TRANSLUCENT = 0b00100000000000000000000000000000; // don't consume surface fragments inside
|
||||
const TRIGGER = 0b01000000000000000000000000000000;
|
||||
const NODROP = 0b10000000000000000000000000000000; // don't leave bodies or items (death fog, lava)
|
||||
// Removed before bsping an entity
|
||||
const ORIGIN = 0b0000_0001_0000_0000_0000_0000_0000_0000;
|
||||
|
||||
// Should never be on a brush, only in game
|
||||
const BODY = 0b0000_0010_0000_0000_0000_0000_0000_0000;
|
||||
const CORPSE = 0b0000_0100_0000_0000_0000_0000_0000_0000;
|
||||
// Brushes not used for the bsp
|
||||
const DETAIL = 0b0000_1000_0000_0000_0000_0000_0000_0000;
|
||||
// Brushes used for the bsp
|
||||
const STRUCTURAL = 0b0001_0000_0000_0000_0000_0000_0000_0000;
|
||||
// Don't consume surface fragments inside
|
||||
const TRANSLUCENT = 0b0010_0000_0000_0000_0000_0000_0000_0000;
|
||||
const TRIGGER = 0b0100_0000_0000_0000_0000_0000_0000_0000;
|
||||
// Don't leave bodies or items (death fog, lava)
|
||||
const NODROP = 0b1000_0000_0000_0000_0000_0000_0000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +399,7 @@ elsize! {
|
|||
pub struct VisData {
|
||||
pub n_vecs: i32, // Number of vectors.
|
||||
pub sz_vecs: i32, // Size of each vector, in bytes.
|
||||
pub vecs: Vec<u8>, // Visibility data. One bit per cluster per vector.
|
||||
pub vecs: BitVec<u8>, // Visibility data. One bit per cluster per vector.
|
||||
}
|
||||
|
||||
struct BspReader<R> {
|
||||
|
|
@ -413,25 +428,21 @@ impl<R: Read + Seek> BspReader<R> {
|
|||
return Err(ErrorKind::InvalidData.into());
|
||||
}
|
||||
|
||||
let mut entries = Vec::with_capacity(dir_entry.length as usize / T::SIZE);
|
||||
let num_entries = dir_entry.length as usize / T::SIZE;
|
||||
let mut entries = Vec::with_capacity(num_entries);
|
||||
self.inner.seek(SeekFrom::Start(dir_entry.offset as u64))?;
|
||||
let mut reader = BspReader {
|
||||
inner: self.inner.by_ref().take(dir_entry.length as u64),
|
||||
};
|
||||
loop {
|
||||
match f(&mut reader) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(e) => {
|
||||
if e.kind() != ErrorKind::UnexpectedEof || reader.inner.bytes().next().is_some()
|
||||
{
|
||||
return Err(e);
|
||||
} else {
|
||||
break;
|
||||
|
||||
for _ in 0..num_entries {
|
||||
entries.push(f(&mut reader)?);
|
||||
}
|
||||
|
||||
if entries.len() != num_entries {
|
||||
return Err(ErrorKind::InvalidData.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.shrink_to_fit();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
|
|
@ -804,9 +815,19 @@ impl<R: Read> BspReader<R> {
|
|||
fn read_visdata(&mut self) -> Result<VisData> {
|
||||
let n_vecs = self.inner.read_i32::<LittleEndian>()?;
|
||||
let sz_vecs = self.inner.read_i32::<LittleEndian>()?;
|
||||
let vecs_size = n_vecs as u64 * sz_vecs as u64;
|
||||
let mut vecs = Vec::with_capacity(vecs_size as usize);
|
||||
self.inner.by_ref().take(vecs_size).read_to_end(&mut vecs)?;
|
||||
let vecs_size = n_vecs as usize * sz_vecs as usize;
|
||||
let mut vecs = Vec::with_capacity(vecs_size);
|
||||
self.inner
|
||||
.by_ref()
|
||||
.take(vecs_size as u64)
|
||||
.read_to_end(&mut vecs)?;
|
||||
|
||||
if vecs.len() != vecs_size {
|
||||
return Err(Error::from(ErrorKind::InvalidData));
|
||||
}
|
||||
|
||||
let vecs = BitVec::from_bits(vecs);
|
||||
|
||||
let vis_data = VisData {
|
||||
n_vecs,
|
||||
sz_vecs,
|
||||
|
|
@ -816,6 +837,25 @@ impl<R: Read> BspReader<R> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Handle<'a, T> {
|
||||
bsp: &'a Bsp,
|
||||
data: &'a T,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Store all the allocated objects inline to improve cache usage
|
||||
#[derive(Debug)]
|
||||
pub struct Bsp {
|
||||
|
|
@ -839,13 +879,15 @@ pub struct Bsp {
|
|||
pub vis_data: VisData,
|
||||
}
|
||||
|
||||
pub fn read_bsp<R: Read + Seek>(reader: R) -> Result<Bsp> {
|
||||
impl Bsp {
|
||||
pub fn read<R: Read + Seek>(reader: R) -> Result<Self> {
|
||||
const EXPECTED_HEADER: Header = Header {
|
||||
i: b'I',
|
||||
b: b'B',
|
||||
s: b'S',
|
||||
p: b'P',
|
||||
};
|
||||
// TODO: Use this to decide on the version to parse it as
|
||||
const EXPECTED_VERSION: i32 = 0x2e;
|
||||
|
||||
let mut reader = BspReader { inner: reader };
|
||||
|
|
@ -853,7 +895,10 @@ pub fn read_bsp<R: Read + Seek>(reader: R) -> Result<Bsp> {
|
|||
let version = reader.read_version()?;
|
||||
|
||||
if header != EXPECTED_HEADER || version != EXPECTED_VERSION {
|
||||
return Err(ErrorKind::InvalidData.into());
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Invalid header or version"),
|
||||
));
|
||||
}
|
||||
|
||||
let dir_entries = reader.read_directories()?;
|
||||
|
|
@ -902,11 +947,152 @@ pub fn read_bsp<R: Read + Seek>(reader: R) -> Result<Bsp> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn leaf(&self, n: usize) -> Option<Handle<'_, Leaf>> {
|
||||
self.leafs.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 texture(&self, n: usize) -> Option<&Texture> {
|
||||
self.textures.get(n)
|
||||
}
|
||||
|
||||
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 leaf_at(&self, point: [f32; 3]) -> 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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle<'_, Face> {
|
||||
pub fn texture(&self) -> Option<&Texture> {
|
||||
self.bsp.texture(self.texture as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle<'_, Node> {
|
||||
pub fn plane(&self) -> Option<Handle<'_, Plane>> {
|
||||
self.bsp.plane(self.plane 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 {
|
||||
Some(
|
||||
bsp.leafs
|
||||
.iter()
|
||||
.filter(move |leaf| {
|
||||
if leaf.cluster == cluster {
|
||||
true
|
||||
} else if leaf.cluster > 0 {
|
||||
let cluster_vis_start =
|
||||
leaf.cluster as u64 * bsp.vis_data.sz_vecs as u64 * 8;
|
||||
bsp.vis_data.vecs[cluster_vis_start + 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.leaf_face as usize;
|
||||
let end = start + self.num_leaf_faces 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 random_file() {
|
||||
use std::fs::File;
|
||||
|
||||
let bsp = read_bsp(&mut File::open("test.bsp").expect("Cannot open file")).unwrap();
|
||||
Bsp::read(&mut File::open("test.bsp").expect("Cannot open file")).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
println!("{:#?}", bsp.textures);
|
||||
#[cfg(feature = "bench")]
|
||||
mod benches {
|
||||
use super::Bsp;
|
||||
use test::Bencher;
|
||||
|
||||
const MAP_BYTES: &[u8] = include_bytes!("../test.bsp");
|
||||
|
||||
#[bench]
|
||||
fn from_bytes(b: &mut Bencher) {
|
||||
use std::io::Cursor;
|
||||
|
||||
b.iter(|| {
|
||||
Bsp::read(&mut Cursor::new(MAP_BYTES)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn leaf_at(b: &mut Bencher) {
|
||||
use std::io::Cursor;
|
||||
|
||||
let bsp = Bsp::read(&mut Cursor::new(MAP_BYTES)).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
test::black_box(bsp.leaf_at(test::black_box([0., 0., 0.])));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue