diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:23 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:23 +0100 |
commit | 439219e74090c7158f8dbc33fed4107a5eb7c003 (patch) | |
tree | 7ba62254b2d888578ff6c1c8de4f0f35c01c75dd | |
parent | 04f17923d38171f07f72603a54237f20ca3572dd (diff) |
refactor(levels): no longer q3 specific
42 files changed, 327 insertions, 1866 deletions
diff --git a/examples/render-bsp/src/main.rs b/examples/render-bsp/src/main.rs index 16854f5..a3cea25 100644 --- a/examples/render-bsp/src/main.rs +++ b/examples/render-bsp/src/main.rs @@ -19,7 +19,7 @@ use stockton_contrib::delta_time::*; use stockton_contrib::flycam::*; use stockton_input::{Axis, InputManager, Mouse}; -use stockton_levels::{prelude::*, q3::Q3BspFile}; +use stockton_levels::prelude::*; use stockton_render::error::full_error_display; use stockton_render::systems::*; @@ -28,6 +28,8 @@ use stockton_render::{Renderer, UiState, WindowEvent}; use stockton_types::components::{CameraSettings, Transform}; use stockton_types::{Session, Vector3}; +type MapFile = (); + #[derive(InputManager, Default, Clone, Debug)] struct MovementInputs { #[axis] @@ -91,14 +93,8 @@ fn try_main() -> Result<()> { } window.set_cursor_visible(false); - // Parse the map file and swizzle the co-ords - let path = Path::new("./examples/render-bsp/data/newtest.bsp"); - let mut data = Vec::new(); - File::open(path)?.read_to_end(&mut data)?; - let bsp: Result<Q3BspFile<Q3System>, stockton_levels::types::ParseError> = - Q3BspFile::parse_file(data.as_slice()); - let bsp: Q3BspFile<Q3System> = bsp.context("Error loading bsp")?; - let bsp: Q3BspFile<VulkanSystem> = bsp.swizzle_to(); + // TODO: Parse the map file + let bsp = todo!(); // Create the UI State let mut ui = UiState::new(); @@ -139,16 +135,13 @@ fn try_main() -> Result<()> { move |schedule| { schedule .add_system(update_deltatime_system()) - .add_system(process_window_events_system::< - MovementInputsManager, - Q3BspFile<VulkanSystem>, - >()) + .add_system(process_window_events_system::<MovementInputsManager, MapFile>()) .flush() .add_system(hello_world_system()) .add_system(flycam_move_system::<MovementInputsManager>()) .flush() - .add_system(calc_vp_matrix_system::<Q3BspFile<VulkanSystem>>()) - .add_thread_local(do_render_system::<Q3BspFile<VulkanSystem>>()); + .add_system(calc_vp_matrix_system::<MapFile>()) + .add_thread_local(do_render_system::<MapFile>()); }, ); diff --git a/stockton-levels/Cargo.toml b/stockton-levels/Cargo.toml index 2cae877..526b21c 100644 --- a/stockton-levels/Cargo.toml +++ b/stockton-levels/Cargo.toml @@ -2,13 +2,11 @@ name = "stockton-levels" version = "0.1.0" authors = ["Oscar <oscar.shrimpton.personal@gmail.com>"] -description = "Library for parsing different types of .bsp files." +description = "Traits relating to levels renderable by stockton." repository = "https://github.com/tcmal/stockton" homepage = "https://github.com/tcmal/stockton" edition = "2018" [dependencies] nalgebra = "^0.20" -bitflags = "^1.2" -thiserror = "1.0.25" -bitvec = "0.17" +serde = { version = "1.0", features = ["derive"] }
\ No newline at end of file diff --git a/stockton-levels/src/coords.rs b/stockton-levels/src/coords.rs deleted file mode 100644 index 1b1fa55..0000000 --- a/stockton-levels/src/coords.rs +++ /dev/null @@ -1,41 +0,0 @@ -// 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/>. -//! Marker traits for different co-ordinate systems, and functions to swizzle between them - -use na::base::Scalar; -use na::Vector3; -use std::ops::Neg; - -pub trait CoordSystem {} - -/// X points East, Y points South, Z points downwards -pub struct Q3System; -impl CoordSystem for Q3System {} - -/// X points east, Y points downwards, Z points inwards -pub struct VulkanSystem; -impl CoordSystem for VulkanSystem {} - -pub struct Swizzler; - -pub trait SwizzleFromTo<F: CoordSystem, T: CoordSystem> { - fn swizzle<U: Scalar + Copy + Neg<Output = U>>(vec: &mut Vector3<U>); -} - -impl SwizzleFromTo<Q3System, VulkanSystem> for Swizzler { - fn swizzle<U: Scalar + Copy + Neg<Output = U>>(vec: &mut Vector3<U>) { - let temp = vec.y; - vec.y = vec.z; - vec.z = -temp; - } -} diff --git a/stockton-levels/src/features.rs b/stockton-levels/src/features.rs index 9b05772..f748bd0 100644 --- a/stockton-levels/src/features.rs +++ b/stockton-levels/src/features.rs @@ -12,8 +12,7 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. //! Marker traits for different feature sets -use crate::coords::CoordSystem; -use crate::traits::*; +use crate::parts::*; -pub trait MinBspFeatures<S: CoordSystem>: HasBspTree<S> + Send + Sync {} -impl<T, S: CoordSystem> MinBspFeatures<S> for T where T: HasBspTree<S> + Send + Sync {} +pub trait MinRenderFeatures: HasFaces + HasTextures + Send + Sync {} +impl<T> MinRenderFeatures for T where T: HasFaces + HasTextures + Send + Sync {} diff --git a/stockton-levels/src/helpers.rs b/stockton-levels/src/helpers.rs deleted file mode 100644 index 0fe3e96..0000000 --- a/stockton-levels/src/helpers.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! 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 u32s into a 2D vector -/// # Panics -/// If slice isn't 8 bytes long. -pub fn slice_to_vec2ui(slice: &[u8]) -> Vector2<u32> { - Vector2::new(slice_to_u32(&slice[0..4]), slice_to_u32(&slice[4..8])) -} diff --git a/stockton-levels/src/lib.rs b/stockton-levels/src/lib.rs index a71d8e2..49609c2 100644 --- a/stockton-levels/src/lib.rs +++ b/stockton-levels/src/lib.rs @@ -10,18 +10,11 @@ // 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. -#![allow(incomplete_features)] -#![feature(generic_associated_types)] +//! Interfaces & Data structures stockton expects when rendering a level. extern crate nalgebra as na; -#[macro_use] -extern crate bitflags; -pub mod coords; pub mod features; -mod helpers; +pub mod parts; pub mod prelude; -pub mod q3; -pub mod traits; pub mod types; diff --git a/stockton-levels/src/parts/entities.rs b/stockton-levels/src/parts/entities.rs new file mode 100644 index 0000000..7a5ac74 --- /dev/null +++ b/stockton-levels/src/parts/entities.rs @@ -0,0 +1,36 @@ +use std::iter::Iterator; + +pub type EntityRef = u32; + +/// A game entity +pub trait IsEntity<C: HasEntities + ?Sized> { + fn get_attr(&self, container: &C) -> Option<&str>; +} + +pub trait HasEntities { + type Entity: IsEntity<Self>; + + fn get_entity(&self, idx: EntityRef) -> Option<&Self::Entity>; + fn iter_entities(&self) -> Entities<Self> { + Entities { + next: 0, + container: self, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Entities<'a, T: HasEntities + ?Sized> { + next: EntityRef, + container: &'a T, +} + +impl<'a, T: HasEntities> Iterator for Entities<'a, T> { + type Item = &'a T::Entity; + + fn next(&mut self) -> Option<Self::Item> { + let res = self.container.get_entity(self.next); + self.next += 1; + res + } +} diff --git a/stockton-levels/src/parts/faces.rs b/stockton-levels/src/parts/faces.rs new file mode 100644 index 0000000..0168cd8 --- /dev/null +++ b/stockton-levels/src/parts/faces.rs @@ -0,0 +1,43 @@ +use super::{textures::TextureRef, vertices::Vertex}; +use serde::{Deserialize, Serialize}; + +pub type FaceRef = u32; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Geometry { + Vertices(Vertex, Vertex, Vertex), +} + +pub trait IsFace<C: HasFaces + ?Sized> { + fn index(&self, container: &C) -> FaceRef; + fn geometry(&self, container: &C) -> Geometry; + fn texture_idx(&self, container: &C) -> TextureRef; +} + +pub trait HasFaces { + type Face: IsFace<Self>; + + fn get_face(&self, index: FaceRef) -> Option<&Self::Face>; + fn iter_faces(&self) -> Faces<Self> { + Faces { + next: 0, + container: self, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Faces<'a, T: HasFaces + ?Sized> { + next: FaceRef, + container: &'a T, +} + +impl<'a, T: HasFaces> Iterator for Faces<'a, T> { + type Item = &'a T::Face; + + fn next(&mut self) -> Option<Self::Item> { + let res = self.container.get_face(self.next); + self.next += 1; + res + } +} diff --git a/stockton-levels/src/parts/mod.rs b/stockton-levels/src/parts/mod.rs new file mode 100644 index 0000000..05697f0 --- /dev/null +++ b/stockton-levels/src/parts/mod.rs @@ -0,0 +1,17 @@ +mod entities; +mod faces; +mod textures; +mod vertices; +mod visdata; + +pub mod data { + pub use super::entities::{Entities, EntityRef}; + pub use super::faces::{FaceRef, Faces, Geometry}; + pub use super::textures::{TextureRef, Textures}; + pub use super::vertices::{Vertex, VertexRef}; +} + +pub use entities::{HasEntities, IsEntity}; +pub use faces::{HasFaces, IsFace}; +pub use textures::{HasTextures, IsTexture}; +pub use visdata::HasVisData; diff --git a/stockton-levels/src/parts/textures.rs b/stockton-levels/src/parts/textures.rs new file mode 100644 index 0000000..8e6cb9a --- /dev/null +++ b/stockton-levels/src/parts/textures.rs @@ -0,0 +1,48 @@ +// 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 TextureRef = u32; + +pub trait IsTexture { + fn name(&self) -> &str; +} + +pub trait HasTextures { + type Texture: IsTexture; + + fn get_texture(&self, idx: TextureRef) -> Option<&Self::Texture>; + fn iter_textures(&self) -> Textures<Self> { + Textures { + next: 0, + container: self, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Textures<'a, T: HasTextures + ?Sized> { + next: TextureRef, + container: &'a T, +} + +impl<'a, T: HasTextures> Iterator for Textures<'a, T> { + type Item = &'a T::Texture; + + fn next(&mut self) -> Option<Self::Item> { + let res = self.container.get_texture(self.next); + self.next += 1; + res + } +} diff --git a/stockton-levels/src/parts/vertices.rs b/stockton-levels/src/parts/vertices.rs new file mode 100644 index 0000000..0b7dfd6 --- /dev/null +++ b/stockton-levels/src/parts/vertices.rs @@ -0,0 +1,156 @@ +use crate::types::Rgba; +use na::{Vector2, Vector3}; +use serde::de; +use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::Deserialize; +use std::fmt; + +pub type VertexRef = u32; + +/// A vertex, used to describe a face. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Vertex { + pub position: Vector3<f32>, + pub tex: Vector2<f32>, + pub color: Rgba, +} + +impl Serialize for Vertex { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let mut state = serializer.serialize_struct("Vertex", 5)?; + state.serialize_field("pos_x", &self.position.x)?; + state.serialize_field("pos_y", &self.position.y)?; + state.serialize_field("pos_z", &self.position.z)?; + state.serialize_field("tex_u", &self.tex.x)?; + state.serialize_field("tex_v", &self.tex.y)?; + state.serialize_field("color", &self.color)?; + + state.end() + } +} + +impl<'de> Deserialize<'de> for Vertex { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + PosX, + PosY, + PosZ, + TexU, + TexV, + Color, + } + const FIELDS: &'static [&'static str] = + &["pos_x", "pos_y", "pos_z", "tex_x", "tex_y", "color"]; + + struct VertexVisitor; + + impl<'de> Visitor<'de> for VertexVisitor { + type Value = Vertex; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Vertex") + } + + fn visit_seq<V>(self, mut seq: V) -> Result<Vertex, V::Error> + where + V: SeqAccess<'de>, + { + let pos_x = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let pos_y = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let pos_z = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(2, &self))?; + let tex_u = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(3, &self))?; + let tex_v = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(4, &self))?; + let color = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(5, &self))?; + Ok(Vertex { + position: Vector3::new(pos_x, pos_y, pos_z), + tex: Vector2::new(tex_u, tex_v), + color, + }) + } + + fn visit_map<V>(self, mut map: V) -> Result<Vertex, V::Error> + where + V: MapAccess<'de>, + { + let mut pos_x = None; + let mut pos_y = None; + let mut pos_z = None; + let mut tex_u = None; + let mut tex_v = None; + let mut color = None; + while let Some(key) = map.next_key()? { + match key { + Field::PosX => { + if pos_x.is_some() { + return Err(de::Error::duplicate_field("pos_x")); + } + pos_x = Some(map.next_value()?); + } + Field::PosY => { + if pos_y.is_some() { + return Err(de::Error::duplicate_field("pos_y")); + } + pos_y = Some(map.next_value()?); + } + Field::PosZ => { + if pos_z.is_some() { + return Err(de::Error::duplicate_field("pos_z")); + } + pos_z = Some(map.next_value()?); + } + Field::TexU => { + if tex_u.is_some() { + return Err(de::Error::duplicate_field("tex_u")); + } + tex_u = Some(map.next_value()?); + } + Field::TexV => { + if tex_v.is_some() { + return Err(de::Error::duplicate_field("tex_v")); + } + tex_v = Some(map.next_value()?); + } + Field::Color => { + if color.is_some() { + return Err(de::Error::duplicate_field("color")); + } + color = Some(map.next_value()?); + } + } + } + let position = Vector3::new( + pos_x.ok_or_else(|| de::Error::missing_field("pos_x"))?, + pos_y.ok_or_else(|| de::Error::missing_field("pos_y"))?, + pos_z.ok_or_else(|| de::Error::missing_field("pos_z"))?, + ); + let tex = Vector2::new( + tex_u.ok_or_else(|| de::Error::missing_field("tex_u"))?, + tex_v.ok_or_else(|| de::Error::missing_field("tex_v"))?, + ); + let color = color.ok_or_else(|| de::Error::missing_field("nanos"))?; + Ok(Vertex { + position, + tex, + color, + }) + } + } + + deserializer.deserialize_struct("Vertex", FIELDS, VertexVisitor) + } +} diff --git a/stockton-levels/src/parts/visdata.rs b/stockton-levels/src/parts/visdata.rs new file mode 100644 index 0000000..f311bf7 --- /dev/null +++ b/stockton-levels/src/parts/visdata.rs @@ -0,0 +1,8 @@ +use super::faces::FaceRef; +use na::Vector3; +use std::iter::Iterator; + +pub trait HasVisData { + type Faces: Iterator<Item = FaceRef>; + fn get_visible(pos: Vector3<f32>, rot: Vector3<f32>) -> Self::Faces; +} diff --git a/stockton-levels/src/prelude.rs b/stockton-levels/src/prelude.rs index 2d09252..0da890b 100644 --- a/stockton-levels/src/prelude.rs +++ b/stockton-levels/src/prelude.rs @@ -12,6 +12,5 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. //! Common traits, etc. -pub use crate::coords::*; pub use crate::features::*; -pub use crate::traits::*; +pub use crate::parts::*; diff --git a/stockton-levels/src/q3/brushes.rs b/stockton-levels/src/q3/brushes.rs deleted file mode 100644 index 9cb0d8e..0000000 --- a/stockton-levels/src/q3/brushes.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! 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 super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::slice_to_i32; -use crate::traits::brushes::*; -use crate::types::{ParseError, Result}; - -/// 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<T: CoordSystem> HasBrushes<T> for Q3BspFile<T> { - type BrushesIter<'a> = std::slice::Iter<'a, Brush>; - - fn brushes_iter(&self) -> Self::BrushesIter<'_> { - self.brushes.iter() - } - - fn get_brush(&self, index: u32) -> &Brush { - &self.brushes[index as usize] - } -} diff --git a/stockton-levels/src/q3/effects.rs b/stockton-levels/src/q3/effects.rs deleted file mode 100644 index 369ed80..0000000 --- a/stockton-levels/src/q3/effects.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::str; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::slice_to_u32; -use crate::traits::effects::*; -use crate::types::{ParseError, Result}; - -/// 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<T: CoordSystem> HasEffects<T> for Q3BspFile<T> { - type EffectsIter<'a> = std::slice::Iter<'a, Effect>; - - fn effects_iter(&self) -> Self::EffectsIter<'_> { - self.effects.iter() - } - - fn get_effect(&self, index: u32) -> &Effect { - &self.effects[index as usize] - } -} diff --git a/stockton-levels/src/q3/entities.rs b/stockton-levels/src/q3/entities.rs deleted file mode 100644 index d36eb37..0000000 --- a/stockton-levels/src/q3/entities.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::collections::HashMap; -use std::str; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::traits::entities::*; -use crate::types::{ParseError, Result}; - -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<T: CoordSystem> HasEntities for Q3BspFile<T> { - type EntitiesIter<'a> = std::slice::Iter<'a, Entity>; - - fn entities_iter(&self) -> Self::EntitiesIter<'_> { - self.entities.iter() - } -} diff --git a/stockton-levels/src/q3/faces.rs b/stockton-levels/src/q3/faces.rs deleted file mode 100644 index 95467bb..0000000 --- a/stockton-levels/src/q3/faces.rs +++ /dev/null @@ -1,148 +0,0 @@ -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::{slice_to_i32, slice_to_u32, slice_to_vec2ui, slice_to_vec3}; -use crate::traits::faces::*; -use crate::types::{ParseError, Result}; -use na::Vector3; - -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, - n_effects, - n_vertices, - n_meshverts, - n_lightmaps, - )?); - } - - Ok(faces.into_boxed_slice()) -} - -fn face_from_slice( - data: &[u8], - n_textures: u32, - n_effects: u32, - n_vertices: u32, - n_meshverts: u32, - n_lightmaps: u32, -) -> Result<Face> { - if data.len() != FACE_SIZE { - panic!("tried to call face.from_slice with invalid slice size"); - } - - // texture - let texture_idx = slice_to_u32(&data[0..4]); - if texture_idx >= n_textures { - return Err(ParseError::Invalid); - } - - // effects - let effect_idx = slice_to_u32(&data[4..8]); - 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_u32(&data[8..12])) }; - - // vertices - let vertex_offset = slice_to_u32(&data[12..16]); - let vertex_n = slice_to_u32(&data[16..20]); - 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_u32(&data[20..24]); - let meshverts_n = slice_to_u32(&data[24..28]); - 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]); - let lightmap_idx = if lightmap_idx > 0 { - if lightmap_idx as u32 >= n_lightmaps { - return Err(ParseError::Invalid); - } - - Some(lightmap_idx as u32) - } else { - None - }; - - // map properties - let map_start = slice_to_vec2ui(&data[32..40]); - let map_size = slice_to_vec2ui(&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, map_vec) in map_vecs.iter_mut().enumerate() { - let offset = 60 + (n * 3 * 4); - *map_vec = slice_to_vec3(&data[offset..offset + 12]); - } - - // normal & size - let normal = slice_to_vec3(&data[84..96]); - let size = slice_to_vec2ui(&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<T: CoordSystem> HasFaces<T> for Q3BspFile<T> { - type FacesIter<'a> = std::slice::Iter<'a, Face>; - - fn faces_iter(&self) -> Self::FacesIter<'_> { - self.faces.iter() - } - - fn faces_len(&self) -> u32 { - self.faces.len() as u32 - } - - fn get_face(&self, index: u32) -> &Face { - &self.faces[index as usize] - } -} diff --git a/stockton-levels/src/q3/file.rs b/stockton-levels/src/q3/file.rs deleted file mode 100644 index f577fb0..0000000 --- a/stockton-levels/src/q3/file.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! A complete BSP file - -// Trait implementations are stored in their own files. - -use bitvec::prelude::*; -use std::marker::PhantomData; - -use self::header::Header; -use crate::coords::*; -use crate::types::Result; - -use super::*; -use crate::traits::brushes::Brush; -use crate::traits::effects::Effect; -use crate::traits::entities::Entity; -use crate::traits::faces::Face; -use crate::traits::light_maps::LightMap; -use crate::traits::light_vols::LightVol; -use crate::traits::models::Model; -use crate::traits::planes::Plane; -use crate::traits::textures::Texture; -use crate::traits::tree::BspNode; -use crate::traits::vertices::{MeshVert, Vertex}; - -/// A parsed Quake 3 BSP File. -pub struct Q3BspFile<T: CoordSystem> { - 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, - _phantom: PhantomData<T>, -} - -impl Q3BspFile<Q3System> { - /// Parse `data` as a quake 3 bsp file. - pub fn parse_file(data: &[u8]) -> Result<Q3BspFile<Q3System>> { - 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, - _phantom: PhantomData, - }) - } -} - -impl<T: CoordSystem> Q3BspFile<T> { - pub fn swizzle_to<D: CoordSystem>(mut self) -> Q3BspFile<D> - where - Swizzler: SwizzleFromTo<T, D>, - { - for vertex in self.vertices.iter_mut() { - Swizzler::swizzle(&mut vertex.normal); - Swizzler::swizzle(&mut vertex.position); - } - - for model in self.models.iter_mut() { - Swizzler::swizzle(&mut model.mins); - Swizzler::swizzle(&mut model.maxs); - } - - for face in self.faces.iter_mut() { - Swizzler::swizzle(&mut face.normal); - } - - for plane in self.planes.iter_mut() { - Swizzler::swizzle(&mut plane.normal); - } - - // TODO: Possibly don't need to move? - Q3BspFile { - visdata: self.visdata, - textures: self.textures, - entities: self.entities, - planes: self.planes, - vertices: self.vertices, - meshverts: self.meshverts, - light_maps: self.light_maps, - light_vols: self.light_vols, - brushes: self.brushes, - effects: self.effects, - faces: self.faces, - tree_root: self.tree_root, - models: self.models, - _phantom: PhantomData, - } - } -} diff --git a/stockton-levels/src/q3/header.rs b/stockton-levels/src/q3/header.rs deleted file mode 100644 index 45478e0..0000000 --- a/stockton-levels/src/q3/header.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::types::{ParseError, Result}; -use std::convert::TryInto; - -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] - } -} diff --git a/stockton-levels/src/q3/light_maps.rs b/stockton-levels/src/q3/light_maps.rs deleted file mode 100644 index 377fea5..0000000 --- a/stockton-levels/src/q3/light_maps.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::traits::light_maps::*; -use crate::types::{ParseError, Result, Rgb}; - -/// 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, outer) in map.iter_mut().enumerate() { - for (y, inner) in outer.iter_mut().enumerate() { - let offset = (x * 128 * 3) + (y * 3); - *inner = Rgb::from_slice(&raw[offset..offset + 3]); - } - } - maps.push(LightMap { map }) - } - - Ok(maps.into_boxed_slice()) -} - -impl<T: CoordSystem> HasLightMaps for Q3BspFile<T> { - type LightMapsIter<'a> = std::slice::Iter<'a, LightMap>; - - fn lightmaps_iter(&self) -> Self::LightMapsIter<'_> { - self.light_maps.iter() - } - - fn get_lightmap(&self, index: u32) -> &LightMap { - &self.light_maps[index as usize] - } -} diff --git a/stockton-levels/src/q3/light_vols.rs b/stockton-levels/src/q3/light_vols.rs deleted file mode 100644 index 46f2557..0000000 --- a/stockton-levels/src/q3/light_vols.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::convert::TryInto; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::traits::light_vols::*; -use crate::types::{ParseError, Result, Rgb}; - -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<T: CoordSystem> HasLightVols for Q3BspFile<T> { - type LightVolsIter<'a> = std::slice::Iter<'a, LightVol>; - - fn lightvols_iter(&self) -> Self::LightVolsIter<'_> { - self.light_vols.iter() - } - - fn get_lightvol(&self, index: u32) -> &LightVol { - &self.light_vols[index as usize] - } -} diff --git a/stockton-levels/src/q3/mod.rs b/stockton-levels/src/q3/mod.rs deleted file mode 100644 index 44d5159..0000000 --- a/stockton-levels/src/q3/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Parsing data from Q3 and similar BSPs - -mod brushes; -mod effects; -mod entities; -mod faces; -pub mod file; -mod header; -mod light_maps; -mod light_vols; -mod models; -mod planes; -mod textures; -mod tree; -mod vertices; -mod visdata; - -pub use self::file::Q3BspFile; diff --git a/stockton-levels/src/q3/models.rs b/stockton-levels/src/q3/models.rs deleted file mode 100644 index a3de54a..0000000 --- a/stockton-levels/src/q3/models.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::{slice_to_u32, slice_to_vec3}; -use crate::traits::models::*; -use crate::types::{ParseError, Result}; - -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<T: CoordSystem> HasModels<T> for Q3BspFile<T> { - type ModelsIter<'a> = std::slice::Iter<'a, Model>; - - fn models_iter(&self) -> Self::ModelsIter<'_> { - self.models.iter() - } - - fn get_model(&self, index: u32) -> &Model { - &self.models[index as usize] - } -} diff --git a/stockton-levels/src/q3/planes.rs b/stockton-levels/src/q3/planes.rs deleted file mode 100644 index b4d3217..0000000 --- a/stockton-levels/src/q3/planes.rs +++ /dev/null @@ -1,40 +0,0 @@ -const PLANE_SIZE: usize = (4 * 3) + 4; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::{slice_to_f32, slice_to_vec3}; -use crate::traits::planes::*; -use crate::types::{ParseError, Result}; - -/// 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<T: CoordSystem> HasPlanes<T> for Q3BspFile<T> { - type PlanesIter<'a> = std::slice::Iter<'a, Plane>; - - fn planes_iter(&self) -> Self::PlanesIter<'_> { - self.planes.iter() - } - - fn get_plane(&self, idx: u32) -> &Plane { - &self.planes[idx as usize] - } -} diff --git a/stockton-levels/src/q3/textures.rs b/stockton-levels/src/q3/textures.rs deleted file mode 100644 index 98c2fe4..0000000 --- a/stockton-levels/src/q3/textures.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::str; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::slice_to_u32; -use crate::traits::textures::*; -use crate::types::{ParseError, Result}; - -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)? - .trim_matches('\0') - .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<T: CoordSystem> HasTextures for Q3BspFile<T> { - type TexturesIter<'a> = std::slice::Iter<'a, Texture>; - - fn textures_iter(&self) -> Self::TexturesIter<'_> { - self.textures.iter() - } - - fn get_texture(&self, idx: u32) -> Option<&Texture> { - if idx >= self.textures.len() as u32 { - None - } else { - Some(&self.textures[idx as usize]) - } - } -} - -#[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', 0x00, 0x00, 0x00, - 0x00, // name (padded to 64 bytes) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 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, "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', 0x00, 0x00, - 0x00, // name (padded to 64 bytes) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 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', 0x00, 0x00, - 0x00, // name (padded to 64 bytes) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 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', 0x00, 0x00, - 0x00, // name (padded to 64 bytes) - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 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, "TEST TEXTURE1"); - assert_eq!(lump[1].name, "TEST TEXTURE2"); - assert_eq!(lump[2].name, "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 deleted file mode 100644 index 05f1531..0000000 --- a/stockton-levels/src/q3/tree.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! Parses the BSP tree into a usable format - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::{slice_to_i32, slice_to_u32, slice_to_vec3i}; -use crate::traits::tree::*; -use crate::types::{ParseError, Result}; - -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); - } - - 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<T: CoordSystem> HasBspTree<T> for Q3BspFile<T> { - fn get_bsp_root(&self) -> &BspNode { - &self.tree_root - } -} diff --git a/stockton-levels/src/q3/vertices.rs b/stockton-levels/src/q3/vertices.rs deleted file mode 100644 index 77ff667..0000000 --- a/stockton-levels/src/q3/vertices.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::convert::TryInto; - -use super::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::{slice_to_u32, slice_to_vec3}; -use crate::traits::vertices::*; -use crate::types::{ParseError, Result, Rgba}; - -/// 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_u32(&data[n * 4..(n + 1) * 4])) - } - - Ok(meshverts.into_boxed_slice()) -} - -impl<T: CoordSystem> HasVertices<T> for Q3BspFile<T> { - type VerticesIter<'a> = std::slice::Iter<'a, Vertex>; - - fn vertices_iter(&self) -> Self::VerticesIter<'_> { - self.vertices.iter() - } - - fn get_vertex(&self, index: u32) -> &Vertex { - &self.vertices[index as usize] - } -} - -impl<T: CoordSystem> HasMeshVerts<T> for Q3BspFile<T> { - type MeshVertsIter<'a> = std::slice::Iter<'a, MeshVert>; - - fn meshverts_iter(&self) -> Self::MeshVertsIter<'_> { - self.meshverts.iter() - } - - fn get_meshvert<'a>(&self, index: u32) -> MeshVert { - self.meshverts[index as usize] - } -} diff --git a/stockton-levels/src/q3/visdata.rs b/stockton-levels/src/q3/visdata.rs deleted file mode 100644 index d3cc532..0000000 --- a/stockton-levels/src/q3/visdata.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Parses visdata from Q3 BSPs. - -use bitvec::prelude::*; -use std::vec::IntoIter; - -use super::file::Q3BspFile; -use crate::coords::CoordSystem; -use crate::helpers::slice_to_i32; -use crate::traits::visdata::*; -use crate::types::{ParseError, Result}; - -/// 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<T: CoordSystem> HasVisData for Q3BspFile<T> { - 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 deleted file mode 100644 index 911d068..0000000 --- a/stockton-levels/src/traits/brushes.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Parses the brushes & brushsides lumps from a bsp file - -use super::HasPlanes; -use crate::coords::CoordSystem; - -/// 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<S: CoordSystem>: HasPlanes<S> { - type BrushesIter<'a>: Iterator<Item = &'a Brush>; - - fn brushes_iter(&self) -> Self::BrushesIter<'_>; - fn get_brush(&self, index: u32) -> &Brush; -} diff --git a/stockton-levels/src/traits/effects.rs b/stockton-levels/src/traits/effects.rs deleted file mode 100644 index 75e696e..0000000 --- a/stockton-levels/src/traits/effects.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::HasBrushes; -use crate::coords::CoordSystem; - -/// 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<S: CoordSystem>: HasBrushes<S> { - type EffectsIter<'a>: Iterator<Item = &'a Effect>; - - fn effects_iter(&self) -> Self::EffectsIter<'_>; - fn get_effect(&self, index: u32) -> &Effect; -} diff --git a/stockton-levels/src/traits/entities.rs b/stockton-levels/src/traits/entities.rs deleted file mode 100644 index 330ef35..0000000 --- a/stockton-levels/src/traits/entities.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::collections::HashMap; -use std::iter::Iterator; - -#[derive(Debug, Clone, PartialEq)] -/// A game entity -pub struct Entity { - pub attributes: HashMap<String, String>, -} - -pub trait HasEntities { - type EntitiesIter<'a>: Iterator<Item = &'a Entity>; - - fn entities_iter(&self) -> Self::EntitiesIter<'_>; -} diff --git a/stockton-levels/src/traits/faces.rs b/stockton-levels/src/traits/faces.rs deleted file mode 100644 index 00addc4..0000000 --- a/stockton-levels/src/traits/faces.rs +++ /dev/null @@ -1,42 +0,0 @@ -use na::{Vector2, Vector3}; -use std::ops::Range; - -use super::{HasEffects, HasLightMaps, HasMeshVerts, HasTextures}; -use crate::coords::CoordSystem; - -#[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: u32, - pub effect_idx: Option<u32>, - pub lightmap_idx: Option<u32>, - pub vertices_idx: Range<u32>, - pub meshverts_idx: Range<u32>, - - pub map_start: Vector2<u32>, - pub map_size: Vector2<u32>, - pub map_origin: Vector3<f32>, - pub map_vecs: [Vector3<f32>; 2], - - pub normal: Vector3<f32>, - pub size: Vector2<u32>, -} - -pub trait HasFaces<S: CoordSystem>: - HasTextures + HasEffects<S> + HasLightMaps + HasMeshVerts<S> -{ - type FacesIter<'a>: Iterator<Item = &'a Face>; - - fn faces_iter(&self) -> Self::FacesIter<'_>; - fn faces_len(&self) -> u32; - fn get_face(&self, index: u32) -> &Face; -} diff --git a/stockton-levels/src/traits/light_maps.rs b/stockton-levels/src/traits/light_maps.rs deleted file mode 100644 index 0ee468a..0000000 --- a/stockton-levels/src/traits/light_maps.rs +++ /dev/null @@ -1,45 +0,0 @@ -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 { - type LightMapsIter<'a>: Iterator<Item = &'a LightMap>; - - fn lightmaps_iter(&self) -> Self::LightMapsIter<'_>; - fn get_lightmap(&self, index: u32) -> &LightMap; -} diff --git a/stockton-levels/src/traits/light_vols.rs b/stockton-levels/src/traits/light_vols.rs deleted file mode 100644 index 2014fad..0000000 --- a/stockton-levels/src/traits/light_vols.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::types::Rgb; - -#[derive(Debug, Clone, Copy)] -pub struct LightVol { - pub ambient: Rgb, - pub directional: Rgb, - pub dir: [u8; 2], -} - -pub trait HasLightVols { - type LightVolsIter<'a>: Iterator<Item = &'a LightVol>; - - fn lightvols_iter(&self) -> Self::LightVolsIter<'_>; - fn get_lightvol(&self, index: u32) -> &LightVol; -} diff --git a/stockton-levels/src/traits/mod.rs b/stockton-levels/src/traits/mod.rs deleted file mode 100644 index 84fa7eb..0000000 --- a/stockton-levels/src/traits/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Traits for parts of files that can exist - -pub mod brushes; -pub mod effects; -pub mod entities; -pub mod faces; -pub mod light_maps; -pub mod light_vols; -pub mod models; -pub mod planes; -pub mod textures; -pub mod tree; -pub mod vertices; -pub mod visdata; - -pub use self::brushes::HasBrushes; -pub use self::effects::HasEffects; -pub use self::entities::HasEntities; -pub use self::faces::HasFaces; -pub use self::light_maps::HasLightMaps; -pub use self::light_vols::HasLightVols; -pub use self::models::HasModels; -pub use self::planes::HasPlanes; -pub use self::textures::HasTextures; -pub use self::tree::HasBspTree; -pub use self::vertices::{HasMeshVerts, HasVertices}; -pub use self::visdata::HasVisData; diff --git a/stockton-levels/src/traits/models.rs b/stockton-levels/src/traits/models.rs deleted file mode 100644 index 58b5f74..0000000 --- a/stockton-levels/src/traits/models.rs +++ /dev/null @@ -1,20 +0,0 @@ -use na::Vector3; -use std::ops::Range; - -use super::{HasBrushes, HasFaces}; -use crate::coords::CoordSystem; - -#[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<S: CoordSystem>: HasFaces<S> + HasBrushes<S> { - type ModelsIter<'a>: Iterator<Item = &'a Model>; - - fn models_iter(&self) -> Self::ModelsIter<'_>; - fn get_model(&self, index: u32) -> &Model; -} diff --git a/stockton-levels/src/traits/planes.rs b/stockton-levels/src/traits/planes.rs deleted file mode 100644 index ed48c63..0000000 --- a/stockton-levels/src/traits/planes.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::coords::CoordSystem; -use na::Vector3; -use std::iter::Iterator; - -/// 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<S: CoordSystem> { - type PlanesIter<'a>: Iterator<Item = &'a Plane>; - - fn planes_iter(&self) -> Self::PlanesIter<'_>; - fn get_plane(&self, idx: u32) -> &Plane; -} diff --git a/stockton-levels/src/traits/textures.rs b/stockton-levels/src/traits/textures.rs deleted file mode 100644 index 75764b6..0000000 --- a/stockton-levels/src/traits/textures.rs +++ /dev/null @@ -1,147 +0,0 @@ -// 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 { - type TexturesIter<'a>: Iterator<Item = &'a Texture>; - - fn textures_iter(&self) -> Self::TexturesIter<'_>; - fn get_texture(&self, idx: u32) -> Option<&Texture>; -} diff --git a/stockton-levels/src/traits/tree.rs b/stockton-levels/src/traits/tree.rs deleted file mode 100644 index f22be2c..0000000 --- a/stockton-levels/src/traits/tree.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Parses the BSP tree into a usable format - -use super::{HasBrushes, HasFaces, HasVisData}; -use crate::coords::CoordSystem; -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<S: CoordSystem>: HasFaces<S> + HasBrushes<S> + HasVisData { - fn get_bsp_root(&self) -> &BspNode; -} diff --git a/stockton-levels/src/traits/vertices.rs b/stockton-levels/src/traits/vertices.rs deleted file mode 100644 index 11cd681..0000000 --- a/stockton-levels/src/traits/vertices.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::coords::CoordSystem; -use crate::helpers::slice_to_f32; -use crate::types::Rgba; -use na::Vector3; - -/// 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[8..12])], - v: [slice_to_f32(&bytes[4..8]), slice_to_f32(&bytes[12..16])], - } - } -} - -/// A vertex offset, used to describe generalised triangle meshes -pub type MeshVert = u32; - -pub trait HasVertices<S: CoordSystem> { - type VerticesIter<'a>: Iterator<Item = &'a Vertex>; - - fn vertices_iter(&self) -> Self::VerticesIter<'_>; - fn get_vertex(&self, index: u32) -> &Vertex; -} - -pub trait HasMeshVerts<S: CoordSystem>: HasVertices<S> { - type MeshVertsIter<'a>: Iterator<Item = &'a MeshVert>; - - fn meshverts_iter(&self) -> Self::MeshVertsIter<'_>; - fn get_meshvert(&self, index: u32) -> MeshVert; - - fn resolve_meshvert(&self, index: u32, base: u32) -> &Vertex { - self.get_vertex(self.get_meshvert(index) + base) - } -} diff --git a/stockton-levels/src/traits/visdata.rs b/stockton-levels/src/traits/visdata.rs deleted file mode 100644 index 7cd4d20..0000000 --- a/stockton-levels/src/traits/visdata.rs +++ /dev/null @@ -1,27 +0,0 @@ -// 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(&self, from: ClusterId) -> Self::VisibleIterator; - - /// Returns true if `dest` is visible from `from`. - fn cluster_visible_from(&self, from: ClusterId, dest: ClusterId) -> bool; -} diff --git a/stockton-levels/src/types.rs b/stockton-levels/src/types.rs index dd42a5e..dad824c 100644 --- a/stockton-levels/src/types.rs +++ b/stockton-levels/src/types.rs @@ -1,10 +1,10 @@ //! Various types used in parsed BSP files. +use serde::{Deserialize, Serialize}; use std::convert::TryInto; -use thiserror::Error; /// RGBA Colour (0-255) -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Rgba { pub r: u8, pub g: u8, @@ -32,7 +32,7 @@ impl Rgba { } /// RGB Colour (0-255) -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Rgb { pub r: u8, pub g: u8, @@ -65,16 +65,3 @@ impl Rgb { Rgb::from_bytes(slice.try_into().unwrap()) } } - -#[derive(Error, Debug)] -/// An error encountered while parsing. -pub enum ParseError { - #[error("Unsupported format")] - Unsupported, - - #[error("Invalid file")] - Invalid, -} - -/// Standard result type. -pub type Result<T> = std::result::Result<T, ParseError>; |