diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
commit | d076d3a6fd484e298915cd85609ba9706abacc87 (patch) | |
tree | 0d74395ff54e56fd54cab35ec0f27254e8306822 /stockton-levels | |
parent | 5dc6c64394d1e0a09c882b88ecb2b8f04f9e5b22 (diff) |
refactor(all): move stockton-bsp to this repo and start using traits
Diffstat (limited to 'stockton-levels')
32 files changed, 2265 insertions, 0 deletions
diff --git a/stockton-levels/Cargo.toml b/stockton-levels/Cargo.toml new file mode 100644 index 0000000..facae7d --- /dev/null +++ b/stockton-levels/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "stockton-levels" +version = "0.1.0" +authors = ["Oscar <oscar.shrimpton.personal@gmail.com>"] +description = "Library for parsing different types of .bsp files." +repository = "https://github.com/tcmal/stockton" +homepage = "https://github.com/tcmal/stockton" + +[dependencies] +nalgebra = "^0.20" +bitflags = "^1.2" +bitvec = "0.17"
\ No newline at end of file diff --git a/stockton-levels/src/helpers.rs b/stockton-levels/src/helpers.rs new file mode 100644 index 0000000..b81fb6a --- /dev/null +++ b/stockton-levels/src/helpers.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Helper functions for parsing + +use na::{Vector2, Vector3}; +use std::convert::TryInto; + +/// Turn a slice into a le i32, the int datatype in a bsp file. +/// # Panics +/// If slice is not 4 bytes long +pub fn slice_to_i32(slice: &[u8]) -> i32 { + i32::from_le_bytes(slice.try_into().unwrap()) +} + +/// Turn a slice into a le u32, used for some bitflags. +/// # Panics +/// If slice is not 4 bytes long. +pub fn slice_to_u32(slice: &[u8]) -> u32 { + u32::from_le_bytes(slice.try_into().unwrap()) +} + +/// Turn a slice into a le f32, the float datatype in a bsp file. +/// # Panics +/// If slice is not 4 bytes long +pub fn slice_to_f32(slice: &[u8]) -> f32 { + f32::from_bits(u32::from_le_bytes(slice.try_into().unwrap())) +} + +/// Turn a slice of floats into a 3D vector +/// # Panics +/// If slice isn't 12 bytes long. +pub fn slice_to_vec3(slice: &[u8]) -> Vector3<f32> { + Vector3::new( + slice_to_f32(&slice[0..4]), + slice_to_f32(&slice[4..8]), + slice_to_f32(&slice[8..12]), + ) +} + +/// Turn a slice of i32s into a 3D vector +/// # Panics +/// If slice isn't 12 bytes long. +pub fn slice_to_vec3i(slice: &[u8]) -> Vector3<i32> { + Vector3::new( + slice_to_i32(&slice[0..4]), + slice_to_i32(&slice[4..8]), + slice_to_i32(&slice[8..12]), + ) +} + +/// Turn a slice of i32s into a 2D vector +/// # Panics +/// If slice isn't 8 bytes long. +pub fn slice_to_vec2i(slice: &[u8]) -> Vector2<i32> { + Vector2::new(slice_to_i32(&slice[0..4]), slice_to_i32(&slice[4..8])) +} diff --git a/stockton-levels/src/lib.rs b/stockton-levels/src/lib.rs new file mode 100644 index 0000000..37d1050 --- /dev/null +++ b/stockton-levels/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. +//! Parses common features from many BSP file formats. + +#[macro_use] +extern crate bitflags; +extern crate bitvec; +extern crate nalgebra as na; + +mod helpers; +pub mod q3; +pub mod types; +pub mod traits;
\ No newline at end of file diff --git a/stockton-levels/src/q3/brushes.rs b/stockton-levels/src/q3/brushes.rs new file mode 100644 index 0000000..48462a2 --- /dev/null +++ b/stockton-levels/src/q3/brushes.rs @@ -0,0 +1,117 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Parses the brushes & brushsides lumps from a bsp file + +/// The size of one brush record. +const BRUSH_SIZE: usize = 4 * 3; + +/// The size of one brushsize record +const SIDE_SIZE: usize = 4 * 2; + +use crate::helpers::slice_to_i32; +use crate::types::{ParseError, Result}; +use crate::traits::brushes::*; +use super::Q3BSPFile; + +/// Parse the brushes & brushsides lump from a bsp file. +pub fn from_data( + brushes_data: &[u8], + sides_data: &[u8], + n_textures: u32, + n_planes: u32 +) -> Result<Box<[Brush]>> { + if brushes_data.len() % BRUSH_SIZE != 0 || sides_data.len() % SIDE_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = brushes_data.len() / BRUSH_SIZE; + + let mut brushes = Vec::with_capacity(length as usize); + for n in 0..length { + let offset = n * BRUSH_SIZE; + let brush = &brushes_data[offset..offset + BRUSH_SIZE]; + + let texture_idx = slice_to_i32(&brush[8..12]) as usize; + if texture_idx >= n_textures as usize { + return Err(ParseError::Invalid); + } + + brushes.push(Brush { + sides: get_sides( + sides_data, + slice_to_i32(&brush[0..4]), + slice_to_i32(&brush[4..8]), + n_textures as usize, + n_planes as usize + )?, + texture_idx + }); + } + + Ok(brushes.into_boxed_slice()) +} + +/// Internal function to get the relevant brushsides for a brush from the data in the brush lump. +fn get_sides( + sides_data: &[u8], + start: i32, + length: i32, + n_textures: usize, + n_planes: usize, +) -> Result<Box<[BrushSide]>> { + let mut sides = Vec::with_capacity(length as usize); + + if length > 0 { + for n in start..start + length { + let offset = n as usize * SIDE_SIZE; + let brush = &sides_data[offset..offset + SIDE_SIZE]; + + let plane_idx = slice_to_i32(&brush[0..4]) as usize; + if plane_idx / 2 >= n_planes { + return Err(ParseError::Invalid); + } + + let is_opposing = plane_idx % 2 != 0; + + let texture_idx = slice_to_i32(&brush[4..8]) as usize; + if texture_idx >= n_textures { + return Err(ParseError::Invalid); + } + + sides.push(BrushSide { + plane_idx, + texture_idx, + is_opposing + }); + } + } + + Ok(sides.into_boxed_slice()) +} + + +impl<'a> HasBrushes<'a> for Q3BSPFile { + type BrushesIter = std::slice::Iter<'a, Brush>; + + fn brushes_iter(&'a self) -> Self::BrushesIter { + self.brushes.iter() + } + + fn get_brush(&'a self, index: u32) -> &'a Brush { + &self.brushes[index as usize] + } +} diff --git a/stockton-levels/src/q3/effects.rs b/stockton-levels/src/q3/effects.rs new file mode 100644 index 0000000..2a76da9 --- /dev/null +++ b/stockton-levels/src/q3/effects.rs @@ -0,0 +1,63 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::str; + +use crate::helpers::slice_to_u32; +use crate::types::{Result, ParseError}; +use crate::traits::effects::*; +use super::Q3BSPFile; + +/// The size of one effect definition +const EFFECT_SIZE: usize = 64 + 4 + 4; + +pub fn from_data(data: &[u8], n_brushes: u32) -> Result<Box<[Effect]>> { + if data.len() % EFFECT_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / EFFECT_SIZE; + + let mut effects = Vec::with_capacity(length); + for n in 0..length { + let raw = &data[n * EFFECT_SIZE..(n + 1) * EFFECT_SIZE]; + + let brush_idx = slice_to_u32(&raw[64..68]); + if brush_idx >= n_brushes { + return Err(ParseError::Invalid); + } + + effects.push(Effect { + name: str::from_utf8(&raw[..64]).map_err(|_| ParseError::Invalid)?.to_owned(), + brush_idx + }); + } + + Ok(effects.into_boxed_slice()) +} + + +impl<'a> HasEffects<'a> for Q3BSPFile { + type EffectsIter = std::slice::Iter<'a, Effect>; + + fn effects_iter(&'a self) -> Self::EffectsIter { + self.effects.iter() + } + + fn get_effect(&'a self, index: u32) -> &'a Effect { + &self.effects[index as usize] + } +} diff --git a/stockton-levels/src/q3/entities.rs b/stockton-levels/src/q3/entities.rs new file mode 100644 index 0000000..1614f26 --- /dev/null +++ b/stockton-levels/src/q3/entities.rs @@ -0,0 +1,108 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::str; +use std::collections::HashMap; + +use crate::types::{Result, ParseError}; +use crate::traits::entities::*; +use super::Q3BSPFile; + +const QUOTE: u8 = b'"'; +const END_BRACKET: u8 = b'}'; +const START_BRACKET: u8 = b'{'; + +/// Internal enum to parse through the entities string. +#[derive(PartialEq, Eq)] +enum ParseState { + InKey, + InValue, + AfterKey, + InsideEntity, + OutsideEntity, +} + +/// Parse the given data as an Entities lump +pub fn from_data(data: &[u8]) -> Result<Box<[Entity]>> { + use self::ParseState::*; + + let string = str::from_utf8(data).unwrap(); + + let mut attrs = HashMap::new(); + let mut entities = Vec::new(); + + let mut state = ParseState::OutsideEntity; + + let mut key_start = 0; + let mut key_end = 0; + let mut val_start = 0; + let mut val_end; + + for (i, chr) in string.bytes().enumerate() { + match chr { + QUOTE => match state { + InsideEntity => { + state = ParseState::InKey; + key_start = i + 1; + } + InKey => { + state = ParseState::AfterKey; + key_end = i; + } + AfterKey => { + state = ParseState::InValue; + val_start = i + 1; + } + InValue => { + state = ParseState::InsideEntity; + val_end = i; + + attrs.insert(string[key_start..key_end].to_owned(), string[val_start..val_end].to_owned()); + } + _ => { + return Err(ParseError::Invalid); + } + }, + END_BRACKET => { + if state != InsideEntity { + return Err(ParseError::Invalid); + } + + state = OutsideEntity; + + entities.push(Entity { attributes: attrs }); + attrs = HashMap::new(); + } + START_BRACKET => { + if state != OutsideEntity { + return Err(ParseError::Invalid); + } + state = InsideEntity; + } + _ => {} + } + } + Ok(entities.into_boxed_slice()) +} + +impl<'a> HasEntities<'a> for Q3BSPFile { + type EntitiesIter = std::slice::Iter<'a, Entity>; + + fn entities_iter(&'a self) -> Self::EntitiesIter { + self.entities.iter() + } +} diff --git a/stockton-levels/src/q3/faces.rs b/stockton-levels/src/q3/faces.rs new file mode 100644 index 0000000..835dbe8 --- /dev/null +++ b/stockton-levels/src/q3/faces.rs @@ -0,0 +1,163 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use crate::helpers::{slice_to_i32, slice_to_vec2i, slice_to_vec3}; +use crate::types::{Result, ParseError}; +use na::Vector3; +use crate::traits::faces::*; +use super::Q3BSPFile; + +const FACE_SIZE: usize = (4 * 8) + (4 * 2) + (4 * 2) + (4 * 3) + ((4 * 2) * 3) + (4 * 3) + (4 * 2); + + +pub fn from_data( + data: &[u8], + n_textures: u32, + n_effects: u32, + n_vertices: u32, + n_meshverts: u32, + n_lightmaps: u32, +) -> Result<Box<[Face]>> { + if data.len() % FACE_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / FACE_SIZE; + + let mut faces = Vec::with_capacity(length); + for n in 0..length { + faces.push(face_from_slice( + &data[n * FACE_SIZE..(n + 1) * FACE_SIZE], + n_textures as usize, + n_effects as usize, + n_vertices as usize, + n_meshverts as usize, + n_lightmaps as usize, + )?); + } + + Ok(faces.into_boxed_slice()) +} + + +fn face_from_slice( + data: &[u8], + n_textures: usize, + n_effects: usize, + n_vertices: usize, + n_meshverts: usize, + n_lightmaps: usize, +) -> Result<Face> { + if data.len() != FACE_SIZE { + panic!("tried to call face.from_slice with invalid slice size"); + } + + // texture + let texture_idx = slice_to_i32(&data[0..4]) as usize; + if texture_idx >= n_textures { + return Err(ParseError::Invalid); + } + + // effects + let effect_idx = slice_to_i32(&data[4..8]) as usize; + let effect_idx = if effect_idx < 0xffffffff { + if effect_idx >= n_effects { + return Err(ParseError::Invalid); + } + + Some(effect_idx) + } else { + None + }; + + // face type + // TODO + let face_type: FaceType = unsafe { ::std::mem::transmute(slice_to_i32(&data[8..12])) }; + + // vertices + let vertex_offset = slice_to_i32(&data[12..16]) as usize; + let vertex_n = slice_to_i32(&data[16..20]) as usize; + if (vertex_offset + vertex_n) > n_vertices { + return Err(ParseError::Invalid); + } + + let vertices_idx = vertex_offset..vertex_offset + vertex_n; + + // meshverts + let meshverts_offset = slice_to_i32(&data[20..24]) as usize; + let meshverts_n = slice_to_i32(&data[24..28]) as usize; + if (meshverts_offset + meshverts_n) > n_meshverts { + return Err(ParseError::Invalid); + } + + let meshverts_idx = meshverts_offset..meshverts_offset + meshverts_n; + + // lightmap + let lightmap_idx = slice_to_i32(&data[28..32]) as usize; + let lightmap_idx = if lightmap_idx < 0xffffffff { + if lightmap_idx >= n_lightmaps { + return Err(ParseError::Invalid); + } + + Some(lightmap_idx) + } else { + None + }; + + // map properties + let map_start = slice_to_vec2i(&data[32..40]); + let map_size = slice_to_vec2i(&data[40..48]); + let map_origin = slice_to_vec3(&data[48..60]); + + // map_vecs + let mut map_vecs = [Vector3::new(0.0, 0.0, 0.0); 2]; + for n in 0..2 { + let offset = 60 + (n * 3 * 4); + map_vecs[n] = slice_to_vec3(&data[offset..offset + 12]); + } + + // normal & size + let normal = slice_to_vec3(&data[84..96]); + let size = slice_to_vec2i(&data[96..104]); + + Ok(Face { + face_type, + texture_idx, + effect_idx, + vertices_idx, + meshverts_idx, + lightmap_idx, + map_start, + map_size, + map_origin, + map_vecs, + normal, + size, + }) +} + + +impl<'a> HasFaces<'a> for Q3BSPFile { + type FacesIter = std::slice::Iter<'a, Face>; + + fn faces_iter(&'a self) -> Self::FacesIter { + self.faces.iter() + } + + fn get_face(&'a self, index: u32) -> &'a Face { + &self.faces[index as usize] + } +} diff --git a/stockton-levels/src/q3/file.rs b/stockton-levels/src/q3/file.rs new file mode 100644 index 0000000..7c342d5 --- /dev/null +++ b/stockton-levels/src/q3/file.rs @@ -0,0 +1,100 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +//! A complete BSP file + +// Trait implementations are stored in their own files. + +use bitvec::prelude::*; + +use self::header::Header; +use crate::types::Result; + +use super::*; +use crate::traits::textures::Texture; +use crate::traits::entities::Entity; +use crate::traits::planes::Plane; +use crate::traits::vertices::{Vertex, MeshVert}; +use crate::traits::light_maps::LightMap; +use crate::traits::light_vols::LightVol; +use crate::traits::brushes::Brush; +use crate::traits::effects::Effect; +use crate::traits::faces::Face; +use crate::traits::tree::BSPNode; +use crate::traits::models::Model; + +/// A parsed Quake 3 BSP File. +pub struct Q3BSPFile { + pub(crate) visdata: Box<[BitBox<Local, u8>]>, + pub(crate) textures: Box<[Texture]>, + pub(crate) entities: Box<[Entity]>, + pub(crate) planes: Box<[Plane]>, + pub(crate) vertices: Box<[Vertex]>, + pub(crate) meshverts: Box<[MeshVert]>, + pub(crate) light_maps: Box<[LightMap]>, + pub(crate) light_vols: Box<[LightVol]>, + pub(crate) brushes: Box<[Brush]>, + pub(crate) effects: Box<[Effect]>, + pub(crate) faces: Box<[Face]>, + pub(crate) models: Box<[Model]>, + pub(crate) tree_root: BSPNode, +} + +impl Q3BSPFile { + /// Parse `data` as a quake 3 bsp file. + pub fn new(data: &[u8]) -> Result<Q3BSPFile> { + let header = Header::from(data)?; + + let entities = entities::from_data(header.get_lump(&data, 0))?; + let textures = textures::from_data(header.get_lump(&data, 1))?; + let planes = planes::from_data(header.get_lump(&data, 2))?; + let vertices = vertices::verts_from_data(header.get_lump(&data, 10))?; + let meshverts = vertices::meshverts_from_data(header.get_lump(&data, 11))?; + let light_maps = light_maps::from_data(header.get_lump(&data, 14))?; + let light_vols = light_vols::from_data(header.get_lump(&data, 15))?; + let visdata = visdata::from_data(header.get_lump(&data, 16))?; + let brushes = brushes::from_data( + header.get_lump(&data, 8), + header.get_lump(&data, 9), + textures.len() as u32, + planes.len() as u32 + )?; + let effects = effects::from_data(header.get_lump(&data, 12), brushes.len() as u32)?; + let faces = faces::from_data( + header.get_lump(&data, 13), + textures.len() as u32, + effects.len() as u32, + vertices.len() as u32, + meshverts.len() as u32, + light_maps.len() as u32 + )?; + + let tree_root = tree::from_data( + header.get_lump(&data, 3), + header.get_lump(&data, 4), + header.get_lump(&data, 5), + header.get_lump(&data, 6), + faces.len() as u32, + brushes.len() as u32 + )?; + + let models = models::from_data(header.get_lump(&data, 7), faces.len() as u32, brushes.len() as u32)?; + + Ok(Q3BSPFile { + visdata, textures, entities, planes, vertices, meshverts, light_maps, + light_vols, brushes, effects, faces, tree_root, models + }) + } +} diff --git a/stockton-levels/src/q3/header.rs b/stockton-levels/src/q3/header.rs new file mode 100644 index 0000000..d4e4f2f --- /dev/null +++ b/stockton-levels/src/q3/header.rs @@ -0,0 +1,83 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +use std::convert::TryInto; +use crate::types::{ParseError, Result}; + +const MAGIC_HEADER: &[u8] = &[0x49, 0x42, 0x53, 0x50]; +const HEADER_LEN: usize = 4 + 4 + (17 * 4 * 2); + +/// The header found at the start of a (Q3) bsp file +#[derive(Clone, Copy, Debug)] +pub struct Header { + pub version: u32, + pub dir_entries: [DirEntry; 17], +} + +/// A directory entry, pointing to a lump in the file +#[derive(Clone, Copy, Debug)] +pub struct DirEntry { + /// Offset from beginning of file to start of lump + pub offset: u32, + + /// Length of lump, multiple of 4. + pub length: u32, +} + +impl Header { + /// Deserialise from buffer. + /// # Format + /// string[4] magic Magic number. Always "IBSP". + /// int version Version number. 0x2e for the BSP files distributed with Quake 3. + /// direntry[17] direntries Lump directory, seventeen entries. + pub fn from(v: &[u8]) -> Result<Header> { + if v.len() < HEADER_LEN { + return Err(ParseError::Invalid); + } + let magic = &v[0..4]; + + if magic != MAGIC_HEADER { + return Err(ParseError::Invalid); + } + + let version: &[u8; 4] = v[4..8].try_into().unwrap(); + + let entries: &[u8] = &v[8..144]; + let mut dir_entries: [DirEntry; 17] = [DirEntry { + offset: 0, + length: 0, + }; 17]; + + for n in 0..17 { + let base = &entries[(n * 8)..(n * 8) + 8]; + dir_entries[n] = DirEntry { + offset: u32::from_le_bytes(base[0..4].try_into().unwrap()), + length: u32::from_le_bytes(base[4..8].try_into().unwrap()), + } + } + + Ok(Header { + version: u32::from_le_bytes(*version), + dir_entries, + }) + } + + /// Get the lump at given index from the buffer, with offset & length based on this directory. + pub fn get_lump<'l>(&self, buf: &'l [u8], index: usize) -> &'l [u8] { + let entry = self.dir_entries[index]; + + &buf[entry.offset as usize..entry.offset as usize + entry.length as usize] + } +}
\ No newline at end of file diff --git a/stockton-levels/src/q3/light_maps.rs b/stockton-levels/src/q3/light_maps.rs new file mode 100644 index 0000000..6743aa4 --- /dev/null +++ b/stockton-levels/src/q3/light_maps.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use crate::types::{Result, RGB, ParseError}; +use crate::traits::light_maps::*; +use super::Q3BSPFile; + +/// The size of one LightMap +const LIGHTMAP_SIZE: usize = 128 * 128 * 3; + +/// Parse the LightMap data from a bsp file. +pub fn from_data(data: &[u8]) -> Result<Box<[LightMap]>> { + if data.len() % LIGHTMAP_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / LIGHTMAP_SIZE; + + let mut maps = Vec::with_capacity(length as usize); + for n in 0..length { + let raw = &data[n * LIGHTMAP_SIZE..(n + 1) * LIGHTMAP_SIZE]; + let mut map: [[RGB; 128]; 128] = [[RGB::white(); 128]; 128]; + + for x in 0..128 { + for y in 0..128 { + let offset = (x * 128 * 3) + (y * 3); + map[x][y] = RGB::from_slice(&raw[offset..offset + 3]); + } + } + maps.push(LightMap { map }) + } + + Ok(maps.into_boxed_slice()) +} + +impl<'a> HasLightMaps<'a> for Q3BSPFile { + type LightMapsIter = std::slice::Iter<'a, LightMap>; + + fn lightmaps_iter(&'a self) -> Self::LightMapsIter { + self.light_maps.iter() + } + + fn get_lightmap(&'a self, index: u32) -> &'a LightMap { + &self.light_maps[index as usize] + } +}
\ No newline at end of file diff --git a/stockton-levels/src/q3/light_vols.rs b/stockton-levels/src/q3/light_vols.rs new file mode 100644 index 0000000..02c4cb4 --- /dev/null +++ b/stockton-levels/src/q3/light_vols.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::convert::TryInto; + +use crate::types::{Result, ParseError, RGB}; +use crate::traits::light_vols::*; +use super::Q3BSPFile; + +const VOL_LENGTH: usize = (3 * 2) + 2; + +pub fn from_data(data: &[u8]) -> Result<Box<[LightVol]>> { + if data.len() % VOL_LENGTH != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / VOL_LENGTH; + + let mut vols = Vec::with_capacity(length); + for n in 0..length { + let data = &data[n * VOL_LENGTH..(n + 1) * VOL_LENGTH]; + vols.push(LightVol { + ambient: RGB::from_slice(&data[0..3]), + directional: RGB::from_slice(&data[3..6]), + dir: data[6..8].try_into().unwrap(), + }); + } + + Ok(vols.into_boxed_slice()) +} + + +impl<'a> HasLightVols<'a> for Q3BSPFile { + type LightVolsIter = std::slice::Iter<'a, LightVol>; + + fn lightvols_iter(&'a self) -> Self::LightVolsIter { + self.light_vols.iter() + } + + fn get_lightvol(&'a self, index: u32) -> &'a LightVol { + &self.light_vols[index as usize] + } +} diff --git a/stockton-levels/src/q3/mod.rs b/stockton-levels/src/q3/mod.rs new file mode 100644 index 0000000..72634f8 --- /dev/null +++ b/stockton-levels/src/q3/mod.rs @@ -0,0 +1,33 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +//! Parsing data from Q3 and similar BSPs + +mod visdata; +mod header; +mod textures; +mod entities; +mod planes; +mod vertices; +mod light_maps; +mod light_vols; +mod brushes; +mod effects; +mod faces; +mod tree; +mod models; +pub mod file; + +pub use self::file::Q3BSPFile;
\ No newline at end of file diff --git a/stockton-levels/src/q3/models.rs b/stockton-levels/src/q3/models.rs new file mode 100644 index 0000000..e2307d6 --- /dev/null +++ b/stockton-levels/src/q3/models.rs @@ -0,0 +1,86 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use crate::helpers::{slice_to_u32, slice_to_vec3}; +use crate::types::{Result, ParseError}; +use crate::traits::models::*; +use super::Q3BSPFile; + +const MODEL_SIZE: usize = (4 * 3 * 2) + (4 * 4); + +pub fn from_data( + data: &[u8], + n_faces: u32, + n_brushes: u32, +) -> Result<Box<[Model]>> { + if data.len() % MODEL_SIZE != 0 { + return Err(ParseError::Invalid); + } + let n_models = data.len() / MODEL_SIZE; + + let mut models = Vec::with_capacity(n_models); + for n in 0..n_models { + let raw = &data[n * MODEL_SIZE..(n + 1) * MODEL_SIZE]; + + let mins = slice_to_vec3(&raw[0..12]); + let maxs = slice_to_vec3(&raw[12..24]); + + let faces_idx = { + let start = slice_to_u32(&raw[24..28]); + let n = slice_to_u32(&raw[28..32]); + + if start + n > n_faces { + return Err(ParseError::Invalid); + } + + start..start+n + }; + + let brushes_idx = { + let start = slice_to_u32(&raw[32..36]); + let n = slice_to_u32(&raw[36..40]); + + if start + n > n_brushes { + return Err(ParseError::Invalid); + } + + start..start+n + }; + + models.push(Model { + mins, + maxs, + faces_idx, + brushes_idx, + }) + } + + Ok(models.into_boxed_slice()) +} + + +impl<'a> HasModels<'a> for Q3BSPFile { + type ModelsIter = std::slice::Iter<'a, Model>; + + fn models_iter(&'a self) -> Self::ModelsIter { + self.models.iter() + } + + fn get_model(&'a self, index: u32) -> &'a Model { + &self.models[index as usize] + } +} diff --git a/stockton-levels/src/q3/planes.rs b/stockton-levels/src/q3/planes.rs new file mode 100644 index 0000000..970ddde --- /dev/null +++ b/stockton-levels/src/q3/planes.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// rust-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// rust-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with rust-bsp. If not, see <http://www.gnu.org/licenses/>. + +const PLANE_SIZE: usize = (4 * 3) + 4; + +use crate::helpers::{slice_to_f32, slice_to_vec3}; +use crate::types::{Result, ParseError}; +use crate::traits::planes::*; +use super::Q3BSPFile; + +/// Parse a lump of planes. +/// A lump is (data length / plane size) planes long +pub fn from_data(data: &[u8]) -> Result<Box<[Plane]>> { + + let length = data.len() / PLANE_SIZE; + if data.is_empty() || data.len() % PLANE_SIZE != 0 || length % 2 != 0 { + return Err(ParseError::Invalid); + } + + + let mut planes = Vec::with_capacity(length / 2); + for n in 0..length { + let offset = n * PLANE_SIZE; + let plane = &data[offset..offset + PLANE_SIZE]; + planes.push(Plane { + normal: slice_to_vec3(&plane[0..12]), + dist: slice_to_f32(&plane[12..16]), + }); + } + + Ok(planes.into_boxed_slice()) +} + +impl<'a> HasPlanes<'a> for Q3BSPFile { + type PlanesIter = std::slice::Iter<'a, Plane>; + + fn planes_iter(&'a self) -> Self::PlanesIter { + self.planes.iter() + } +}
\ No newline at end of file diff --git a/stockton-levels/src/q3/textures.rs b/stockton-levels/src/q3/textures.rs new file mode 100644 index 0000000..264389e --- /dev/null +++ b/stockton-levels/src/q3/textures.rs @@ -0,0 +1,147 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::str; + +use super::Q3BSPFile; +use crate::traits::textures::*; +use crate::helpers::slice_to_u32; +use crate::types::{Result, ParseError}; + +const TEXTURE_LUMP_SIZE: usize = 64 + 4 + 4; + +/// Try to parse the given buffer as an entities lump. +/// # Format +/// Each entity is: +/// string[64] name Texture name. +/// int flags Surface flags. +/// int contents Content flags. +/// Length of entities is total lump size / TEXTURE_LUMP_SIZE (64 + 4 + 4) +pub fn from_data(lump: &[u8]) -> Result<Box<[Texture]>> { + if lump.is_empty() || lump.len() % TEXTURE_LUMP_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = lump.len() / TEXTURE_LUMP_SIZE; + + let mut textures = Vec::with_capacity(length); + for n in 0..length { + let offset = n * TEXTURE_LUMP_SIZE; + textures.push(Texture { + name: str::from_utf8(&lump[offset..offset + 64]).map_err(|_| ParseError::Invalid)?.to_owned(), + surface: SurfaceFlags::from_bits_truncate(slice_to_u32(&lump[offset + 64..offset + 68])), + contents: ContentsFlags::from_bits_truncate(slice_to_u32(&lump[offset + 68..offset + 72])), + }); + } + + Ok(textures.into_boxed_slice()) +} + +impl<'a> HasTextures<'a> for Q3BSPFile { + type TexturesIter = std::slice::Iter<'a, Texture>; + + fn textures_iter(&'a self) -> Self::TexturesIter { + self.textures.iter() + } +} + +#[test] +fn textures_single_texture() { + let buf: &[u8] = &[ + b'T', b'E', b'S', b'T', b' ', b'T', b'E', b'X', b'T', b'U', b'R', b'E', b' ', b' ', b' ', + b' ', // name (padded to 64 bytes) + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', 0x43, 0x00, 0x04, 0x00, // surface flags + 0x09, 0x00, 0x00, 0x00, // contents flags + ]; + + let lump = from_data(buf).unwrap(); + + assert_eq!(lump.len(), 1); + + assert_eq!(lump[0].name, format!("{:64}", "TEST TEXTURE")); + assert_eq!( + lump[0].surface, + SurfaceFlags::NO_DAMAGE | SurfaceFlags::SLICK | SurfaceFlags::FLESH | SurfaceFlags::DUST + ); + assert_eq!( + lump[0].contents, + ContentsFlags::SOLID | ContentsFlags::LAVA + ); +} + +#[test] +fn textures_multiple_textures() { + let buf: &[u8] = &[ + b'T', b'E', b'S', b'T', b' ', b'T', b'E', b'X', b'T', b'U', b'R', b'E', b'1', b' ', b' ', + b' ', // name (padded to 64 bytes) + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', 0x43, 0x00, 0x04, 0x00, // surface flags + 0x09, 0x00, 0x00, 0x00, // contents flags + b'T', b'E', b'S', b'T', b' ', b'T', b'E', b'X', b'T', b'U', b'R', b'E', b'2', b' ', b' ', + b' ', // name (padded to 64 bytes) + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', 0x2c, 0x10, 0x00, 0x00, // surface flags + 0x01, 0x00, 0x00, 0x00, // contents flags + b'T', b'E', b'S', b'T', b' ', b'T', b'E', b'X', b'T', b'U', b'R', b'E', b'3', b' ', b' ', + b' ', // name (padded to 64 bytes) + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', + b' ', b' ', b' ', 0x00, 0x0a, 0x00, 0x00, // surface flags + 0x41, 0x00, 0x00, 0x00, // contents flags + ]; + + let lump = from_data(buf).unwrap(); + + assert_eq!(lump.len(), 3); + + assert_eq!(lump[0].name, format!("{:64}", "TEST TEXTURE1")); + assert_eq!(lump[1].name, format!("{:64}", "TEST TEXTURE2")); + assert_eq!(lump[2].name, format!("{:64}", "TEST TEXTURE3")); + + assert_eq!( + lump[0].surface, + SurfaceFlags::NO_DAMAGE | SurfaceFlags::SLICK | SurfaceFlags::FLESH | SurfaceFlags::DUST + ); + assert_eq!( + lump[1].surface, + SurfaceFlags::METAL_STEPS + | SurfaceFlags::NO_MARKS + | SurfaceFlags::LADDER + | SurfaceFlags::SKY + ); + assert_eq!( + lump[2].surface, + SurfaceFlags::POINT_LIGHT | SurfaceFlags::SKIP + ); + + assert_eq!( + lump[0].contents, + ContentsFlags::SOLID | ContentsFlags::LAVA + ); + assert_eq!(lump[1].contents, ContentsFlags::SOLID); + assert_eq!( + lump[2].contents, + ContentsFlags::SOLID | ContentsFlags::FOG + ); +} diff --git a/stockton-levels/src/q3/tree.rs b/stockton-levels/src/q3/tree.rs new file mode 100644 index 0000000..4ef5470 --- /dev/null +++ b/stockton-levels/src/q3/tree.rs @@ -0,0 +1,168 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of rust_bsp. +// +// rust_bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// rust_bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with rust_bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Parses the BSP tree into a usable format + +use crate::helpers::{slice_to_u32, slice_to_i32, slice_to_vec3i}; +use crate::types::{ParseError, Result}; +use crate::traits::tree::*; +use super::Q3BSPFile; + +const NODE_SIZE: usize = 4 + (4 * 2) + (4 * 3) + (4 * 3); +const LEAF_SIZE: usize = 4 * 6 + (4 * 3 * 2); + +pub fn from_data( + nodes: &[u8], + leaves: &[u8], + leaf_faces: &[u8], + leaf_brushes: &[u8], + n_faces: u32, + n_brushes: u32, +) -> Result<BSPNode> { + if nodes.len() % NODE_SIZE != 0 || leaves.len() % LEAF_SIZE != 0 { + return Err(ParseError::Invalid); + } + + Ok(compile_node( + 0, + nodes, + leaves, + leaf_faces, + leaf_brushes, + n_faces, + n_brushes, + )?, + ) +} + + /// Internal function. Visits given node and all its children. Used to recursively build tree. +fn compile_node( + i: i32, + nodes: &[u8], + leaves: &[u8], + leaf_faces: &[u8], + leaf_brushes: &[u8], + n_faces: u32, + n_brushes: u32, +) -> Result<BSPNode> { + if i < 0 { + // Leaf. + let i = i.abs() - 1; + + let raw = &leaves[i as usize * LEAF_SIZE..(i as usize * LEAF_SIZE) + LEAF_SIZE]; + + let faces_idx = { + let start = slice_to_u32(&raw[32..36]) as usize; + let n = slice_to_u32(&raw[36..40]) as usize; + + let mut faces = Vec::with_capacity(n); + if n > 0 { + if start + n > leaf_faces.len() / 4 { + return Err(ParseError::Invalid); + } + + for i in start..start + n { + let face_idx = slice_to_u32(&leaf_faces[i * 4..(i + 1) * 4]); + if face_idx >= n_faces { + return Err(ParseError::Invalid); + } + + faces.push(face_idx); + } + } + + faces.into_boxed_slice() + }; + + let brushes_idx = { + let start = slice_to_u32(&raw[40..44]) as usize; + let n = slice_to_u32(&raw[44..48]) as usize; + let mut brushes = Vec::with_capacity(n); + + if n > 0 { + if start + n > leaf_brushes.len() / 4 { + return Err(ParseError::Invalid); + } + + for i in start..start + n { + let brush_idx = slice_to_u32(&leaf_brushes[i * 4..(i + 1) * 4]); + if brush_idx >= n_brushes { + return Err(ParseError::Invalid); + } + + brushes.push(brush_idx); + } + } + + brushes.into_boxed_slice() + }; + + let leaf = BSPLeaf { + cluster_id: slice_to_u32(&raw[0..4]), + area: slice_to_i32(&raw[4..8]), + // 8..20 = min + // 20..32 = max + faces_idx, + brushes_idx, + }; + + Ok(BSPNode { + plane_idx: 0, + min: slice_to_vec3i(&raw[8..20]), + max: slice_to_vec3i(&raw[20..32]), + value: BSPNodeValue::Leaf(leaf), + }) + } else { + // Node. + let raw = &nodes[i as usize * NODE_SIZE..(i as usize * NODE_SIZE) + NODE_SIZE]; + + let plane_idx = slice_to_u32(&raw[0..4]); + let child_one = compile_node( + slice_to_i32(&raw[4..8]), + nodes, + leaves, + leaf_faces, + leaf_brushes, + n_faces, + n_brushes, + )?; + let child_two = compile_node( + slice_to_i32(&raw[8..12]), + nodes, + leaves, + leaf_faces, + leaf_brushes, + n_faces, + n_brushes, + )?; + let min = slice_to_vec3i(&raw[12..24]); + let max = slice_to_vec3i(&raw[24..36]); + + Ok(BSPNode { + plane_idx, + value: BSPNodeValue::Children(Box::new(child_one), Box::new(child_two)), + min, + max, + }) + } +} + +impl<'a> HasBSPTree<'a> for Q3BSPFile { + fn get_bsp_root(&'a self) -> &'a BSPNode { + &self.tree_root + } +}
\ No newline at end of file diff --git a/stockton-levels/src/q3/vertices.rs b/stockton-levels/src/q3/vertices.rs new file mode 100644 index 0000000..4583aef --- /dev/null +++ b/stockton-levels/src/q3/vertices.rs @@ -0,0 +1,89 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::convert::TryInto; + +use super::Q3BSPFile; +use crate::helpers::{slice_to_i32, slice_to_vec3}; +use crate::types::{Result, ParseError, RGBA}; +use crate::traits::vertices::*; + +/// The size of one vertex +const VERTEX_SIZE: usize = (4 * 3) + (2 * 2 * 4) + (4 * 3) + 4; + +/// Parse a Vertices data from the data in a BSP file. +pub fn verts_from_data(data: &[u8]) -> Result<Box<[Vertex]>> { + if data.len() % VERTEX_SIZE != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / VERTEX_SIZE; + + let mut vertices = Vec::with_capacity(length as usize); + for n in 0..length { + let offset = n * VERTEX_SIZE; + let vertex = &data[offset..offset + VERTEX_SIZE]; + + vertices.push(Vertex { + position: slice_to_vec3(&vertex[0..12]), + tex: TexCoord::from_bytes(&vertex[12..28].try_into().unwrap()), + normal: slice_to_vec3(&vertex[28..40]), + color: RGBA::from_slice(&vertex[40..44]), + }) + } + + Ok(vertices.into_boxed_slice()) +} + +/// Parse the given data as a list of MeshVerts. +pub fn meshverts_from_data(data: &[u8]) -> Result<Box<[MeshVert]>> { + if data.len() % 4 != 0 { + return Err(ParseError::Invalid); + } + let length = data.len() / 4; + + + let mut meshverts = Vec::with_capacity(length as usize); + for n in 0..length { + meshverts.push(slice_to_i32(&data[n * 4..(n + 1) * 4])) + } + + Ok(meshverts.into_boxed_slice()) +} + +impl<'a> HasVertices<'a> for Q3BSPFile { + type VerticesIter = std::slice::Iter<'a, Vertex>; + + fn vertices_iter(&'a self) -> Self::VerticesIter { + self.vertices.iter() + } + + fn get_vertex(&'a self, index: u32) -> &'a Vertex { + &self.vertices[index as usize] + } +} + +impl<'a> HasMeshVerts<'a> for Q3BSPFile { + type MeshVertsIter = std::slice::Iter<'a, MeshVert>; + + fn meshverts_iter(&'a self) -> Self::MeshVertsIter { + self.meshverts.iter() + } + + fn get_meshvert(&self, index: u32) -> MeshVert { + self.meshverts[index as usize] + } +}
\ No newline at end of file diff --git a/stockton-levels/src/q3/visdata.rs b/stockton-levels/src/q3/visdata.rs new file mode 100644 index 0000000..39bfcf4 --- /dev/null +++ b/stockton-levels/src/q3/visdata.rs @@ -0,0 +1,68 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. +//! Parses visdata from Q3 BSPs. + + +use std::vec::IntoIter; +use bitvec::prelude::*; + +use crate::types::{Result, ParseError}; +use crate::traits::visdata::*; +use crate::helpers::slice_to_i32; +use super::file::Q3BSPFile; + +/// Stores cluster-to-cluster visibility information. +pub fn from_data(data: &[u8]) -> Result<Box<[BitBox<Local, u8>]>> { + if data.len() < 8 { + return Err(ParseError::Invalid); + } + + let n_vecs = slice_to_i32(&data[0..4]) as usize; + let size_vecs = slice_to_i32(&data[4..8]) as usize; + + if data.len() - 8 != (n_vecs * size_vecs) { + return Err(ParseError::Invalid); + } + + let mut vecs = Vec::with_capacity(n_vecs); + for n in 0..n_vecs { + let offset = 8 + (n * size_vecs); + let slice = &data[offset..offset + size_vecs]; + vecs.push(BitBox::from_slice(slice)); + } + + Ok(vecs.into_boxed_slice()) +} + +impl HasVisData for Q3BSPFile { + type VisibleIterator = IntoIter<ClusterId>; + + fn all_visible_from(&self, from: ClusterId) -> Self::VisibleIterator { + let mut visible = vec![]; + + for (idx,val) in self.visdata[from as usize].iter().enumerate() { + if *val { + visible.push(idx as u32); + } + } + + visible.into_iter() + } + + fn cluster_visible_from(&self, from: ClusterId, dest: ClusterId) -> bool { + self.visdata[from as usize][dest as usize] + } +} + diff --git a/stockton-levels/src/traits/brushes.rs b/stockton-levels/src/traits/brushes.rs new file mode 100644 index 0000000..0b07653 --- /dev/null +++ b/stockton-levels/src/traits/brushes.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Parses the brushes & brushsides lumps from a bsp file + +/// One brush record. Used for collision detection. +/// "Each brush describes a convex volume as defined by its surrounding surfaces." +#[derive(Debug, Clone, PartialEq)] +pub struct Brush { + pub sides: Box<[BrushSide]>, + pub texture_idx: usize, +} + +/// Bounding surface for brush. +#[derive(Debug, Clone, PartialEq)] +pub struct BrushSide { + pub plane_idx: usize, + pub texture_idx: usize, + pub is_opposing: bool, +} + +pub trait HasBrushes<'a> { + type BrushesIter: Iterator<Item = &'a Brush>; + + fn brushes_iter(&'a self) -> Self::BrushesIter; + fn get_brush(&'a self, index: u32) -> &'a Brush; +} diff --git a/stockton-levels/src/traits/effects.rs b/stockton-levels/src/traits/effects.rs new file mode 100644 index 0000000..c889ea7 --- /dev/null +++ b/stockton-levels/src/traits/effects.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +/// One effect definition +#[derive(Debug, Clone, PartialEq)] +pub struct Effect { + /// The name of the effect - always 64 characters long + pub name: String, + + /// The brush used for this effect + pub brush_idx: u32 + + // todo: unknown: i32 +} + +pub trait HasEffects<'a> { + type EffectsIter: Iterator<Item = &'a Effect>; + + fn effects_iter(&'a self) -> Self::EffectsIter; + fn get_effect(&'a self, index: u32) -> &'a Effect; +} diff --git a/stockton-levels/src/traits/entities.rs b/stockton-levels/src/traits/entities.rs new file mode 100644 index 0000000..6a762e0 --- /dev/null +++ b/stockton-levels/src/traits/entities.rs @@ -0,0 +1,29 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +use std::iter::Iterator; +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq)] +/// A game entity +pub struct Entity { + pub attributes: HashMap<String, String>, +} + +pub trait HasEntities<'a> { + type EntitiesIter: Iterator<Item = &'a Entity>; + + fn entities_iter(&'a self) -> Self::EntitiesIter; +}
\ No newline at end of file diff --git a/stockton-levels/src/traits/faces.rs b/stockton-levels/src/traits/faces.rs new file mode 100644 index 0000000..ff73c73 --- /dev/null +++ b/stockton-levels/src/traits/faces.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use na::{Vector2, Vector3}; + +use std::ops::Range; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(i32)] +pub enum FaceType { + Polygon = 1, + Patch = 2, + Mesh = 3, + Billboard = 4, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Face { + pub face_type: FaceType, + pub texture_idx: usize, + pub effect_idx: Option<usize>, + pub lightmap_idx: Option<usize>, + pub vertices_idx: Range<usize>, + pub meshverts_idx: Range<usize>, + + pub map_start: Vector2<i32>, + pub map_size: Vector2<i32>, + pub map_origin: Vector3<f32>, + pub map_vecs: [Vector3<f32>; 2], + + pub normal: Vector3<f32>, + pub size: Vector2<i32>, +} + +pub trait HasFaces<'a> { + type FacesIter: Iterator<Item = &'a Face>; + + fn faces_iter(&'a self) -> Self::FacesIter; + fn get_face(&'a self, index: u32) -> &'a Face; +} diff --git a/stockton-levels/src/traits/light_maps.rs b/stockton-levels/src/traits/light_maps.rs new file mode 100644 index 0000000..406746d --- /dev/null +++ b/stockton-levels/src/traits/light_maps.rs @@ -0,0 +1,62 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::fmt; + +use crate::types::RGB; + +/// Stores light map textures that help make surface lighting more realistic +#[derive(Clone)] +pub struct LightMap { + pub map: [[RGB; 128]; 128], +} + +impl PartialEq for LightMap { + fn eq(&self, other: &LightMap) -> bool { + for x in 0..128 { + for y in 0..128 { + if self.map[x][y] != other.map[x][y] { + return false; + } + } + } + true + } +} + +impl fmt::Debug for LightMap { + // rust can't derive debug for 3d arrays so done manually + // \_( )_/ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "LightMap {{ map: [")?; + for c in self.map.iter() { + write!(f, "[")?; + for x in c.iter() { + write!(f, "{:?}, ", x)?; + } + write!(f, "], ")?; + } + write!(f, "}}") + } +} + +pub trait HasLightMaps<'a> { + type LightMapsIter: Iterator<Item = &'a LightMap>; + + fn lightmaps_iter(&'a self) -> Self::LightMapsIter; + fn get_lightmap(&'a self, index: u32) -> &'a LightMap; +}
\ No newline at end of file diff --git a/stockton-levels/src/traits/light_vols.rs b/stockton-levels/src/traits/light_vols.rs new file mode 100644 index 0000000..027709a --- /dev/null +++ b/stockton-levels/src/traits/light_vols.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::convert::TryInto; + +use crate::types::RGB; + +#[derive(Debug, Clone, Copy)] +pub struct LightVol { + pub ambient: RGB, + pub directional: RGB, + pub dir: [u8; 2], +} + +pub trait HasLightVols<'a> { + type LightVolsIter: Iterator<Item = &'a LightVol>; + + fn lightvols_iter(&'a self) -> Self::LightVolsIter; + fn get_lightvol(&'a self, index: u32) -> &'a LightVol; +} diff --git a/stockton-levels/src/traits/mod.rs b/stockton-levels/src/traits/mod.rs new file mode 100644 index 0000000..15bac30 --- /dev/null +++ b/stockton-levels/src/traits/mod.rs @@ -0,0 +1,41 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. +//! Traits for parts of files that can exist + +pub mod visdata; +pub mod entities; +pub mod textures; +pub mod planes; +pub mod vertices; +pub mod light_maps; +pub mod light_vols; +pub mod brushes; +pub mod effects; +pub mod faces; +pub mod tree; +pub mod models; + +pub use self::visdata::HasVisData; +pub use self::textures::HasTextures; +pub use self::entities::HasEntities; +pub use self::planes::HasPlanes; +pub use self::vertices::{HasVertices, HasMeshVerts}; +pub use self::light_maps::HasLightMaps; +pub use self::light_vols::HasLightVols; +pub use self::brushes::HasBrushes; +pub use self::effects::HasEffects; +pub use self::faces::HasFaces; +pub use self::tree::HasBSPTree; +pub use self::models::HasModels;
\ No newline at end of file diff --git a/stockton-levels/src/traits/models.rs b/stockton-levels/src/traits/models.rs new file mode 100644 index 0000000..ede7f78 --- /dev/null +++ b/stockton-levels/src/traits/models.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use na::Vector3; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct Model { + pub mins: Vector3<f32>, + pub maxs: Vector3<f32>, + pub faces_idx: Range<u32>, + pub brushes_idx: Range<u32>, +} + +pub trait HasModels<'a> { + type ModelsIter: Iterator<Item = &'a Model>; + + fn models_iter(&'a self) -> Self::ModelsIter; + fn get_model(&'a self, index: u32) -> &'a Model; +} diff --git a/stockton-levels/src/traits/planes.rs b/stockton-levels/src/traits/planes.rs new file mode 100644 index 0000000..6962c71 --- /dev/null +++ b/stockton-levels/src/traits/planes.rs @@ -0,0 +1,42 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// rust-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// rust-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with rust-bsp. If not, see <http://www.gnu.org/licenses/>. + +use std::iter::Iterator; +use na::Vector3; + +/// The planes lump from a BSP file. +/// Found at lump index 2 in a q3 bsp. +#[derive(Debug, Clone)] +pub struct PlanesLump { + pub planes: Box<[Plane]>, +} + +/// Generic plane, referenced by nodes & brushsizes +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Plane { + /// Plane normal + pub normal: Vector3<f32>, + + /// Distance from origin to plane along normal + pub dist: f32, +} + +pub trait HasPlanes<'a> { + type PlanesIter: Iterator<Item = &'a Plane>; + + fn planes_iter(&'a self) -> Self::PlanesIter; +}
\ No newline at end of file diff --git a/stockton-levels/src/traits/textures.rs b/stockton-levels/src/traits/textures.rs new file mode 100644 index 0000000..d1a2a83 --- /dev/null +++ b/stockton-levels/src/traits/textures.rs @@ -0,0 +1,149 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +use std::iter::Iterator; + +#[derive(Debug, Clone, PartialEq)] +/// A texture from a BSP File. +pub struct Texture { + pub name: String, + pub surface: SurfaceFlags, + pub contents: ContentsFlags, +} + + +bitflags!( + /// Extracted from the Q3 arena engine code. + /// https://github.com/id-Software/Quake-III-Arena/blob/master/code/game/surfaceflags.h + pub struct SurfaceFlags: u32 { + /// never give falling damage + const NO_DAMAGE = 0x1; + + /// affects game physics + const SLICK = 0x2; + + /// lighting from environment map + const SKY = 0x4; + + /// don't make missile explosions + const NO_IMPACT = 0x10; + + /// function as a ladder + const LADDER = 0x8; + + /// don't leave missile marks + const NO_MARKS = 0x20; + + /// make flesh sounds and effects + const FLESH = 0x40; + + /// don't generate a drawsurface at all + const NODRAW = 0x80; + + /// make a primary bsp splitter + const HINT = 0x01_00; + + /// completely ignore, allowing non-closed brushes + const SKIP = 0x02_00; + + /// surface doesn't need a lightmap + const NO_LIGHT_MAP = 0x04_00; + + /// generate lighting info at vertexes + const POINT_LIGHT = 0x08_00; + + /// clanking footsteps + const METAL_STEPS = 0x10_00; + + /// no footstep sounds + const NO_STEPS = 0x20_00; + + /// don't collide against curves with this set + const NON_SOLID = 0x40_00; + + /// act as a light filter during q3map -light + const LIGHT_FILTER = 0x80_00; + + /// do per-pixel light shadow casting in q3map + const ALPHA_SHADOW = 0x01_00_00; + + /// don't dlight even if solid (solid lava, skies) + const NO_DLIGHT = 0x02_00_00; + + /// leave a dust trail when walking on this surface + const DUST = 0x04_00_00; + } +); + +bitflags!( + /// Extracted from the Q3 arena engine code. Less documented than `SurfaceFlags`. + /// https://github.com/id-Software/Quake-III-Arena/blob/master/code/game/surfaceflags.h + pub struct ContentsFlags: u32 { + // an eye is never valid in a solid + const SOLID = 0x1; + const LAVA = 0x8; + const SLIME = 0x10; + const WATER = 0x20; + const FOG = 0x40; + + const NOT_TEAM1 = 0x00_80; + const NOT_TEAM2 = 0x01_00; + const NOT_BOT_CLIP = 0x02_00; + + const AREA_PORTAL = 0x80_00; + + /// bot specific contents type + const PLAYER_CLIP = 0x01_00_00; + + /// bot specific contents type + const MONSTER_CLIP = 0x02_00_00; + + const TELEPORTER = 0x04_00_00; + const JUMP_PAD = 0x08_00_00; + const CLUSTER_PORTAL = 0x10_00_00; + const DO_NOT_ENTER = 0x20_00_00; + const BOT_CLIP = 0x40_00_00; + const MOVER = 0x80_00_00; + + // removed before bsping an entity + const ORIGIN = 0x01_00_00_00; + + // should never be on a brush, only in game + const BODY = 0x02_00_00_00; + + /// brush not used for the bsp + const DETAIL = 0x08_00_00_00; + + /// brush not used for the bsp + const CORPSE = 0x04_00_00_00; + + /// brushes used for the bsp + const STRUCTURAL = 0x10_00_00_00; + + /// don't consume surface fragments inside + const TRANSLUCENT = 0x20_00_00_00; + + const TRIGGER = 0x40_00_00_00; + + /// don't leave bodies or items (death fog, lava) + const NODROP = 0x80_00_00_00; + } +); + +pub trait HasTextures<'a> { + type TexturesIter: Iterator<Item = &'a Texture>; + + fn textures_iter(&'a self) -> Self::TexturesIter; +} diff --git a/stockton-levels/src/traits/tree.rs b/stockton-levels/src/traits/tree.rs new file mode 100644 index 0000000..06cfd80 --- /dev/null +++ b/stockton-levels/src/traits/tree.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of rust_bsp. +// +// rust_bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// rust_bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with rust_bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Parses the BSP tree into a usable format + +use na::Vector3; + +/// A node in a BSP tree. +/// Either has two children *or* a leaf entry. +#[derive(Debug, Clone)] +pub struct BSPNode { + pub plane_idx: u32, + pub min: Vector3<i32>, + pub max: Vector3<i32>, + pub value: BSPNodeValue +} + +#[derive(Debug, Clone)] +pub enum BSPNodeValue { + Leaf (BSPLeaf), + Children (Box<BSPNode>, Box<BSPNode>) +} + +/// A leaf in a BSP tree. +/// Will be under a `BSPNode`, min and max values are stored there. +#[derive(Debug, Clone)] +pub struct BSPLeaf { + pub cluster_id: u32, + pub area: i32, + pub faces_idx: Box<[u32]>, + pub brushes_idx: Box<[u32]>, +} + +pub trait HasBSPTree<'a> { + fn get_bsp_root(&'a self) -> &'a BSPNode; +}
\ No newline at end of file diff --git a/stockton-levels/src/traits/vertices.rs b/stockton-levels/src/traits/vertices.rs new file mode 100644 index 0000000..cb06b3a --- /dev/null +++ b/stockton-levels/src/traits/vertices.rs @@ -0,0 +1,70 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// stockton-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// stockton-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with stockton-bsp. If not, see <http://www.gnu.org/licenses/>. + +use crate::helpers::{slice_to_f32}; +use crate::types::RGBA; +use na::Vector3; +use std::convert::TryInto; + + +/// A vertex, used to describe a face. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Vertex { + pub position: Vector3<f32>, + pub tex: TexCoord, + pub normal: Vector3<f32>, + pub color: RGBA, +} + +/// Represents a TexCoord. 0 = surface, 1= lightmap. +/// This could also be written as [[f32; 2]; 2] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TexCoord { + pub u: [f32; 2], + pub v: [f32; 2], +} + +impl TexCoord { + /// Internal function. Converts a slice to a TexCoord. + pub fn from_bytes(bytes: &[u8; 16]) -> TexCoord { + TexCoord { + u: [slice_to_f32(&bytes[0..4]), slice_to_f32(&bytes[4..8])], + v: [slice_to_f32(&bytes[8..12]), slice_to_f32(&bytes[12..16])], + } + } +} + +/// A vertex offset, used to describe generalised triangle meshes +pub type MeshVert = i32; + +pub trait HasVertices<'a> { + type VerticesIter: Iterator<Item = &'a Vertex>; + + fn vertices_iter(&'a self) -> Self::VerticesIter; + fn get_vertex(&'a self, index: u32) -> &'a Vertex; +} + +pub trait HasMeshVerts<'a>: HasVertices<'a> { + type MeshVertsIter: Iterator<Item = &'a MeshVert>; + + fn meshverts_iter(&'a self) -> Self::MeshVertsIter; + fn get_meshvert(&self, index: u32) -> MeshVert; + + fn resolve_meshvert(&'a self, index: u32) -> &'a Vertex { + self.get_vertex(self.get_meshvert(index).try_into().unwrap()) + } +}
\ No newline at end of file diff --git a/stockton-levels/src/traits/visdata.rs b/stockton-levels/src/traits/visdata.rs new file mode 100644 index 0000000..27849d3 --- /dev/null +++ b/stockton-levels/src/traits/visdata.rs @@ -0,0 +1,29 @@ +// Copyright (C) Oscar Shrimpton 2019 + +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +use std::iter::Iterator; + +pub type ClusterId = u32; + +pub trait HasVisData { + /// The iterator returned from all_visible_from + type VisibleIterator: Iterator<Item = ClusterId>; + + /// Returns an iterator of all clusters visible from the given Cluster ID + fn all_visible_from<'a>(&'a self, from: ClusterId) -> Self::VisibleIterator; + + /// Returns true if `dest` is visible from `from`. + fn cluster_visible_from(&self, from: ClusterId, dest: ClusterId) -> bool; +}
\ No newline at end of file diff --git a/stockton-levels/src/types.rs b/stockton-levels/src/types.rs new file mode 100644 index 0000000..7bccd23 --- /dev/null +++ b/stockton-levels/src/types.rs @@ -0,0 +1,93 @@ +// Copyright (C) 2019 Oscar Shrimpton +// +// This file is part of stockton-bsp. +// +// rust-bsp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// rust-bsp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with rust-bsp. If not, see <http://www.gnu.org/licenses/>. + +//! Various types used in parsed BSP files. + +use std::convert::TryInto; + +/// RGBA Colour (0-255) +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RGBA { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl RGBA { + /// Interpret the given bytes as an RGBA colour. + pub fn from_bytes(bytes: [u8; 4]) -> RGBA { + RGBA { + r: bytes[0], + g: bytes[1], + b: bytes[2], + a: bytes[3], + } + } + + /// Convert a slice to an RGBA colour + /// # Panics + /// If slice is not 4 bytes long. + pub fn from_slice(slice: &[u8]) -> RGBA { + RGBA::from_bytes(slice.try_into().unwrap()) + } +} + +/// RGB Colour (0-255) +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RGB { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl RGB { + /// 255, 255, 255 + pub fn white() -> RGB { + RGB { + r: 255, + g: 255, + b: 255, + } + } + + /// Interpret the given bytes as an RGB colour. + pub fn from_bytes(bytes: [u8; 3]) -> RGB { + RGB { + r: bytes[0], + g: bytes[1], + b: bytes[2], + } + } + + /// Convert a slice to an RGB colour + /// # Panics + /// If slice is not 3 bytes long. + pub fn from_slice(slice: &[u8]) -> RGB { + RGB::from_bytes(slice.try_into().unwrap()) + } +} + +#[derive(Debug)] +/// An error encountered while parsing. +pub enum ParseError { + Unsupported, + Invalid +} + +/// Standard result type. +pub type Result<T> = std::result::Result<T, ParseError>;
\ No newline at end of file |