1
0
Fork 0
mirror of https://codeberg.org/icewind/vbsp.git synced 2026-06-03 18:54:05 +02:00

split off vector

This commit is contained in:
Robin Appelman 2022-02-20 22:04:30 +01:00
commit 7216783734
2 changed files with 84 additions and 79 deletions

650
src/data/mod.rs Normal file
View file

@ -0,0 +1,650 @@
mod vector;
use crate::bspfile::LumpType;
use crate::StringError;
use arrayvec::ArrayString;
use binrw::io::SeekFrom;
use binrw::{BinRead, BinResult, ReadOptions};
use bitflags::bitflags;
use bv::BitVec;
use num_enum::TryFromPrimitive;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::io::{Read, Seek};
use std::mem::{align_of, size_of};
use std::ops::Index;
pub use vector::Vector;
#[cfg(test)]
fn test_read_bytes<T: BinRead>()
where
T::Args: Default,
{
use binrw::BinReaderExt;
use std::any::type_name;
use std::io::Cursor;
let bytes = [0; 512];
let mut reader = Cursor::new(bytes);
let _ = reader.read_le::<T>().unwrap();
assert_eq!(
reader.position() as usize,
size_of::<T>(),
"Invalid number of bytes used to read {}",
type_name::<T>()
);
}
#[derive(Clone, BinRead)]
pub struct Directories {
entries: [LumpEntry; 64],
}
impl Index<LumpType> for Directories {
type Output = LumpEntry;
fn index(&self, index: LumpType) -> &Self::Output {
&self.entries[index as usize]
}
}
#[derive(Debug, Clone, PartialEq, Eq, BinRead)]
#[br(little)]
pub struct Header {
pub v: u8,
pub b: u8,
pub s: u8,
pub p: u8,
}
#[derive(Clone, Copy, Debug, Default, BinRead)]
#[br(little)]
pub struct LumpEntry {
pub offset: u32,
pub length: u32,
pub version: u32,
pub ident: u32,
}
#[derive(Debug, Clone, BinRead)]
pub struct LeafFace {
pub face: u16,
}
#[derive(Clone)]
pub struct Entities {
pub entities: String,
}
impl fmt::Debug for Entities {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct Entities<'a> {
#[allow(dead_code)]
entities: Vec<Entity<'a>>,
}
Entities {
entities: self.iter().collect(),
}
.fmt(f)
}
}
impl Entities {
pub fn iter(&self) -> impl Iterator<Item = Entity<'_>> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = Entity<'a>;
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('{')? + 1;
let end = start + self.buf[start..].find('}')?;
let out = &self.buf[start..end];
self.buf = &self.buf[end + 1..];
Some(Entity { buf: out })
}
}
Iter {
buf: &self.entities,
}
}
}
#[derive(Clone)]
pub struct Entity<'a> {
buf: &'a str,
}
impl fmt::Debug for Entity<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::collections::HashMap;
self.properties().collect::<HashMap<_, _>>().fmt(f)
}
}
impl<'a> Entity<'a> {
pub fn properties(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
struct Iter<'a> {
buf: &'a str,
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let start = self.buf.find('"')? + 1;
let end = start + self.buf[start..].find('"')?;
let key = &self.buf[start..end];
let rest = &self.buf[end + 1..];
let start = rest.find('"')? + 1;
let end = start + rest[start..].find('"')?;
let value = &rest[start..end];
self.buf = &rest[end + 1..];
Some((key, value))
}
}
Iter { buf: self.buf }
}
}
bitflags! {
#[derive(BinRead)]
pub struct TextureFlags: u32 {
const LIGHT = 0b0000_0000_0000_0000_0001; // value will hold the light strength
const SKY2D = 0b0000_0000_0000_0000_0010; // don't draw, indicate we should skylight + draw 2d sky but don't draw the 3d skybox
const SKY = 0b0000_0000_0000_0000_0100; // don't draw, but add the skybox
const WARP = 0b0000_0000_0000_0000_1000; // turbulent water warp
const TRANS = 0b0000_0000_0000_0001_0000; // texture is translucent
const NOPORTAL = 0b0000_0000_0000_0010_0000; // the surface can't have a portal placed on it
const TRIGGER = 0b0000_0000_0000_0100_0000; // xbox hack to work around elimination of trigger surfaces
const NODRAW = 0b0000_0000_0000_1000_0000; // don't bother referencing the texture
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 NOLIGHT = 0b0000_0000_0100_0000_0000; // dont calculate light
const BUMPLIGHT = 0b0000_0000_1000_0000_0000; // calculate thee light maps for the surface for bump mapping
const NOSHADOWS = 0b0000_0001_0000_0000_0000; // don't receive shadows
const NODECALS = 0b0000_0010_0000_0000_0000; // don't receive decals
const NOCHOP = 0b0000_0100_0000_0000_0000; // don't subdivide patches on this surface
const HITBOX = 0b0000_1000_0000_0000_0000; // surface is part of a hitbox
}
}
/// Fixed length, null-terminated string
#[derive(Debug, Clone)]
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);
impl<const LEN: usize> Display for FixedString<LEN> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<const LEN: usize> BinRead for FixedString<LEN> {
type Args = ();
fn read_options<R: binrw::io::Read + binrw::io::Seek>(
reader: &mut R,
options: &ReadOptions,
args: Self::Args,
) -> BinResult<Self> {
use std::str;
let name_buf = <[u8; LEN]>::read_options(reader, options, args)?;
let zero_pos =
name_buf
.iter()
.position(|c| *c == 0)
.ok_or_else(|| binrw::Error::Custom {
pos: reader.seek(SeekFrom::Current(0)).unwrap(),
err: Box::new(StringError::NotNullTerminated),
})?;
let name = &name_buf[..zero_pos];
Ok(FixedString(
ArrayString::from(
str::from_utf8(name)
.map_err(StringError::NonUTF8)
.map_err(|e| binrw::Error::Custom {
pos: reader.seek(SeekFrom::Current(0)).unwrap(),
err: Box::new(e),
})?,
)
.expect(
"Programmer error: it should be impossible for the string to exceed the capacity",
),
))
}
}
#[derive(Debug, Clone, BinRead)]
pub struct TextureInfo {
pub texture_scale: [f32; 4],
pub texture_transform: [f32; 4],
pub light_map_scale: [f32; 4],
pub light_map_transform: [f32; 4],
pub flags: TextureFlags,
pub texture_data_index: i32,
}
static_assertions::const_assert_eq!(size_of::<TextureInfo>(), 72);
#[derive(Debug, Clone, BinRead)]
pub struct TextureData {
pub reflectivity: Vector,
pub name_string_table_id: i32,
pub width: i32,
pub height: i32,
pub view_width: i32,
pub view_height: i32,
}
#[derive(Debug, Clone, BinRead)]
pub struct Plane {
pub normal: Vector,
pub dist: f32,
pub ty: i32,
}
#[derive(Debug, Clone, BinRead)]
pub struct Node {
pub plane_index: i32,
pub children: [i32; 2],
pub mins: [i16; 3],
pub maxs: [i16; 3],
pub first_face: u16,
pub face_cound: u16,
pub area: i16,
pub padding: i16,
}
static_assertions::const_assert_eq!(size_of::<Node>(), 32);
#[derive(Default, Debug, Clone, BinRead)]
pub struct Leaf {
pub contents: i32,
pub cluster: i16,
pub area_and_flags: i16,
// first 9 bits is area, last 7 bits is flags
pub mins: [i16; 3],
pub maxs: [i16; 3],
pub first_leaf_face: u16,
pub leaf_face_count: u16,
pub first_leaf_brush: u16,
pub leaf_brush_count: u16,
#[br(align_after = align_of::< Leaf > ())]
pub leaf_watter_data_id: i16,
}
static_assertions::const_assert_eq!(size_of::<Leaf>(), 32);
#[test]
fn test_leaf_bytes() {
test_read_bytes::<Leaf>();
}
#[derive(Debug, Clone, BinRead)]
pub struct LeafBrush {
pub brush: u16,
}
#[derive(Debug, Clone, BinRead)]
pub struct Model {
pub mins: Vector,
pub maxs: Vector,
pub origin: Vector,
pub head_node: i32,
pub first_face: i32,
pub face_count: i32,
}
static_assertions::const_assert_eq!(size_of::<Model>(), 48);
#[derive(Debug, Clone, BinRead)]
pub struct Brush {
pub brush_side: u32,
pub num_brush_sides: u32,
pub flags: BrushFlags,
}
impl Brush {
pub fn is_visible(&self) -> bool {
self.flags.intersects(
BrushFlags::SOLID
| BrushFlags::GRATE
| BrushFlags::OPAQUE
| BrushFlags::TESTFOGVOLUME
| BrushFlags::TRANSLUCENT,
)
}
}
bitflags! {
#[derive(BinRead)]
pub struct BrushFlags: u32 {
const EMPTY = 0; // No contents
const SOLID = 0x1; // an eye is never valid in a solid
const WINDOW = 0x2; // translucent, but not watery (glass)
const AUX = 0x4;
const GRATE = 0x8; // alpha-tested "grate" textures. Bullets/sight pass through, but solids don't
const SLIME = 0x10;
const WATER = 0x20;
const MIST = 0x40;
const OPAQUE = 0x80; // block AI line of sight
const TESTFOGVOLUME = 0x100; // things that cannot be seen through (may be non-solid though)
const UNUSED = 0x200; // unused
const UNUSED6 = 0x400; // unused
const TEAM1 = 0x800; // per team contents used to differentiate collisions between players and objects on different teams
const TEAM2 = 0x1000;
const IGNORE_NODRAW_OPAQUE = 0x2000; // ignore CONTENTS_OPAQUE on surfaces that have SURF_NODRAW
const MOVEABLE = 0x4000; // hits entities which are MOVETYPE_PUSH (doors, plats, etc.)
const AREAPORTAL = 0x8000; // remaining contents are non-visible, and don't eat brushes
const PLAYERCLIP = 0x10000;
const MONSTERCLIP = 0x20000;
const CURRENT_0 = 0x40000; // currents can be added to any other contents, and may be mixed
const CURRENT_90 = 0x80000;
const CURRENT_180 = 0x100000;
const CURRENT_270 = 0x200000;
const CURRENT_UP = 0x400000;
const CURRENT_DOWN = 0x800000;
const ORIGIN = 0x1000000; // removed before bsping an entity
const MONSTER = 0x2000000; // should never be on a brush, only in game
const DEBRIS = 0x4000000;
const DETAIL = 0x8000000; // brushes to be added after vis leafs
const TRANSLUCENT = 0x10000000; // auto set if any surface has trans
const LADDER = 0x20000000;
const HITBOX = 0x40000000; // use accurate hitboxes on trace
}
}
#[derive(Debug, Clone, BinRead)]
pub struct BrushSide {
pub plane: u16,
pub texture_info: i16,
pub displacement_info: i16,
pub bevel: i16,
}
#[derive(Debug, Clone, BinRead)]
pub struct Vertex {
pub position: Vector,
}
#[derive(Debug, Clone, BinRead)]
pub struct Edge {
pub start_index: u16,
pub end_index: u16,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum EdgeDirection {
FirstToLast,
LastToFirst,
}
#[derive(Debug, Clone, BinRead)]
pub struct SurfaceEdge {
edge: i32,
}
impl SurfaceEdge {
pub fn edge_index(&self) -> usize {
self.edge.abs() as usize
}
pub fn direction(&self) -> EdgeDirection {
if self.edge >= 0 {
EdgeDirection::FirstToLast
} else {
EdgeDirection::LastToFirst
}
}
}
#[derive(Debug, Clone, BinRead)]
pub struct Face {
pub plane_num: u16,
pub side: u8,
pub on_node: u8,
pub first_edge: i32,
pub num_edges: i16,
pub texture_info: i16,
pub displacement_info: i16,
pub surface_fog_volume_id: i16,
pub styles: [u8; 4],
pub light_offset: i32,
pub area: f32,
pub light_map_texture_min: [i32; 2],
pub light_map_texture_size: [i32; 2],
pub original_face: i32,
pub primitive_count: u16,
pub first_primitive_index: u16,
pub smoothing_groups: u32,
}
static_assertions::const_assert_eq!(size_of::<Face>(), 56);
#[derive(Default, Debug, Clone)]
pub struct VisData {
pub cluster_count: u32,
pub pvs_offsets: Vec<i32>,
pub pas_offsets: Vec<i32>,
pub data: Vec<u8>,
}
impl VisData {
pub fn visible_clusters(&self, cluster: i16) -> BitVec<u8> {
let offset = self.pvs_offsets[cluster as usize] as usize;
let pvs_buffer = &self.data[offset..];
let mut visible_clusters = BitVec::with_capacity(self.cluster_count as u64);
visible_clusters.resize(self.cluster_count as u64, false);
let mut cluster_index = 0;
let mut buffer_index = 0;
while cluster_index < self.cluster_count {
if pvs_buffer[buffer_index] == 0 {
let skip = pvs_buffer[buffer_index + 1];
cluster_index += skip as u32;
buffer_index += 2;
} else {
let packed = pvs_buffer[buffer_index];
for i in 0..8 {
let bit = 1 << i;
if (packed & bit) == bit {
visible_clusters.set(cluster_index as u64, true);
}
cluster_index += 1;
}
buffer_index += 1;
}
}
visible_clusters
}
}
#[derive(Debug, Clone, BinRead)]
pub struct DisplacementInfo {
pub start_position: Vector,
pub displacement_vertex_start: i32,
pub displacement_triangle_tag_start: i32,
pub power: i32,
pub minimum_tesselation: i32,
pub smoothing_angle: f32,
pub contents: i32,
pub map_face: u16,
#[br(align_before = 4)]
pub lightmap_alpha_start: i32,
pub lightmap_sample_position_start: i32,
pub edge_neighbours: [DisplacementNeighbour; 4],
pub corner_neighbours: [DisplacementCornerNeighbour; 4],
pub allowed_vertices: [u32; 10],
}
impl DisplacementInfo {
pub fn vertex_count(&self) -> i32 {
(2i32.pow(self.power as u32) + 1).pow(2)
}
pub fn triangle_count(&self) -> i32 {
2 * 2i32.pow(self.power as u32).pow(2)
}
}
#[test]
fn test_displacement_bytes() {
test_read_bytes::<DisplacementInfo>();
}
static_assertions::const_assert_eq!(size_of::<DisplacementInfo>(), 176);
#[derive(Debug, Clone)]
pub struct DisplacementNeighbour {
pub sub_neighbours: [Option<DisplacementSubNeighbour>; 2],
}
impl BinRead for DisplacementNeighbour {
type Args = ();
fn read_options<R: Read + Seek>(
reader: &mut R,
options: &ReadOptions,
args: Self::Args,
) -> BinResult<Self> {
let raws = <[RawDisplacementSubNeighbour; 2]>::read_options(reader, options, args)?;
Ok(DisplacementNeighbour {
sub_neighbours: raws.map(|raw| raw.try_into().ok()),
})
}
}
static_assertions::const_assert_eq!(size_of::<DisplacementNeighbour>(), 12);
#[derive(Debug, Clone, BinRead)]
#[br(assert(neighbour_index == u16::MAX || (neighbour_orientation < 4 && span < 4 && neighbour_span < 4), "valid neighbour index with invalid enum fields"))]
struct RawDisplacementSubNeighbour {
neighbour_index: u16,
neighbour_orientation: u8,
span: u8,
#[br(align_after = align_of::< DisplacementSubNeighbour > ())]
neighbour_span: u8,
}
#[test]
fn test_sub_neighbour_bytes() {
test_read_bytes::<RawDisplacementSubNeighbour>();
}
#[derive(Debug, Clone)]
pub struct DisplacementSubNeighbour {
pub neighbour_index: u16,
/// Orientation of the neighbour relative to us
pub neighbour_orientation: NeighbourOrientation,
/// How the neighbour fits into us
pub span: NeighbourSpan,
/// How we fit into our neighbour
pub neighbour_span: NeighbourSpan,
}
impl TryFrom<RawDisplacementSubNeighbour> for DisplacementSubNeighbour {
type Error = ();
fn try_from(value: RawDisplacementSubNeighbour) -> Result<Self, Self::Error> {
match value.neighbour_index {
u16::MAX => Err(()),
neighbour_index => Ok(DisplacementSubNeighbour {
neighbour_index,
// note that we already checked if these enums are valid in the assert of the RawDisplacementSubNeighbour reader
neighbour_orientation: NeighbourOrientation::try_from(value.neighbour_orientation)
.unwrap(),
span: NeighbourSpan::try_from(value.span).unwrap(),
neighbour_span: NeighbourSpan::try_from(value.neighbour_span).unwrap(),
}),
}
}
}
static_assertions::const_assert_eq!(size_of::<DisplacementSubNeighbour>(), 6);
#[derive(Debug, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum NeighbourSpan {
CornerToCorner,
CornerToMidPoint,
MidPointToCorner,
}
#[derive(Debug, Clone, TryFromPrimitive)]
#[repr(u8)]
pub enum NeighbourOrientation {
Ccw0,
Ccw90,
Ccw180,
Ccw270,
}
#[derive(Debug, Clone, BinRead)]
pub struct DisplacementCornerNeighbour {
pub neighbours: [u16; 4],
#[br(align_after = align_of::< DisplacementCornerNeighbour > ())]
pub neighbour_count: u8,
}
static_assertions::const_assert_eq!(size_of::<DisplacementCornerNeighbour>(), 10);
#[test]
fn test_corner_neighbour_bytes() {
test_read_bytes::<DisplacementCornerNeighbour>();
}
#[derive(Debug, Clone, BinRead)]
pub struct DisplacementVertex {
pub vector: Vector,
pub distance: f32,
pub alpha: f32,
}
impl DisplacementVertex {
pub fn displacement(&self) -> Vector {
self.vector * self.distance
}
}
#[derive(Debug, Clone, BinRead)]
pub struct DisplacementTriangle {
pub tags: DisplacementTriangleFlags,
}
bitflags! {
#[derive(BinRead)]
pub struct DisplacementTriangleFlags: u8 {
const SURFACE = 0x01;
const WALKABLE = 0x02;
const BULDABLE = 0x04;
const SURFACE_PROP1 = 0x08;
const SURFACE_PROP2 = 0x10;
}
}

