3 Commits

Author SHA1 Message Date
Jim
313fb3c577 WIP: Implementing the r_<FOO> files 2026-05-31 20:17:25 +01:00
Jim
dad59e261d RD-6: Implemeneted M_Init 2026-05-28 20:41:59 +01:00
Jim
93a3c8548e RD-5: Implemented Rust equivalent zone allocator 2026-05-27 21:14:43 +01:00
13 changed files with 3063 additions and 33 deletions

View File

@@ -1,7 +1,12 @@
# RustyDoom
A Rust version of the original DOOM from the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
A Rust version of the original DOOM base on the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
This is more of a learning exercise than something good!
This is more of a learning exercise than something good! For example, the original `z_zone.c` and `z_zone.h` will be more "Rust-like" compared to the original version.
This repository will only contain the SHAREWARE verison of the original DOOM1, but will be able to run both IWAD and PWADS.
This repository will be done in two parts before a "release"
1. An initial port keeping as close to the original C code as possible, which some minor modifications where it is clear on how to convert the code to Rust
2. Rust-ify any (hopefully all) parts of the code, that is removing `unsafe` and other such things. This will also include breaking down some of the `uber` sized funtions of the original source into more managable bits

View File

@@ -8,10 +8,13 @@ use crate::m_argv;
use crate::m_argv::M_CheckParm;
use crate::m_argv::M_GetOptionalArgumentValueByArgument;
use crate::m_argv::PARM_NOT_FOUND;
use crate::m_menu::M_Init;
use crate::m_misc::M_LoadDefaults;
use crate::renderer::R_Init;
use crate::v_video::V_Init;
use crate::w_wad::{W_CheckNumForName, W_InitMultipleFiles};
use crate::z_zone::Z_Init;
use crate::doomdef::{
VERSION,
@@ -408,11 +411,9 @@ pub fn D_DoomMain() {
println!("M_LoadDefaults: Load system defaults.");
M_LoadDefaults();
/* TODO: Implement subsytem inits
println!("Z_init: Init zone memory allocation daemon.");
Z_Init();
*/
println!("W_Init: Init WADfiles");
W_InitMultipleFiles(DOOMGLOBALS::with_ref(|g| g.wadfiles.clone())); // we are loading so how cares about a borrow
@@ -467,14 +468,15 @@ pub fn D_DoomMain() {
}
}
// TODO: Implement other inits
/*
println!("M_Init: Init miscellaneous info.");
M_Init();
println!("R_Init: Init DOOM refresh daemon - ")
println!("R_Init: Init DOOM refresh daemon - ");
R_Init();
// TODO: Implement other inits
/*
println!("P_Init: Init Playloop state.");
P_Init();

View File

@@ -1,10 +1,10 @@
const MAXCHAR: i8 = i8::MAX;
const MAXSHORT: i16 = i16::MAX;
const MAXINT: i32 = i32::MAX;
const MAXLONG: i32 = i32::MAX;
pub const MAXCHAR: i8 = i8::MAX;
pub const MAXSHORT: i16 = i16::MAX;
pub const MAXINT: i32 = i32::MAX;
pub const MAXLONG: i32 = i32::MAX;
// Minimum values
const MINCHAR: i8 = i8::MIN;
const MINSHORT: i16 = i16::MIN;
const MININT: i32 = i32::MIN;
const MINLONG: i32 = i32::MIN;
pub const MINCHAR: i8 = i8::MIN;
pub const MINSHORT: i16 = i16::MIN;
pub const MININT: i32 = i32::MIN;
pub const MINLONG: i32 = i32::MIN;

323
src/m_menu/mod.rs Normal file
View File

@@ -0,0 +1,323 @@
// Menu Widget stuff i.e episode selection etc.
// TODO: Implement d_event.handle
use std::ptr::addr_of_mut;
use crate::doomdef::{DOOMGLOBALS, GameMode};
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_snake_case)]
struct menuitem_t {
status: i32,
name: [u8; 10],
routine: Option<fn(choice: i32)>,
// hotkey in menu
alpha_key: u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_snake_case)]
struct menu_t {
/// Number of items in the menu
numitems: i32,
/// Pointer to the previous menu
prevMenu: *mut menu_t,
/// The menu items
menuItems: *mut menuitem_t,
/// draw routine
routine: Option<fn()>,
/// X of the menu
x: i32,
/// y of the menu
y: i32,
/// The last item the user was on in the menu
lastOn: i32
}
/// TEMP function
#[allow(non_snake_case)]
fn VoidRoutine() {
}
#[allow(non_snake_case)]
fn VoidRoutineWithChoice(choice: i32) {
let _ = choice;
}
// Main Menu
#[allow(non_snake_case)]
mod MainMenu {
use super::*;
/// Enumeration of the main menu options
#[allow(non_camel_case_types)]
pub(super) enum main_e {
newgame = 0,
options,
loadgame,
savegame,
readthis,
quitdoom,
main_end
}
/// Main Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut MainMenu: [menuitem_t; 6] = [
menuitem_t {
status: 1,
name: *b"M_NGAME\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'n'
},
menuitem_t {
status: 1,
name: *b"M_OPTION\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'o'
},
menuitem_t {
status: 1,
name: *b"M_LOADG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'l'
},
menuitem_t {
status: 1,
name: *b"M_SAVEG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b's'
},
menuitem_t {
status: 1,
name: *b"M_RDTHIS\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'r'
},
menuitem_t {
status: 1,
name: *b"M_QUITG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'q'
}
];
/// Default MainMenu
#[allow(non_upper_case_globals)]
pub(super) static mut MainDef: menu_t = menu_t {
numitems: main_e::main_end as i32,
prevMenu: std::ptr::null_mut(),
menuItems: &raw mut MainMenu as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 97,
y: 64,
lastOn: 0,
};
}
// New Game menu
#[allow(non_snake_case)]
mod NewGameMenu {
use super::*;
/// Enumeration of the New Game options
#[allow(non_camel_case_types)]
pub(super) enum newgame_e {
killthings,
toorough,
hurtme,
violence,
nightmare,
newg_end
}
/// New Game Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut NewGameMenu: [menuitem_t; 5] = [
menuitem_t {
status: 1,
name: *b"M_JKILL\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'i'
},
menuitem_t {
status: 1,
name: *b"M_ROUGH\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'h'
},
menuitem_t {
status: 1,
name: *b"M_HURT\0\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'h'
},
menuitem_t {
status: 1,
name: *b"M_ULTRA\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'u'
},
menuitem_t {
status: 1,
name: *b"M_NMARE\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'n'
}
];
#[allow(non_upper_case_globals)]
pub(super) static mut NewDef: menu_t = menu_t {
numitems: newgame_e::newg_end as i32,
prevMenu: std::ptr::null_mut(), // TODO: Add EpiDef
menuItems: &raw mut NewGameMenu as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 48,
y: 63,
lastOn: newgame_e::hurtme as i32,
};
}
// TODO: add OptionsMenu
#[allow(non_snake_case)]
mod ReadThis1 {
use super::*;
/// Enumeration of the main menu options
#[allow(non_camel_case_types)]
pub(super) enum read_e {
rdthisempty1 = 0,
read1_end,
}
/// Read1 Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut ReadMenu1: [menuitem_t; 1] = [
menuitem_t {
status: 1,
name: *b"\0\0\0\0\0\0\0\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'0'
}
];
/// Default MainMenu
#[allow(non_upper_case_globals)]
pub(super) static mut Read1Def: menu_t = menu_t {
numitems: read_e::rdthisempty1 as i32,
prevMenu: addr_of_mut!(MainMenu::MainDef),
menuItems: &raw mut ReadMenu1 as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 280,
y: 185,
lastOn: 0,
};
}
// TODO: The rest of this hell!
#[allow(non_upper_case_globals)]
static mut currentMenu: *mut menu_t = std::ptr::null_mut();
#[allow(non_upper_case_globals)]
static mut menuactive: bool = false;
#[allow(non_upper_case_globals)]
static mut itemOn: i32 = 0;
#[allow(non_upper_case_globals)]
static mut whichSkull: i32 = 0;
#[allow(non_upper_case_globals)]
static mut skullAnimCounter: i32 = 0;
#[allow(non_upper_case_globals)]
pub(crate) static mut detailLevel: i32 = 0;
#[allow(non_upper_case_globals)]
pub static mut screenSize: i32 = 0;
#[allow(non_upper_case_globals)]
pub(crate) static mut screenBlocks: i32 = 0;
#[allow(non_upper_case_globals)]
static mut messageToPrint: i32 = 0;
#[allow(non_upper_case_globals)]
static mut messageString: *mut u8 = std::ptr::null_mut();
#[allow(non_upper_case_globals)]
static mut messageLastMenuActive: i32 = 0;
#[allow(non_upper_case_globals)]
static mut quickSaveSlot: i32 = -1;
#[allow(non_snake_case)]
pub fn M_Ticket() {
}
#[allow(non_snake_case)]
pub fn M_Drawer() {
}
#[allow(non_snake_case)]
pub fn M_Init() {
unsafe {
currentMenu = addr_of_mut!(MainMenu::MainDef);
menuactive = false;
itemOn = (*currentMenu).lastOn;
whichSkull = 0;
skullAnimCounter = 10;
screenSize = screenBlocks - 3;
messageToPrint = 0;
messageString = std::ptr::null_mut();
messageLastMenuActive = menuactive as i32;
quickSaveSlot = -1;
match DOOMGLOBALS::with_ref(|g| g.gamemode) {
GameMode::Commercial => {
/*
Original source code comment:
// This is used because DOOM 2 had only one HELP
// page. I use CREDIT as second page now, but
// kept this hack for educational purposes.
*/
MainMenu::MainMenu[MainMenu::main_e::readthis as usize] = MainMenu::MainMenu[MainMenu::main_e::quitdoom as usize];
MainMenu::MainDef.numitems -=1;
MainMenu::MainDef.y += 8;
NewGameMenu::NewDef.prevMenu = addr_of_mut!(MainMenu::MainDef);
ReadThis1::Read1Def.routine = Some(VoidRoutine);
ReadThis1::Read1Def.x = 330;
ReadThis1::Read1Def.y = 165;
ReadThis1::ReadMenu1[ReadThis1::read_e::rdthisempty1 as usize].routine = Some(VoidRoutineWithChoice);
},
GameMode::Shareware | GameMode::Registered => {
// TODO EpiDef.numitems--
}
_ => {
// do nothing :)
}
}
}
}
#[allow(non_snake_case)]
pub fn M_StartControlPanel() {
}

