#![allow(clippy::indexing_slicing)]
use alloc::vec::Vec;
use core::fmt;
use crate::math::{fixed_div, Angle, Fixed, FRACBITS};
use crate::wad::Wad;
pub mod line_special {
pub const MANUAL_DOOR: i16 = 1;
pub const WALK_OPEN_DOOR: i16 = 2;
pub const WALK_CLOSE_DOOR: i16 = 3;
pub const WALK_RAISE_DOOR: i16 = 4;
pub const WALK_LIFT: i16 = 10;
pub const EXIT_NORMAL: i16 = 11;
pub const WALK_CLOSE30_OPEN: i16 = 16;
pub const W1_FLOOR_LOWER_LOWEST: i16 = 36;
pub const W1_FLOOR_LOWER_LOWEST_CHANGE: i16 = 37;
pub const W1_FLOOR_LOWER_LOWEST_NX: i16 = 38;
pub const WR_PLAT_DOWN_WAIT_UP: i16 = 88;
pub const BLUE_LOCKED_DOOR: i16 = 26;
pub const YELLOW_LOCKED_DOOR: i16 = 27;
pub const RED_LOCKED_DOOR: i16 = 28;
pub const MANUAL_OPEN_STAY: i16 = 31;
pub const BLUE_OPEN_STAY: i16 = 32;
pub const RED_OPEN_STAY: i16 = 33;
pub const YELLOW_OPEN_STAY: i16 = 34;
pub const WALK_EXIT: i16 = 52;
pub const EXIT_SECRET: i16 = 51;
pub const SWITCH_LIFT: i16 = 62;
pub const BLAZE_RAISE: i16 = 117;
pub const BLAZE_OPEN: i16 = 118;
pub const EXIT_TELEPORT: i16 = 124;
}
pub mod doomednum {
pub const PLAYER1_START: i16 = 1;
pub const PLAYER2_START: i16 = 2;
pub const PLAYER3_START: i16 = 3;
pub const PLAYER4_START: i16 = 4;
pub const DEATHMATCH_START: i16 = 11;
pub const HEALTH_BONUS: i32 = 2014;
pub const ARMOR_BONUS: i32 = 2015;
pub const STIMPACK: i32 = 2011;
pub const MEDIKIT: i32 = 2012;
pub const SOUL_SPHERE: i32 = 2013;
pub const GREEN_ARMOR: i32 = 2018;
pub const BLUE_ARMOR: i32 = 2019;
pub const CLIP: i32 = 2007;
pub const BOX_OF_AMMO: i32 = 2048;
pub const SHELLS: i32 = 2008;
pub const SHELL_BOX: i32 = 2049;
pub const ROCKET: i32 = 2010;
pub const ROCKET_BOX: i32 = 2046;
pub const CELL: i32 = 17;
pub const SHOTGUN: i32 = 2001;
pub const CHAINGUN: i32 = 2002;
pub const ROCKET_LAUNCHER: i32 = 2003;
pub const PLASMA_RIFLE: i32 = 2004;
pub const BFG: i32 = 2006;
pub const BACKPACK: i32 = 8;
pub const BLUE_CARD: i32 = 5;
pub const YELLOW_CARD: i32 = 6;
pub const RED_CARD: i32 = 13;
pub const BLUE_SKULL: i32 = 40;
pub const YELLOW_SKULL: i32 = 39;
pub const RED_SKULL: i32 = 38;
}
pub type VertexId = u16;
pub type SectorId = u16;
pub type SideId = u16;
pub type LineId = u16;
pub type SegId = u16;
pub type SubSectorId = u16;
pub type NodeId = u16;
pub const NF_SUBSECTOR: u16 = 0x8000;
pub const MAPBLOCKSHIFT: i32 = FRACBITS + 7; pub const MAPBLOCKSIZE: Fixed = 1 << MAPBLOCKSHIFT; pub const MAPBTOFRAC: i32 = MAPBLOCKSHIFT - FRACBITS;
#[derive(Debug, Clone, Copy)]
pub struct Vertex {
pub x: Fixed,
pub y: Fixed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlopeType {
Horizontal,
Vertical,
Positive,
Negative,
}
#[derive(Debug, Clone)]
pub struct Sector {
pub floor_height: Fixed,
pub ceiling_height: Fixed,
pub floor_pic: i16,
pub ceiling_pic: i16,
pub light_level: i16,
pub special: i16,
pub tag: i16,
pub sound_traversed: i32,
pub valid_count: i32,
pub line_count: usize,
pub first_line: usize,
pub floor_pic_name: [u8; 8],
pub ceiling_pic_name: [u8; 8],
}
#[derive(Debug, Clone)]
pub struct Side {
pub textureoffset: Fixed,
pub rowoffset: Fixed,
pub top_texture: i16,
pub bottom_texture: i16,
pub mid_texture: i16,
pub sector: SectorId,
pub top_texture_name: [u8; 8],
pub bottom_texture_name: [u8; 8],
pub mid_texture_name: [u8; 8],
}
#[derive(Debug, Clone)]
pub struct Line {
pub v1: VertexId,
pub v2: VertexId,
pub dx: Fixed,
pub dy: Fixed,
pub flags: i16,
pub special: i16,
pub tag: i16,
pub sidenum: [Option<SideId>; 2],
pub bbox: BBox,
pub slope_type: SlopeType,
pub front_sector: SectorId,
pub back_sector: Option<SectorId>,
pub valid_count: i32,
}
pub type BBox = [Fixed; 4];
pub const BOXTOP: usize = 0;
pub const BOXBOTTOM: usize = 1;
pub const BOXLEFT: usize = 2;
pub const BOXRIGHT: usize = 3;
#[derive(Debug, Clone)]
pub struct Seg {
pub v1: VertexId,
pub v2: VertexId,
pub offset: Fixed,
pub angle: Angle,
pub side: SideId,
pub line: LineId,
pub front_sector: SectorId,
pub back_sector: Option<SectorId>,
}
#[derive(Debug, Clone)]
pub struct SubSector {
pub sector: SectorId,
pub num_lines: u16,
pub first_line: SegId,
}
#[derive(Debug, Clone)]
pub struct Node {
pub x: Fixed,
pub y: Fixed,
pub dx: Fixed,
pub dy: Fixed,
pub bbox: [BBox; 2],
pub children: [u16; 2],
}
#[derive(Debug, Clone, Copy)]
pub struct MapThing {
pub x: i16,
pub y: i16,
pub angle: i16,
pub type_num: i16,
pub options: i16,
}
#[derive(Debug, Clone)]
pub struct BlockMap {
pub origin_x: Fixed,
pub origin_y: Fixed,
pub width: usize,
pub height: usize,
pub offsets: Vec<u32>,
pub lists: Vec<u16>,
}
#[derive(Debug, Clone)]
pub struct MapData {
pub vertexes: Vec<Vertex>,
pub sectors: Vec<Sector>,
pub sides: Vec<Side>,
pub lines: Vec<Line>,
pub segs: Vec<Seg>,
pub subsectors: Vec<SubSector>,
pub nodes: Vec<Node>,
pub blockmap: BlockMap,
pub reject: Vec<u8>,
pub things: Vec<MapThing>,
}
#[derive(Debug)]
pub enum MapError {
MissingLump(&'static str),
InvalidSize { lump: &'static str, size: usize, expected_multiple: usize },
InvalidIndex { lump: &'static str, field: &'static str, value: i16, max: usize },
}
impl fmt::Display for MapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingLump(name) => write!(f, "missing map lump: {name}"),
Self::InvalidSize { lump, size, expected_multiple } => {
write!(f, "{lump} lump size {size} not a multiple of {expected_multiple}")
}
Self::InvalidIndex { lump, field, value, max } => {
write!(f, "{lump}.{field} index {value} out of range (max {max})")
}
}
}
}
impl core::error::Error for MapError {}
fn read_i16_le(data: &[u8], off: usize) -> i16 {
let Some(slice) = data.get(off..off + 2) else { return 0 };
let mut bytes = [0u8; 2];
bytes.copy_from_slice(slice);
i16::from_le_bytes(bytes)
}
fn read_u16_le(data: &[u8], off: usize) -> u16 {
let Some(slice) = data.get(off..off + 2) else { return 0 };
let mut bytes = [0u8; 2];
bytes.copy_from_slice(slice);
u16::from_le_bytes(bytes)
}
fn lump_count(data: &[u8], entry_size: usize, lump_name: &'static str) -> Result<usize, MapError> {
if !data.len().is_multiple_of(entry_size) {
return Err(MapError::InvalidSize {
lump: lump_name,
size: data.len(),
expected_multiple: entry_size,
});
}
Ok(data.len() / entry_size)
}
fn read_texture_name(data: &[u8], off: usize) -> i16 {
if data[off] == b'-' || data[off] == 0 {
-1
} else {
0 }
}
fn read_flat_name(data: &[u8], off: usize) -> i16 {
if data[off] == b'-' || data[off] == 0 {
-1
} else {
0 }
}
fn slope_type(dx: Fixed, dy: Fixed) -> SlopeType {
if dx == 0 {
SlopeType::Vertical
} else if dy == 0 {
SlopeType::Horizontal
} else if fixed_div(dy, dx) > 0 {
SlopeType::Positive
} else {
SlopeType::Negative
}
}
impl MapData {
pub fn load(wad: &Wad, map_name: &str) -> Result<Self, MapError> {
let marker = wad
.find_lump(map_name)
.ok_or(MapError::MissingLump("map marker"))?;
let get_lump = |name: &'static str| -> Result<&[u8], MapError> {
let idx = wad
.find_lump_after(name, marker + 1)
.ok_or(MapError::MissingLump(name))?;
Ok(wad.lump_data(idx))
};
let vert_data = get_lump("VERTEXES")?;
let sector_data = get_lump("SECTORS")?;
let side_data = get_lump("SIDEDEFS")?;
let line_data = get_lump("LINEDEFS")?;
let seg_data = get_lump("SEGS")?;
let ssector_data = get_lump("SSECTORS")?;
let node_data = get_lump("NODES")?;
let thing_data = get_lump("THINGS")?;
let blockmap_data = get_lump("BLOCKMAP")?;
let reject_data = get_lump("REJECT")?;
let vertexes = Self::load_vertexes(vert_data)?;
let sectors = Self::load_sectors(sector_data)?;
let sides = Self::load_sidedefs(side_data, sectors.len())?;
let lines = Self::load_linedefs(line_data, &vertexes, &sides)?;
let segs = Self::load_segs(seg_data, &vertexes, &lines, &sides)?;
let subsectors = Self::load_subsectors(ssector_data, &segs, §ors)?;
let nodes = Self::load_nodes(node_data)?;
let things = Self::load_things(thing_data)?;
let blockmap = Self::load_blockmap(blockmap_data)?;
Ok(Self {
vertexes,
sectors,
sides,
lines,
segs,
subsectors,
nodes,
blockmap,
reject: reject_data.to_vec(),
things,
})
}
fn load_vertexes(data: &[u8]) -> Result<Vec<Vertex>, MapError> {
let count = lump_count(data, 4, "VERTEXES")?;
let mut verts = Vec::with_capacity(count);
for i in 0..count {
let off = i * 4;
verts.push(Vertex {
x: (read_i16_le(data, off) as Fixed) << FRACBITS,
y: (read_i16_le(data, off + 2) as Fixed) << FRACBITS,
});
}
Ok(verts)
}
fn load_sectors(data: &[u8]) -> Result<Vec<Sector>, MapError> {
let count = lump_count(data, 26, "SECTORS")?;
let mut sectors = Vec::with_capacity(count);
for i in 0..count {
let off = i * 26;
let mut floor_name = [0u8; 8];
floor_name.copy_from_slice(&data[off + 4..off + 12]);
let mut ceil_name = [0u8; 8];
ceil_name.copy_from_slice(&data[off + 12..off + 20]);
sectors.push(Sector {
floor_height: (read_i16_le(data, off) as Fixed) << FRACBITS,
ceiling_height: (read_i16_le(data, off + 2) as Fixed) << FRACBITS,
floor_pic: read_flat_name(data, off + 4),
ceiling_pic: read_flat_name(data, off + 12),
light_level: read_i16_le(data, off + 20),
special: read_i16_le(data, off + 22),
tag: read_i16_le(data, off + 24),
sound_traversed: 0,
valid_count: 0,
line_count: 0,
first_line: 0,
floor_pic_name: floor_name,
ceiling_pic_name: ceil_name,
});
}
Ok(sectors)
}
fn load_sidedefs(data: &[u8], num_sectors: usize) -> Result<Vec<Side>, MapError> {
let count = lump_count(data, 30, "SIDEDEFS")?;
let mut sides = Vec::with_capacity(count);
for i in 0..count {
let off = i * 30;
let sector_idx = read_i16_le(data, off + 28);
if sector_idx < 0 || sector_idx as usize >= num_sectors {
return Err(MapError::InvalidIndex {
lump: "SIDEDEFS",
field: "sector",
value: sector_idx,
max: num_sectors,
});
}
let mut top_name = [0u8; 8];
top_name.copy_from_slice(&data[off + 4..off + 12]);
let mut bottom_name = [0u8; 8];
bottom_name.copy_from_slice(&data[off + 12..off + 20]);
let mut mid_name = [0u8; 8];
mid_name.copy_from_slice(&data[off + 20..off + 28]);
sides.push(Side {
textureoffset: (read_i16_le(data, off) as Fixed) << FRACBITS,
rowoffset: (read_i16_le(data, off + 2) as Fixed) << FRACBITS,
top_texture: read_texture_name(data, off + 4),
bottom_texture: read_texture_name(data, off + 12),
mid_texture: read_texture_name(data, off + 20),
sector: sector_idx as SectorId,
top_texture_name: top_name,
bottom_texture_name: bottom_name,
mid_texture_name: mid_name,
});
}
Ok(sides)
}
fn load_linedefs(
data: &[u8],
vertexes: &[Vertex],
sides: &[Side],
) -> Result<Vec<Line>, MapError> {
let count = lump_count(data, 14, "LINEDEFS")?;
let mut lines = Vec::with_capacity(count);
for i in 0..count {
let off = i * 14;
let v1_idx = read_u16_le(data, off);
let v2_idx = read_u16_le(data, off + 2);
let flags = read_i16_le(data, off + 4);
let special = read_i16_le(data, off + 6);
let tag = read_i16_le(data, off + 8);
let s1 = read_i16_le(data, off + 10);
let s2 = read_i16_le(data, off + 12);
let v1 = &vertexes[v1_idx as usize];
let v2 = &vertexes[v2_idx as usize];
let dx = v2.x - v1.x;
let dy = v2.y - v1.y;
let side0 = if s1 >= 0 && (s1 as usize) < sides.len() {
Some(s1 as SideId)
} else {
None
};
let side1 = if s2 >= 0 && (s2 as usize) < sides.len() {
Some(s2 as SideId)
} else {
None
};
let front_sector = side0
.map(|s| sides[s as usize].sector)
.unwrap_or(0);
let back_sector = side1.map(|s| sides[s as usize].sector);
let bbox = [
v1.y.max(v2.y), v1.y.min(v2.y), v1.x.min(v2.x), v1.x.max(v2.x), ];
lines.push(Line {
v1: v1_idx,
v2: v2_idx,
dx,
dy,
flags,
special,
tag,
sidenum: [side0, side1],
bbox,
slope_type: slope_type(dx, dy),
front_sector,
back_sector,
valid_count: 0,
});
}
Ok(lines)
}
fn load_segs(
data: &[u8],
vertexes: &[Vertex],
lines: &[Line],
sides: &[Side],
) -> Result<Vec<Seg>, MapError> {
let count = lump_count(data, 12, "SEGS")?;
let mut segs = Vec::with_capacity(count);
for i in 0..count {
let off = i * 12;
let v1 = read_u16_le(data, off);
let v2 = read_u16_le(data, off + 2);
let angle_raw = read_i16_le(data, off + 4);
let linedef_idx = read_u16_le(data, off + 6);
let side = read_i16_le(data, off + 8);
let offset = read_i16_le(data, off + 10);
let line = &lines[linedef_idx as usize];
let side_idx = if side == 0 {
line.sidenum[0].unwrap_or(0)
} else {
line.sidenum[1].unwrap_or(0)
};
let front_sector = sides[side_idx as usize].sector;
let back_sector = if side == 0 {
line.sidenum[1].map(|s| sides[s as usize].sector)
} else {
line.sidenum[0].map(|s| sides[s as usize].sector)
};
let vx1 = &vertexes[v1 as usize];
let vx2 = &vertexes[v2 as usize];
let _ = angle_raw; let angle = point_to_angle(vx2.x - vx1.x, vx2.y - vx1.y);
segs.push(Seg {
v1,
v2,
offset: (offset as Fixed) << FRACBITS,
angle,
side: side_idx,
line: linedef_idx,
front_sector,
back_sector,
});
}
Ok(segs)
}
fn load_subsectors(
data: &[u8],
segs: &[Seg],
sectors: &[Sector],
) -> Result<Vec<SubSector>, MapError> {
let count = lump_count(data, 4, "SSECTORS")?;
let _ = sectors; let mut subsectors = Vec::with_capacity(count);
for i in 0..count {
let off = i * 4;
let num_segs = read_u16_le(data, off);
let first_seg = read_u16_le(data, off + 2);
let sector = segs[first_seg as usize].front_sector;
subsectors.push(SubSector {
sector,
num_lines: num_segs,
first_line: first_seg,
});
}
Ok(subsectors)
}
fn load_nodes(data: &[u8]) -> Result<Vec<Node>, MapError> {
let count = lump_count(data, 28, "NODES")?;
let mut nodes = Vec::with_capacity(count);
for i in 0..count {
let off = i * 28;
let x = (read_i16_le(data, off) as Fixed) << FRACBITS;
let y = (read_i16_le(data, off + 2) as Fixed) << FRACBITS;
let dx = (read_i16_le(data, off + 4) as Fixed) << FRACBITS;
let dy = (read_i16_le(data, off + 6) as Fixed) << FRACBITS;
let mut bbox = [[0i32; 4]; 2];
for (child, child_bbox) in bbox.iter_mut().enumerate() {
for (coord, val) in child_bbox.iter_mut().enumerate() {
let idx = off + 8 + child * 8 + coord * 2;
*val = (read_i16_le(data, idx) as Fixed) << FRACBITS;
}
}
let children = [
read_u16_le(data, off + 24),
read_u16_le(data, off + 26),
];
nodes.push(Node { x, y, dx, dy, bbox, children });
}
Ok(nodes)
}
fn load_things(data: &[u8]) -> Result<Vec<MapThing>, MapError> {
let count = lump_count(data, 10, "THINGS")?;
let mut things = Vec::with_capacity(count);
for i in 0..count {
let off = i * 10;
things.push(MapThing {
x: read_i16_le(data, off),
y: read_i16_le(data, off + 2),
angle: read_i16_le(data, off + 4),
type_num: read_i16_le(data, off + 6),
options: read_i16_le(data, off + 8),
});
}
Ok(things)
}
fn load_blockmap(data: &[u8]) -> Result<BlockMap, MapError> {
if data.len() < 8 {
return Err(MapError::InvalidSize {
lump: "BLOCKMAP",
size: data.len(),
expected_multiple: 8,
});
}
let origin_x = (read_i16_le(data, 0) as Fixed) << FRACBITS;
let origin_y = (read_i16_le(data, 2) as Fixed) << FRACBITS;
let width = read_u16_le(data, 4) as usize;
let height = read_u16_le(data, 6) as usize;
let num_blocks = width * height;
let mut offsets = Vec::with_capacity(num_blocks);
for i in 0..num_blocks {
let off = 8 + i * 2;
if off + 2 > data.len() {
break;
}
offsets.push(read_u16_le(data, off) as u32);
}
let total_u16 = data.len() / 2;
let mut lists = Vec::with_capacity(total_u16);
for i in 0..total_u16 {
lists.push(read_u16_le(data, i * 2));
}
Ok(BlockMap {
origin_x,
origin_y,
width,
height,
offsets,
lists,
})
}
pub fn root_node(&self) -> u16 {
debug_assert!(!self.nodes.is_empty(), "BSP tree has no nodes");
(self.nodes.len() - 1) as u16
}
pub fn resolve_textures(&mut self, textures: &crate::texture::TextureData) {
for side in &mut self.sides {
side.top_texture = textures.texture_num_for_name(&side.top_texture_name);
side.bottom_texture = textures.texture_num_for_name(&side.bottom_texture_name);
side.mid_texture = textures.texture_num_for_name(&side.mid_texture_name);
}
for sector in &mut self.sectors {
sector.floor_pic = textures.flat_num_for_name(§or.floor_pic_name);
sector.ceiling_pic = textures.flat_num_for_name(§or.ceiling_pic_name);
}
}
}
pub fn point_to_angle(dx: Fixed, dy: Fixed) -> Angle {
use crate::math::{ANG90, ANG180, ANG270};
use crate::tables::TANTOANGLE;
if dx == 0 && dy == 0 {
return 0;
}
if dx >= 0 {
if dy >= 0 {
if dx > dy {
TANTOANGLE[slope_div(dy as u32, dx as u32)]
} else {
ANG90 - 1 - TANTOANGLE[slope_div(dx as u32, dy as u32)]
}
} else {
let ay = (-dy) as u32;
if dx as u32 > ay {
0u32.wrapping_sub(TANTOANGLE[slope_div(ay, dx as u32)])
} else {
ANG270 + TANTOANGLE[slope_div(dx as u32, ay)]
}
}
} else {
let ax = (-dx) as u32;
if dy >= 0 {
if ax > dy as u32 {
ANG180 - 1 - TANTOANGLE[slope_div(dy as u32, ax)]
} else {
ANG90 + TANTOANGLE[slope_div(ax, dy as u32)]
}
} else {
let ay = (-dy) as u32;
if ax > ay {
ANG180 + TANTOANGLE[slope_div(ay, ax)]
} else {
ANG270 - 1 - TANTOANGLE[slope_div(ax, ay)]
}
}
}
}
fn slope_div(num: u32, den: u32) -> usize {
const SLOPERANGE: u32 = 2048;
if den < 512 {
return SLOPERANGE as usize;
}
let ans = (num << 3) / (den >> 8);
if ans <= SLOPERANGE {
ans as usize
} else {
SLOPERANGE as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slope_type_classification() {
assert_eq!(slope_type(0, 100), SlopeType::Vertical);
assert_eq!(slope_type(100, 0), SlopeType::Horizontal);
assert_eq!(slope_type(100, 100), SlopeType::Positive);
assert_eq!(slope_type(100, -100), SlopeType::Negative);
assert_eq!(slope_type(-100, 100), SlopeType::Negative);
assert_eq!(slope_type(-100, -100), SlopeType::Positive);
}
}