81
src/data/vector.rs Normal file
View file

@ -0,0 +1,81 @@
use binrw::BinRead;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::ops::{Add, Mul, Sub};
#[derive(Debug, Clone, Copy, BinRead)]
pub struct Vector {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vector {
pub fn iter(&self) -> impl Iterator<Item = f32> {
[self.x, self.y, self.z].into_iter()
}
pub fn length_squared(&self) -> f32 {
self.x.powf(2.0) + self.y.powf(2.0) + self.z.powf(2.0)
}
}
impl Add<Vector> for Vector {
type Output = Vector;
fn add(self, rhs: Vector) -> Self::Output {
Vector {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Sub<Vector> for Vector {
type Output = Vector;
fn sub(self, rhs: Vector) -> Self::Output {
Vector {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Mul<f32> for Vector {
type Output = Vector;
fn mul(self, rhs: f32) -> Self::Output {
Vector {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
impl PartialEq for Vector {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z
}
}
impl PartialOrd for Vector {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.length_squared().partial_cmp(&other.length_squared())
}
}
impl From<Vector> for [f32; 3] {
fn from(vector: Vector) -> Self {
[vector.x, vector.y, vector.z]
}
}
impl From<&Vector> for [f32; 3] {
fn from(vector: &Vector) -> Self {
[vector.x, vector.y, vector.z]
}
}