View File

@@ -4,9 +4,12 @@ mod doomdef;
mod doomtype;
mod hu_stuff;
mod m_argv;
mod m_menu;
mod m_misc;
mod renderer;
mod v_video;
mod w_wad;
mod z_zone;
mod math;

View File

@@ -2,46 +2,63 @@
This provides fixed-point arithmetic as 32-bit 16.16
*/
use std::ops::ShrAssign;
use crate::doomtype::{
MININT,
MAXINT
};
pub const FRACBITS: i32 = 16;
pub const FRACUNIT: i32 = 1 << FRACBITS;
pub const MININT: i32 = 0x80000000;
pub const MAXINT: i32 = 0x7fffffff;
/// Struct representing a fixed point 32-bit value
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct FixedPoint {
#[allow(non_camel_case_types)]
pub struct fixed_t {
pub value: i32,
}
impl FixedPoint {
impl fixed_t {
#[allow(non_snake_case)]
pub fn FixedMul(a: FixedPoint, b: FixedPoint) -> FixedPoint {
let value = ((a.value as u64) * (b.value as u64) >> FRACBITS) as i32;
FixedPoint{value}
pub const fn new(val: i32) -> Self {
fixed_t { value: val }
}
#[allow(non_snake_case)]
pub fn FixedDiv(a: FixedPoint, b: FixedPoint) -> FixedPoint {
pub fn FixedMul(a: fixed_t, b: fixed_t) -> fixed_t {
let value = ((a.value as u64) * (b.value as u64) >> FRACBITS) as i32;
fixed_t{value}
}
#[allow(non_snake_case)]
pub fn FixedDiv(a: fixed_t, b: fixed_t) -> fixed_t {
if (i32::abs(a.value) >> 14) >= i32::abs(b.value) {
if a.value^b.value == 0 {
FixedPoint{value: MININT};
fixed_t{value: MININT};
}
else {
FixedPoint{value: MAXINT};
fixed_t{value: MAXINT};
}
}
FixedPoint::FixedDiv2(a,b)
fixed_t::FixedDiv2(a,b)
}
#[allow(non_snake_case)]
pub fn FixedDiv2(a: FixedPoint, b: FixedPoint) -> FixedPoint {
pub fn FixedDiv2(a: fixed_t, b: fixed_t) -> fixed_t {
let c: f64 = ((a.value as f64) / (b.value as f64)) * FRACUNIT as f64;
if c >= 2147483648.0 || c < -2147483648.0 {
panic!("FixedDiv: divide by zero");
}
FixedPoint { value: c as i32 }
fixed_t { value: c as i32 }
}
}
impl ShrAssign<i32> for fixed_t {
fn shr_assign(&mut self, rhs: i32) {
// Shift the underlying wrapped i32 value directly
self.value >>= rhs;
}
}

View File

@@ -1,3 +1,5 @@
// pub mod m_fixed;
mod m_fixed;
// pub use m_fixed::FixedPoint;
pub use m_fixed::fixed_t;
pub use m_fixed::FRACUNIT;
pub use m_fixed::FRACBITS;

130
src/renderer/mod.rs Normal file
View File

@@ -0,0 +1,130 @@
// Main entry to Render stuff
// i.e r_main c/h
mod r_data;
mod r_defs;
mod r_plane;
use crate::doomdef::SCREENWIDTH;
use crate::math::{fixed_t, FRACUNIT};
use crate::m_menu::{detailLevel, screenBlocks};
use r_data::R_InitData;
use r_defs::lighttable_t;
use r_plane::R_InitPlanes;
// Light constants
// Orignal source comment: Now why not 32 levels here?
pub const LIGHTLEVELS: i32 = 16;
pub const MAXLIGHTSCALE: i32 = 48;
pub const LIGHTSCALESHIFT: i32 = 12;
pub const MAXLIGHTZ: i32 = 128;
pub const LIGHTZSHIFT: i32 = 20;
#[allow(non_upper_case_globals)]
pub static mut scalelight: [[*mut lighttable_t; MAXLIGHTSCALE as usize]; LIGHTLEVELS as usize] =
[[std::ptr::null_mut(); MAXLIGHTSCALE as usize]; LIGHTLEVELS as usize];
#[allow(non_upper_case_globals)]
pub static mut scalelightfixed: [*mut lighttable_t; MAXLIGHTSCALE as usize] =
[std::ptr::null_mut(); MAXLIGHTSCALE as usize];
#[allow(non_upper_case_globals)]
pub static mut zlight: [[*mut lighttable_t; MAXLIGHTZ as usize]; LIGHTLEVELS as usize] =
[[std::ptr::null_mut(); MAXLIGHTZ as usize]; LIGHTLEVELS as usize];
/// R_InitLightTables
/// Only inits the zlight table,
/// because the scalelight table changes with view size.
pub const DISTMAP:i32 = 2;
/// Number of diminishing brightness levels.
/// There a 0-31, i.e. 32 LUT in the COLORMAP lump.
pub const NUMCOLORMAPS: i32 = 32;
#[allow(non_upper_case_globals)]
static mut viewcos: fixed_t = fixed_t{value: 0};
#[allow(non_upper_case_globals)]
static mut viewsin: fixed_t = fixed_t{value: 0};
#[allow(non_upper_case_globals)]
static mut setsizeneeded: bool = false;
#[allow(non_upper_case_globals)]
static mut setblocks: i32 = 0;
#[allow(non_upper_case_globals)]
static mut setdetail: i32 = 0;
#[allow(non_snake_case)]
fn R_InitPointToAngle() {
// this is unused in the original source
// we get this data from tables
}
#[allow(non_snake_case)]
fn R_InitTables() {
// this is unused in the original source
// we get this data from tables
}
#[allow(non_snake_case)]
fn R_InitLightTables() {
let mut startmap: i32;
let mut scale: fixed_t;
let mut level: i32;
for i in 0..(LIGHTLEVELS as usize) {
startmap = ((LIGHTLEVELS - 1 - (i as i32)) * 2) * NUMCOLORMAPS/LIGHTLEVELS;
for j in 0..(MAXLIGHTZ as usize) {
scale = fixed_t::FixedDiv(fixed_t{value: (SCREENWIDTH/2*FRACUNIT)}, fixed_t{value: ((j as i32) + 1) << LIGHTZSHIFT});
scale >>= LIGHTSCALESHIFT;
level = startmap - scale.value/DISTMAP;
level = level.clamp(0, NUMCOLORMAPS - 1);
// zlight[i][j] = colormaps + level * 256; //TODO: Implement zlight
}
}
}
/* // TODO: Implement player_t
pub fn R_RenderPlayerView(player: *const player_t) {
}
*/
#[allow(non_snake_case)]
pub fn R_Init() {
R_InitData();
println!("R_InitData");
R_InitPointToAngle();
println!("R_InitPointToAngle");
R_InitTables();
// viewwidth / viewheight / detailLevel are set by the defaults
println!("R_InitTables");
unsafe {R_SetViewSize(screenBlocks, detailLevel);}
R_InitPlanes();
println!("R_InitPlanes");
R_InitLightTables();
println!("R_InitLightTables");
//R_InitSkyMap();
println!("R_InitSkyMap");
//R_InitTranslationTables();
println!("R_InitTranslationTables")
// framecount = 0;
}
#[allow(non_snake_case)]
pub fn R_SetViewSize(blocks: i32, detail: i32) {
unsafe {
setsizeneeded = true;
setblocks = blocks;
setdetail = detail;
}
}

60
src/renderer/r_data.rs Normal file
View File

@@ -0,0 +1,60 @@
#[allow(non_snake_case)]
fn R_InitTextures() {
}
#[allow(non_snake_case)]
fn R_InitFlats() {
}
#[allow(non_snake_case)]
fn R_InitSpriteLumps() {
}
#[allow(non_snake_case)]
fn R_InitColormaps() {
}
#[allow(non_snake_case)]
pub fn R_GetColumn (tex: i32, col: i32) {
}
/// R_InitData
/// Locates all the lumps that will be used by all views
/// This must be called after `W_Init`
#[allow(non_snake_case)]
pub fn R_InitData() {
R_InitTextures();
println!("InitTextures");
R_InitFlats();
println!("InitFlats");
R_InitSpriteLumps();
println!("InitSprites");
R_InitColormaps();
println!("InitColormaps");
}
#[allow(non_snake_case)]
pub fn R_PrecacheLevel() {
}
#[allow(non_snake_case)]
pub fn R_FlatNumForName(name: &str) -> i32 {
1
}
#[allow(non_snake_case)]
pub fn R_TextureNumForName(name: &str) -> i32 {
1
}
#[allow(non_snake_case)]
pub fn R_CheckTextureNumForName(name: &str) -> i32 {
1
}

441
src/renderer/r_defs.rs Normal file
View File

@@ -0,0 +1,441 @@
use crate::doomdef::SCREENWIDTH;
use crate::m_fixed::{fixed_t};
use crate::tables::angle_t;
// TODO: Implement d_think.h and p_mobj.h
/// Original Source comment:
/// Silhouette, needed for clipping Segs (mainly)
/// and sprites representing things.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum Silhouette {
None = 0,
Bottom = 1,
Top = 2,
Both = 3,
}
pub const MAXDRAWSEGS: i32 = 256;
/// Original source comment
/// This could be wider for >8 bit display.
/// Indeed, true color support is posibble
/// precalculating 24bpp lightmap/colormap LUT.
/// from darkening PLAYPAL to all black.
/// Could even us emore than 32 levels.
type lighttable_t = u8;
/// Original Source comment:
/// Your plain vanilla vertex.
/// Note: transformed values not buffered locally,
/// like some DOOM-alikes ("wt", "WebView") did.
#[derive(Copy, Clone)]
pub struct vertex_t {
x: fixed_t,
y: fixed_t
}
/// Original Source comment:
/// Each sector has a degenmobj_t in its center
/// for sound origin purposes.
/// I suppose this does not handle sound from
/// moving objects (doppler), because
/// position is prolly just buffered, not
/// updated.
pub struct degenmobj_t {
//TODO: implement d_think.h
// thinker: thinker_t,
x: fixed_t,
y: fixed_t,
z: fixed_t
}
/// Sectors represent a distinct, closed polygon area on the map with uniform
/// floor heights, ceiling heights, lighting levels, and floor/ceiling textures.
/// They also serve as containers for gameplay entities (`mobj_t`).
/// Original Source comment:
/// The SECTORS record, at runtime.
/// Stores things/mobjs.
pub struct sector_t {
/// Height of the floor
pub(crate) floorheight: fixed_t,
/// Height of the ceiling
pub(crate) ceilingheight: fixed_t,
/// Texture index applied to the floor surface
pub(crate) floorpic: i16,
/// Texture index applied to the ceiling surface
pub(crate) ceilingpic: i16,
/// The current brightness level for the sector
pub(crate) lightlevel: i16,
/// Sector type/behaviour flags (e.g sewage / lava damaging floors)
pub(crate) special: i16,
/// Unique ID tag used by linedef triggers to identify and manipluate this sector
pub(crate) tag: i16,
/// Sound propagation state variable used during sound traversal calculations.
/// Values: 0 = untraversed, 1 or 2 = sound lines - 1.
pub(crate) soundtraversed: i32,
//TODO: mobj_t implementation (p_mobj.h)
/// Pointer to the map object (`mobj_t`) that generated a sound in this sector, or null.
pub(crate) soundtarget: *mut mobj_t,
/// A bounding box `[ymin, ymax, xmin, xmax]` mapping blocks for height changes and collisions.
pub(crate) blockbox: [i32; 4],
/// simplified, stationary map object representing the central origin point for sound playback.
pub(crate) soundorg: degenmobj_t,
/// A frame check counter variable used to avoid re-processing this sector multiple times per tick.
pub(crate) validcount: i32,
/// A dynamic collection of all map objects currently inside this sector.
/// TODO: Change back to *mut mobj_t linked list later
pub(crate) thinglist: Vec<mobj_t>,
/// A generic raw pointer to an active thinker struct (e.g., `plat_t`, `ceiling_t`) for handling moving parts
pub(crate) specialdata: *mut (),
/// The total number of linedefs that form the boundaries of this sector.
pub(crate) linecount: i32,
/// A raw pointer to an array of pointers pointing to the `line_s` (linedef) structs that border this sector.
pub(crate) lines: *mut *mut line_s,
}
/// Linedefs are made up of either one or two sidedefs (front and back).
/// A sidedef contains texture alignment offsets, texture indices, and
/// points directly to the sector it faces.
pub struct sidedef_t {
/// Horizontal alignment offset added to the calculated texture column.
pub(crate) textureoffset: fixed_t,
/// Vertical alignment offset added to the calculated texture top.
pub(crate) rowoffset: fixed_t,
// texture indices
/// The index of the upper wall texture (above the ceiling gap).
pub(crate) toptexture: i16,
/// The index of the lower wall texture (below the floor gap).
pub(crate) bottomtexture: i16,
/// The index of the middle wall texture (used on single-sided walls or windows).
pub(crate) midtexture: i16,
/// A raw pointer to the sector this specific side faces.
pub(crate) sector: *mut sector_t,
}
/// The line slope classification type.
///
/// Used by the collision and rendering engines to optimize intersection
/// checks and line-of-sight tracking based on the orientation of a wall line.
#[derive(Copy, Clone)]
enum slopetype_t {
/// A perfectly horizontal line (dy == 0).
ST_HORIZONTAL,
/// A perfectly vertical line (dx == 0).
ST_VERTICAL,
/// A line with a positive slope rising from bottom-left to top-right (dx and dy have the same sign).
ST_POSITIVE,
/// A line with a negative slope falling from top-left to bottom-right (dx and dy have opposite signs).
ST_NEGATIVE
}
/// Linedefs form the walls and structural geometry of a map. They contain
/// pointers to their start/end vertices, front/back siding data, physical
/// triggers (`special`), and precalculated bounding extents to assist the physics engine.
pub struct linedef_t {
/// Raw pointer to the starting vertex (v1) of the wall.
pub(crate) v1: *mut vertex_t,
/// Raw pointer to the ending vertex (v2) of the wall.
pub(crate) v2: *mut vertex_t,
/// Precalculated horizontal delta value (v2.x - v1.x) for quick side-of-line checking.
pub(crate) dx: fixed_t,
/// Precalculated vertical delta value (v2.y - v1.y) for quick side-of-line checking.
pub(crate) dy: fixed_t,
/// Configuration flags handling behavior (e.g., solid wall, blocks monsters, secret map wall).
pub(crate) flags: i16,
/// The linedef activation type/trigger function index (e.g., doors, teleporters, lifts).
pub(crate) special: i16,
/// A unique ID tag matching sector targets to process execution triggers.
pub(crate) tag: i16,
/// Sidedef lookups. `sidenum[0]` is the front side; `sidenum[1]` is the back side (or -1 if single-sided).
pub(crate) sidenum: [i16; 2],
/// Bounding box layout `[ymin, ymax, xmin, xmax]` representing the full grid extent of the line.
pub(crate) bbox: [fixed_t; 4],
/// Slope classification used to optimize physics intersection and visibility clipping loops.
pub(crate) slopetype: slopetype_t,
/// Raw pointer to the sector directly in front of this linedef's first side.
pub(crate) frontsector: *mut sector_t,
/// Raw pointer to the sector behind this linedef's second side, or null if single-sided.
pub(crate) backsector: *mut sector_t,
/// A frame counter lookup variable used to avoid parsing or processing this line multiple times per tick.
pub(crate) validcount: i32,
/// A generic raw pointer to a running thinker action layout (e.g., standard scrolling wall textures).
pub(crate) specialdata: *mut (),
}
/// Subsectors are the structural leaves at the bottom of the BSP tree.
/// They represent smaller, guaranteed-convex sub-polygons carved out of a
/// larger sector, defined by a sequential slice of rendering lines (`seg_t`).
pub struct subsectordef_t {
/// A raw pointer to the parent sector that this subsector belongs to.
pub(crate) sector: *mut sector_t,
/// The total number of consecutive line segments (`seg_t`) that form this subsector.
pub(crate) numlines: i16,
/// The starting array index inside the global `segs` array for this subsector's lines.
pub(crate) firstline: i16,
}
/// Segs are clipped slices of linedefs generated by the node builder.
/// The rendering engine processes them in strict front-to-back order
/// using the BSP tree to draw wall surfaces without sorting artifacts.
pub struct segdef_t {
/// Raw pointer to the starting vertex (v1) of this specific segment.
pub(crate) v1: *mut vertex_t,
/// Raw pointer to the ending vertex (v2) of this specific segment.
pub(crate) v2: *mut vertex_t,
/// The distance offset along the original linedef where this segment begins.
pub(crate) offset: fixed_t,
/// The absolute binary angle of the segment (represented as a 32-bit unsigned integer).
pub(crate) angle: angle_t,
/// Raw pointer to the sidedef structure containing the texture data for this segment.
pub(crate) sidedef: *mut sidedef_t,
/// Raw pointer to the parent linedef structure this segment was split out from.
pub(crate) linedef: *mut linedef_t,
/// Raw pointer to the sector directly in front of this segment.
pub(crate) frontsector: *mut sector_t,
/// Raw pointer to the sector behind this segment, or null if it belongs to a solid one-sided wall.
pub(crate) backsector: *mut sector_t,
}
/// Nodes form the internal branching skeleton of the map's 3D visibility tree.
/// Each node acts as a cutting plane line segment that splits a sub-region into
pub struct node_t {
/// Starting X coordinate of the division splitter line.
pub(crate) x: fixed_t,
/// Starting Y coordinate of the division splitter line.
pub(crate) y: fixed_t,
/// Horizontal delta length of the division splitter vector.
pub(crate) dx: fixed_t,
/// Vertical delta length of the division splitter vector.
pub(crate) dy: fixed_t,
/// Precalculated bounding boxes [ymin, ymax, xmin, xmax] for the right [0] and left [1] child spaces.
pub(crate) bbox: [[FixedT; 4]; 2],
/// References to left [0] and right [1] children. If highest bit (0x8000) is set, it targets a subsector_t index.
pub(crate) children: [u16; 2],
}
/// A runtime record of a drawn wall segment.
/// Stores clipping and scaling metadata to correctly occlude and scale sprites behind it.
pub struct drawseg_t {
/// Pointer to the line segment being rendered (usually seg_t).
pub(crate) curline: *mut sector_t,
/// Starting horizontal screen column (left boundary).
pub(crate) x1: i32,
/// Ending horizontal screen column (right boundary).
pub(crate) x2: i32,
/// Initial texture scale at the starting column (x1).
pub(crate) scale1: fixed_t,
/// Final texture scale at the ending column (x2).
pub(crate) scale2: fixed_t,
/// Amount to change the texture scale per horizontal pixel step.
pub(crate) scalestep: fixed_t,
/// Silhouette type flags handling sprite occlusion (e.g., SIL_BOTTOM, SIL_TOP).
pub(crate) silhouette: i32,
/// Maximum floor height boundary for bottom silhouette clipping.
pub(crate) bsilheight: fixed_t,
/// Minimum ceiling height boundary for top silhouette clipping.
pub(crate) tsilheight: fixed_t,
/// Pointer to the screen column array managing top clipping bounds for sprites.
pub(crate) sprtopclip: *mut u16,
/// Pointer to the screen column array managing bottom clipping bounds for sprites.
pub(crate) sprbottomclip: *mut u16,
/// Pointer to the column offset array used for rendering masked textures.
pub(crate) maskedtexturecol: *mut u16,
}
/// A hardware-agnostic 2D picture or graphic asset.
/// Uses a column-oriented format to optimize rapid vertical stretching and scaling.
pub struct patch_t {
/// Total width of the graphic patch in pixels.
pub(crate) width: u16,
/// Total height of the graphic patch in pixels.
pub(crate) height: u16,
/// Horizontal offset relative to the origin (used to center weapon and sprite frames).
pub(crate) leftoffset: u16,
/// Vertical offset relative to the origin (used to center weapon and sprite frames).
pub(crate) topoffset: u16,
/// Array of byte offsets from the start of the patch to the start of each vertical pixel column.
/// only [width] used
// the [0] is &columnofs[width]
pub(crate) columnofs: [i32; 8],
}
/// A visible sprite entry prepared for rendering.
/// Tracks screen bounds, scaling, and clipping parameters for a partly visible map object.
pub struct vissprite_t {
/// Pointer to the previous sprite in the doubly linked list.
pub(crate) prev: *mut vissprite_t,
/// Pointer to the next sprite in the doubly linked list.
pub(crate) next: *mut vissprite_t,
/// Starting horizontal screen column (left boundary).
pub(crate) x1: i32,
/// Ending horizontal screen column (right boundary).
pub(crate) x2: i32,
/// Global X coordinate used for line-side visibility checks.
pub(crate) gx: fixed_t,
/// Global Y coordinate used for line-side visibility checks.
pub(crate) gy: fixed_t,
/// Global bottom Z coordinate used for silhouette clipping calculations.
pub(crate) gz: fixed_t,
/// Global top Z coordinate used for silhouette clipping calculations.
pub(crate) gzt: fixed_t,
/// Horizontal texture offset fraction at screen column x1.
pub(crate) startfrac: fixed_t,
/// Scale factor of the sprite relative to its distance from the camera.
pub(crate) scale: fixed_t,
/// Horizontal step scale factor (inverted and negative if the sprite is flipped).
pub(crate) xiscale: fixed_t,
/// Vertical texture alignment midpoint coordinate.
pub(crate) texturemid: fixed_t,
/// The index of the graphic patch asset being rendered.
pub(crate) patch: i32,
/// Pointer to the color lookup array handling lighting, shadows, and full-brightness frames.
pub(crate) colormap: *mut lighttable_t,
/// Cached copy of the parent map object's physics and state flags.
pub(crate) mobjflags: i32,
}
/// Animation frame data for a sprite object.
/// Tracks raw graphic lump IDs and horizontal flip bits for all 8 view rotations.
#[derive(Copy, Clone)]
pub struct spriteframe_t {
/// True if the sprite has directional rotation variants; false if it looks identical from all sides.
pub(crate) rotate: bool,
/// WAD graphic lump indices representing the image assets for view angles 0 to 7.
pub(crate) lump: [i16; 8],
/// Bitmask flags (1 = mirror image horizontally) applied to view angles 0 to 7 to save space.
pub(crate) flip: [u8; 8],
}
/// A top-level sprite asset container managing a series of sequential animation frames.
pub struct spritedef_t {
/// Total number of animation frames allocated for this sprite sequence.
pub(crate) numframes: i32,
/// Raw pointer to the array of frame records tracking rotation and asset data.
pub(crate) spriteframes: *mut spriteframe_t,
}
/// A floor or ceiling polygon segment prepared for flat horizontal texture mapping.
/// Merges drawing spans to prevent visible gaps or layout cracking during screen refresh.
pub struct visplane_t {
/// Absolute height level of the floor or ceiling plane in fixed-point space.
pub(crate) height: fixed_t,
/// WAD graphic texture flat index applied to the surface.
pub(crate) picnum: i32,
/// Calculated light/brightness visibility level of the flat polygon plane.
pub(crate) lightlevel: i32,
/// The leftmost horizontal screen column index bound for drawing.
pub(crate) minx: i32,
/// The rightmost horizontal screen column index bound for drawing.
pub(crate) maxx: i32,
/// Memory padding byte mimicking historical x-1 lookups.
pub(crate) pad1: u8,
/// Dynamic horizontal boundary array tracing top rendering margins across columns.
pub(crate) top: [u8; SCREENWIDTH],
/// Memory padding byte mimicking historical array bounds checks.
pub(crate) pad2: u8,
/// Memory padding byte mimicking historical array bounds checks.
pub(crate) pad3: u8,
/// Dynamic horizontal boundary array tracing bottom rendering margins across columns.
pub(crate) bottom: [u8; SCREENWIDTH],
/// Memory padding byte mimicking historical x+1 lookups.
pub(crate) pad4: u8,
}

39
src/renderer/r_plane.rs Normal file
View File

@@ -0,0 +1,39 @@
#[allow(non_snake_case)]
pub fn R_InitPlanes() {
// Original source comment
// doh!
// wtf?
}
#[allow(non_snake_case)]
pub fn R_ClearPlanes() {
}
#[allow(non_snake_case)]
pub fn R_MapPlane (y: i32, x1: i32, x2: i32) {
}
#[allow(non_snake_case)]
pub fn R_MakeSpans(x: i32, t1: i32, b1: i32, t2: i32, b2: i32) {
}
#[allow(non_snake_case)]
pub fn R_DrawPlanes() {
}
/* TODO: Implement visplane_t
#[allow(non_snake_case)]
pub fn R_FindPlane(height: fixed_t, picnum: i32, lightlevel: i32) -> *const visplane_t {
}
#[allow(non_snake_case)]
pub fn R_CheckPlane(pl: *const visplane_t, start: i32, stop: i32) -> *const visplane_t {
}
*/

1843
src/tables/mod.rs Normal file

File diff suppressed because it is too large Load Diff

165
src/z_zone/mod.rs Normal file
View File

@@ -0,0 +1,165 @@
/// Memory allocator stuff
/// Compared to the original source, this will be some what "lean n' mean"
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
/// The PurgeTags (PUs)
/// Tags < 100 are not overwritten untill freed
pub enum PurgeTag {
/// Static the entire execution time
Static = 1,
/// Static while playing
Sound = 2,
/// static while playing
Music = 3,
/// Original source comment: anything else dave wants static
Dave = 4,
/// static until level exit
Level = 50,
/// static for special thinkers in a level
LevSpec = 51,
// Tags >= 100 are "purgable" whenever needed
PurgeLevel = 100,
Cache = 101,
}
/// Provides metadata on
struct AllocMeta {
/// The size of the allocation
size: usize,
/// The PU tag associated with the allocation
tag: i32,
/// A pointer to the caller's tracking pointer, cleared automatically upon freeing
user: *mut *mut u8,
}
thread_local! {
// We promise not to be naughty and access this from another thread :)
/// Mapping from pointers (as usize) to the correspond AllocationMeta
static REGISTRY: RefCell<HashMap<usize, AllocMeta>> = RefCell::new(HashMap::new());
}
// Helper method that we can use to access the registry
fn with_registry<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<usize, AllocMeta>) -> R
{
REGISTRY.with(|registry| f(&mut *registry.borrow_mut()))
}
#[allow(non_snake_case)]
/// Initalized the Zone memory allocator sub-system
pub fn Z_Init() {
// do nothing, we get lazy init of the registry.
}
#[allow(non_snake_case)]
/// Allocate a contiguous chunk of zeroed memory from the zone allocator
/// # Safety
/// This function returns a raw pointer that bypasses Rust's automatic memory
/// tracking. The returned memory must be manually reclaimed using `Z_Free`.
pub unsafe fn Z_Malloc(size: usize, tag: i32, user: *mut *mut u8) -> *mut u8 {
let mut buffer = vec![0u8; size];
let ptr = buffer.as_mut_ptr();
// ensures Rust does not drop the buffer at the end of this block
std::mem::forget(buffer);
// Update the engine's tracking pointer if one was provided
if !user.is_null() {
unsafe {
*user = ptr;
}
}
// Save allocation details using the pointer address as the key
with_registry(|map| {
map.insert(ptr as usize, AllocMeta { size, tag, user });
});
ptr
}
#[allow(non_snake_case)]
/// Reclaims and frees a allocation from Z_Malloc
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry.
/// Passing an invalid or already freed pointer can result in undefined behavior
pub unsafe fn Z_Free(ptr: *mut u8) {
if ptr.is_null() {
return;
}
let removed = with_registry(|map| map.remove(&(ptr as usize)));
if let Some(meta) = removed {
// Clear the caller's tracking pointer to prevent use-after-free
if !meta.user.is_null() {
unsafe {
*meta.user = std::ptr::null_mut();
}
}
// re-build the vector so Rust can drop it
unsafe {
let _dropped_vec = Vec::from_raw_parts(ptr, meta.size, meta.size);
}
}
}
#[allow(non_snake_case)]
/// Frees all memory allocations between the specific lifetime PU tags.
/// # Safety
/// All pointers tracked within this tag boundary are invalidated and deallocate
pub unsafe fn Z_FreeTags(low_tag: i32, high_tag: i32) {
// get all of the allocations to free
let tags_to_free: Vec<usize> = with_registry(|map| {
map.iter()
.filter(|(_, meta)| meta.tag >= low_tag && meta.tag <= high_tag)
.map(|(ptr_addr,_)| *ptr_addr)
.collect()
});
// remove each pointer returned from the map and drop the memory
for ptr_addr in tags_to_free {
let removed_ptr = with_registry(|map| map.remove(&ptr_addr));
if let Some(meta) = removed_ptr {
let p = ptr_addr as *mut u8;
unsafe {
// clear caller tracking pointer to preven use after free
if !meta.user.is_null() {
*meta.user = std::ptr::null_mut();
}
let _dropped_vec = Vec::from_raw_parts(p, meta.size, meta.size);
}
}
}
}
// Internal ChangeTag function
#[allow(non_snake_case)]
fn Z_ChangeTagInternal(ptr: *mut u8, tag: i32) {
if ptr.is_null() {
return;
}
with_registry(|map| {
if let Some(meta) = map.get_mut(&(ptr as usize)) {
meta.tag = tag;
}
})
}
/// Modifies the lifetime tag of an active allocation.
///
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry
#[allow(non_snake_case)]
pub unsafe fn Z_ChangeTag(ptr: *mut u8, tag: i32) {
// removes the original helper macro from the original C, we don't need withcraft here
Z_ChangeTagInternal(ptr, tag);
}