aboutsummaryrefslogtreecommitdiff
path: root/stockton-levels
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:20 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:20 +0100
commitd076d3a6fd484e298915cd85609ba9706abacc87 (patch)
tree0d74395ff54e56fd54cab35ec0f27254e8306822 /stockton-levels
parent5dc6c64394d1e0a09c882b88ecb2b8f04f9e5b22 (diff)
refactor(all): move stockton-bsp to this repo and start using traits
Diffstat (limited to 'stockton-levels')
-rw-r--r--stockton-levels/Cargo.toml12
-rw-r--r--stockton-levels/src/helpers.rs71
-rw-r--r--stockton-levels/src/lib.rs25
-rw-r--r--stockton-levels/src/q3/brushes.rs117
-rw-r--r--stockton-levels/src/q3/effects.rs63
-rw-r--r--stockton-levels/src/q3/entities.rs108
-rw-r--r--stockton-levels/src/q3/faces.rs163
-rw-r--r--stockton-levels/src/q3/file.rs100
-rw-r--r--stockton-levels/src/q3/header.rs83
-rw-r--r--stockton-levels/src/q3/light_maps.rs59
-rw-r--r--stockton-levels/src/q3/light_vols.rs56
-rw-r--r--stockton-levels/src/q3/mod.rs33
-rw-r--r--stockton-levels/src/q3/models.rs86
-rw-r--r--stockton-levels/src/q3/planes.rs54
-rw-r--r--stockton-levels/src/q3/textures.rs147
-rw-r--r--stockton-levels/src/q3/tree.rs168
-rw-r--r--stockton-levels/src/q3/vertices.rs89
-rw-r--r--stockton-levels/src/q3/visdata.rs68
-rw-r--r--stockton-levels/src/traits/brushes.rs41
-rw-r--r--stockton-levels/src/traits/effects.rs35
-rw-r--r--stockton-levels/src/traits/entities.rs29
-rw-r--r--stockton-levels/src/traits/faces.rs54
-rw-r--r--stockton-levels/src/traits/light_maps.rs62
-rw-r--r--stockton-levels/src/traits/light_vols.rs34
-rw-r--r--stockton-levels/src/traits/mod.rs41
-rw-r--r--stockton-levels/src/traits/models.rs34
-rw-r--r--stockton-levels/src/traits/planes.rs42
-rw-r--r--stockton-levels/src/traits/textures.rs149
-rw-r--r--stockton-levels/src/traits/tree.rs50
-rw-r--r--stockton-levels/src/traits/vertices.rs70
-rw-r--r--stockton-levels/src/traits/visdata.rs29
-rw-r--r--stockton-levels/src/types.rs93
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