#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bitflags::bitflags;
use glam::{Vec2, Vec3, dvec2};
use qbsp_macros::{BspValue, BspVariableValue};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
BspData, BspParseError, BspParseResultDoingJobExt, BspResult, QUAKE_PALETTE,
data::util::{FixedStr, NoField},
reader::{BspByteReader, BspParseContext, BspValue, BspVariableValue},
};
pub type TextureName = FixedStr<32>;
pub type EmbeddedTextureName = FixedStr<16>;
impl BspData {
pub fn get_texture_name<'a>(&'a self, tex_info: &'a BspTexInfo) -> Option<TextureName> {
if let Some(extra_info) = &tex_info.extra_info.0 {
Some(extra_info.name)
} else if let Some(texture_idx) = tex_info.texture_idx.0 {
Some(self.textures.get(texture_idx as usize).and_then(Option::as_ref)?.header.name.extend())
} else {
None
}
}
}
#[repr(C)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Palette {
#[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))]
pub colors: [[u8; 3]; 256],
}
impl BspValue for Palette {
fn bsp_parse(reader: &mut BspByteReader) -> BspResult<Self> {
let num_colors = reader.read::<i16>()?;
if num_colors != 256 {
return Err(BspParseError::InvalidPaletteLength(num_colors as usize));
}
let colors = reader.read_bytes(num_colors as usize * 3)?;
Palette::parse(colors)
}
fn bsp_struct_size(ctx: &BspParseContext) -> usize {
i16::bsp_struct_size(ctx) + 3 * 256
}
}
impl Default for Palette {
fn default() -> Self {
QUAKE_PALETTE.clone()
}
}
impl Palette {
pub fn parse(data: &[u8]) -> BspResult<Self> {
let (pixels, rest) = data.as_chunks::<3>();
if !rest.is_empty() {
return Err(BspParseError::InvalidPaletteLength(data.len()));
}
Ok(Self {
colors: pixels.try_into().map_err(|_| BspParseError::InvalidPaletteLength(data.len()))?,
})
}
}
#[derive(BspVariableValue, Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[bsp29(u32)]
#[bsp2(u32)]
#[bsp30(u32)]
#[bsp38(NoField)]
#[qbism(NoField)]
pub struct TextureIdxField(pub Option<u32>);
#[derive(BspVariableValue, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[bsp29(NoField)]
#[bsp2(NoField)]
#[bsp30(NoField)]
#[bsp38(BspTexQ2Info)]
#[qbism(BspTexQ2Info)]
pub struct Q2InfoField(pub Option<BspTexQ2Info>);
#[derive(BspValue, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspTexInfo {
pub projection: PlanarTextureProjection,
pub texture_idx: TextureIdxField,
pub flags: BspTexInfoFlags,
pub extra_info: Q2InfoField,
}
impl BspTexQ2Info {
pub const UNIT_TEXTURE_BRIGHTNESS: u32 = 255;
pub fn brightness(&self) -> f64 {
self.value as f64 / Self::UNIT_TEXTURE_BRIGHTNESS as f64
}
pub fn diffuse_scale(&self) -> f64 {
self.brightness().min(1.)
}
pub fn emissive_scale(&self) -> Option<f64> {
self.value
.checked_sub(Self::UNIT_TEXTURE_BRIGHTNESS + 1)
.map(|brightness| (brightness + 1) as f64 / Self::UNIT_TEXTURE_BRIGHTNESS as f64)
}
}
impl From<BspTexFlags> for BspTexInfoFlags {
fn from(value: BspTexFlags) -> Self {
Self {
texture_flags: Some(value),
surface_flags: BspSurfaceFlags::empty(),
}
}
}
impl From<BspSurfaceFlags> for BspTexInfoFlags {
fn from(value: BspSurfaceFlags) -> Self {
Self {
texture_flags: None,
surface_flags: value,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspTexInfoFlags {
pub texture_flags: Option<BspTexFlags>,
pub surface_flags: BspSurfaceFlags,
}
impl BspVariableValue for BspTexInfoFlags {
type Bsp29 = BspTexFlags;
type Bsp2 = BspTexFlags;
type Bsp30 = BspSurfaceFlags;
type Bsp38 = BspSurfaceFlags;
type Qbism = BspSurfaceFlags;
}
#[derive(BspValue, Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u32)]
pub enum BspTexFlags {
#[default]
Normal = 0,
Special = 1,
Missing = 2,
}
bitflags! {
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
#[cfg_attr(feature = "bevy_reflect", reflect(opaque))]
pub struct BspSurfaceFlags: u32 {
const NO_DAMAGE = 0b0000_0000_0000_0000_0001;
const SLICK = 0b0000_0000_0000_0000_0010;
const SKY = 0b0000_0000_0000_0000_0100;
const WARP = 0b0000_0000_0000_0000_1000;
const NO_IMPACT = 0b0000_0000_0000_0001_0000;
const NO_MARKS = 0b0000_0000_0000_0010_0000;
const FLESH = 0b0000_0000_0000_0100_0000;
const NO_DRAW = 0b0000_0000_0000_1000_0000;
const HINT = 0b0000_0000_0001_0000_0000;
const SKIP = 0b0000_0000_0010_0000_0000;
const NO_LIGHTMAP = 0b0000_0000_0100_0000_0000;
const POINT_LIGHT = 0b0000_0000_1000_0000_0000;
const METAL_STEPS = 0b0000_0001_0000_0000_0000;
const NO_STEPS = 0b0000_0010_0000_0000_0000;
const NON_SOLID = 0b0000_0100_0000_0000_0000;
const LIGHT_FILTER = 0b0000_1000_0000_0000_0000;
const ALPHA_SHADOW = 0b0001_0000_0000_0000_0000;
const NO_DLIGHT = 0b0010_0000_0000_0000_0000;
const UNUSED1 = 0b0100_0000_0000_0000_0000;
const UNUSED2 = 0b1000_0000_0000_0000_0000;
}
}
impl BspValue for BspSurfaceFlags {
fn bsp_parse(reader: &mut BspByteReader) -> BspResult<Self> {
u32::bsp_parse(reader).map(BspSurfaceFlags::from_bits_truncate)
}
fn bsp_struct_size(ctx: &BspParseContext) -> usize {
u32::bsp_struct_size(ctx)
}
}
#[derive(BspValue, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspTexQ2Info {
pub value: u32,
pub name: FixedStr<32>,
pub next: i32,
}
#[derive(BspValue, Debug, Clone, Copy)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PlanarTextureProjection {
pub u_axis: Vec3,
pub u_offset: f32,
pub v_axis: Vec3,
pub v_offset: f32,
}
impl PlanarTextureProjection {
pub fn project(&self, point: Vec3) -> Vec2 {
dvec2(
point.as_dvec3().dot(self.u_axis.as_dvec3()) + self.u_offset as f64,
point.as_dvec3().dot(self.v_axis.as_dvec3()) + self.v_offset as f64,
)
.as_vec2()
}
}
#[derive(BspVariableValue, Default, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[bsp29(NoField)]
#[bsp2(NoField)]
#[bsp30(Palette)]
#[bsp38(NoField)]
#[qbism(NoField)]
pub struct Wad3Palette(pub Option<Palette>);
#[derive(Default, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspTextureData {
pub full: Option<Vec<u8>>,
pub half: Option<Vec<u8>>,
pub quarter: Option<Vec<u8>>,
pub eighth: Option<Vec<u8>>,
pub palette: Wad3Palette,
}
impl std::fmt::Debug for BspTextureData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BspTextureData")
.field("full", &self.full.as_ref().map(|_| ..))
.field("half", &self.half.as_ref().map(|_| ..))
.field("quarter", &self.quarter.as_ref().map(|_| ..))
.field("eighth", &self.eighth.as_ref().map(|_| ..))
.finish()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspMipTexture {
pub header: BspTextureHeader,
pub data: BspTextureData,
}
impl BspValue for BspMipTexture {
fn bsp_parse(reader: &mut BspByteReader) -> BspResult<Self> {
let reader_start = reader.pos();
let header: BspTextureHeader = reader.read()?;
if header.width == 0 || header.height == 0 {
return Ok(Self {
header,
data: BspTextureData::default(),
});
}
macro_rules! read_data {
($offset:ident, $res:literal $(, $($res_operator:tt)+)?) => {{
if header.$offset == 0 {
None
} else {
*reader = reader.with_pos(reader_start + header.$offset as usize);
Some(
reader
.read_bytes((header.width as usize $($($res_operator)+)?) * (header.height as usize $($($res_operator)+)?))
.job(|| format!(concat!("Reading texture (", $res, "res) with header {:#?}"), header))?
.to_vec(),
)
}
}};
}
if [header.offset_full, header.offset_half, header.offset_quarter, header.offset_eighth]
.into_iter()
.all(|o| o == 0)
{
Ok(Self {
data: Default::default(),
header,
})
} else {
Ok(Self {
data: BspTextureData {
full: read_data!(offset_full, "full"),
half: read_data!(offset_half, "half", / 2),
quarter: read_data!(offset_quarter, "quarter", / 4),
eighth: read_data!(offset_eighth, "eighth", / 8),
palette: reader.read()?,
},
header,
})
}
}
fn bsp_struct_size(ctx: &BspParseContext) -> usize {
BspTextureHeader::bsp_struct_size(ctx)
}
}
#[derive(BspValue, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BspTextureHeader {
pub name: EmbeddedTextureName,
pub width: u32,
pub height: u32,
pub offset_full: u32,
#[allow(unused)]
pub offset_half: u32,
#[allow(unused)]
pub offset_quarter: u32,
#[allow(unused)]
pub offset_eighth: u32,
}