#![allow(clippy::indexing_slicing)]
use crate::math::*;
use crate::texture::TextureData;
use super::*;
const MINZ: Fixed = 4 * FRACUNIT;
pub struct ThingRef {
pub x: Fixed,
pub y: Fixed,
pub z: Fixed,
pub angle: Angle,
pub sprite_num: usize,
pub frame: usize,
pub flags: u32,
pub mobj_type: u16,
pub light_level: i16,
}
impl Renderer {
pub fn draw_things(
&mut self,
things: &[ThingRef],
textures: &TextureData,
) {
self.vissprites.clear();
for thing in things {
self.project_sprite(thing, textures);
}
self.vissprites
.sort_by(|a, b| a.scale.cmp(&b.scale));
let vis_count = self.vissprites.len();
for i in 0..vis_count {
self.draw_vissprite(i, textures);
}
}
fn project_sprite(&mut self, thing: &ThingRef, textures: &TextureData) {
let tr_x = thing.x - self.view_x;
let tr_y = thing.y - self.view_y;
let gxt = fixed_mul(tr_x, self.view_cos);
let gyt = -fixed_mul(tr_y, self.view_sin);
let tz = gxt - gyt;
if tz < MINZ {
return; }
let xscale = fixed_div(self.projection, tz);
if xscale <= 0 {
return;
}
let gxt2 = -fixed_mul(tr_x, self.view_sin);
let gyt2 = fixed_mul(tr_y, self.view_cos);
let tx_base = -(gyt2 + gxt2);
if tx_base.abs() > tz << 2 {
return;
}
if thing.sprite_num >= textures.sprites.len() {
return;
}
let sprdef = &textures.sprites[thing.sprite_num];
let frame_idx = thing.frame & 0x7FFF; if frame_idx >= sprdef.frames.len() {
return;
}
let sprframe = &sprdef.frames[frame_idx];
let (lump, flip) = if sprframe.rotate {
let ang = crate::render::bsp::point_to_angle_2(
self.view_x, self.view_y, thing.x, thing.y,
);
let rot = ang
.wrapping_sub(thing.angle)
.wrapping_add((ANG45 / 2).wrapping_mul(9))
>> 29;
let rot = (rot as usize) & 7;
(sprframe.lump[rot], sprframe.flip[rot])
} else {
(sprframe.lump[0], sprframe.flip[0])
};
if lump < 0 || lump as usize >= textures.sprite_info.len() {
return;
}
let lump_idx = lump as usize;
let si = &textures.sprite_info[lump_idx];
let tx = tx_base - si.left_offset;
let x1 = (self.center_x_frac + fixed_mul(tx, xscale)) >> FRACBITS;
if x1 >= SCREENWIDTH as i32 {
return;
}
let tx2 = tx + si.width;
let x2 = ((self.center_x_frac + fixed_mul(tx2, xscale)) >> FRACBITS) - 1;
if x2 < 0 {
return;
}
let iscale = fixed_div(FRACUNIT, xscale);
let texturemid = thing.z + si.top_offset - self.view_z;
let start_frac = if flip {
si.width - 1
} else {
0
};
let x_iscale = if flip { -iscale } else { iscale };
let vis_x1 = x1.max(0);
let vis_x2 = x2.min(SCREENWIDTH as i32 - 1);
let mut sf = start_frac;
if vis_x1 > x1 {
sf += x_iscale * (vis_x1 - x1);
}
let sector_light = ((thing.light_level >> LIGHTSEGSHIFT as i16) as usize)
.min(LIGHTLEVELS - 1);
let light_idx = ((xscale >> LIGHTSCALESHIFT) as usize).min(MAXLIGHTSCALE - 1);
let colormap_idx = self.scalelight[sector_light][light_idx];
let semantic = classify_mobj_type(thing.mobj_type);
self.vissprites.push(VisSprite {
x1: vis_x1,
x2: vis_x2,
scale: xscale,
x_iscale,
start_frac: sf,
texture_mid: texturemid,
patch_lump: lump_idx,
colormap_idx,
mobj_flags: thing.flags,
semantic,
gz: thing.z,
gzt: thing.z + si.top_offset,
tz,
});
}
fn draw_vissprite(&mut self, vis_idx: usize, textures: &TextureData) {
let vis = self.vissprites[vis_idx].clone();
let si = if vis.patch_lump < textures.sprite_info.len() {
&textures.sprite_info[vis.patch_lump]
} else {
return;
};
let Some(patch) = si.patch.as_ref() else {
return;
};
let sprite_scale = vis.scale;
let dc_iscale = vis.x_iscale.unsigned_abs() as Fixed;
let sprtop = self.center_y_frac - fixed_mul(vis.texture_mid, sprite_scale);
let cmap = if vis.colormap_idx < textures.colormaps.len() {
&textures.colormaps[vis.colormap_idx]
} else if !textures.colormaps.is_empty() {
&textures.colormaps[0]
} else {
return;
};
let mut clipbot = [self.view_height; SCREENWIDTH];
let mut cliptop = [-1i32; SCREENWIDTH];
for ds in self.drawsegs.iter().rev() {
if ds.x1 > vis.x2 || ds.x2 < vis.x1 {
continue;
}
let r1 = ds.x1.max(vis.x1);
let r2 = ds.x2.min(vis.x2);
let mut sil = ds.silhouette;
if (sil & SIL_BOTTOM) != 0 && vis.gz >= ds.bsilheight {
sil &= !SIL_BOTTOM;
}
if (sil & SIL_TOP) != 0 && vis.gzt <= ds.tsilheight {
sil &= !SIL_TOP;
}
if sil == SIL_NONE {
continue;
}
for x in r1..=r2 {
let xu = x as usize;
let ds_scale = ds.scale1 + ds.scalestep * (x - ds.x1);
if vis.scale >= ds_scale {
continue; }
if (sil & SIL_BOTTOM) != 0
&& ds.sprite_bottom_clip[xu] < clipbot[xu] as i16
{
clipbot[xu] = ds.sprite_bottom_clip[xu] as i32;
}
if (sil & SIL_TOP) != 0
&& ds.sprite_top_clip[xu] > cliptop[xu] as i16
{
cliptop[xu] = ds.sprite_top_clip[xu] as i32;
}
}
}
let mut frac = vis.start_frac;
for x in vis.x1..=vis.x2 {
let xu = x as usize;
if xu >= SCREENWIDTH {
break;
}
let tex_col = (frac >> FRACBITS) as usize;
frac += vis.x_iscale;
if tex_col >= patch.columns.len() {
continue;
}
let col_top = cliptop[xu] + 1;
let col_bot = clipbot[xu] - 1;
for post in &patch.columns[tex_col] {
let pixels = post.pixels(&patch.pixel_data);
let top_delta = post.top_delta as i32;
let length = pixels.len() as i32;
let top_screen = sprtop + fixed_mul(top_delta << FRACBITS, sprite_scale);
let bottom_screen = top_screen + fixed_mul(length << FRACBITS, sprite_scale);
let mut yl = (top_screen + FRACUNIT - 1) >> FRACBITS;
let mut yh = (bottom_screen - 1) >> FRACBITS;
yl = yl.max(col_top).max(0);
yh = yh.min(col_bot).min(SCREENHEIGHT as i32 - 1);
if yl > yh {
continue;
}
let mut tex_frac = if yl > (top_screen >> FRACBITS) {
dc_iscale as i64 * (yl as i64 - (top_screen >> FRACBITS) as i64)
} else {
0
};
for y in yl..=yh {
let tex_y = (tex_frac >> FRACBITS) as usize;
if tex_y < pixels.len() {
let pixel = pixels[tex_y];
let idx = (y * SCREENWIDTH as i32 + x) as usize;
self.screen[idx] = cmap[pixel as usize];
self.semantic[idx] = vis.semantic as u8;
self.depth[idx] = vis.tz;
}
tex_frac += dc_iscale as i64;
}
}
}
}
pub fn draw_weapon_psp(
&mut self,
psp_state: crate::game_data::StateNum,
psp_sx: Fixed,
psp_sy: Fixed,
textures: &TextureData,
) {
let Some(state) = psp_state.get() else { return };
let sprite_num = state.sprite.0 as usize;
let frame = (state.frame & 0x7FFF) as usize;
let fullbright = (state.frame & 0x8000) != 0;
if sprite_num >= textures.sprites.len() { return; }
let sprdef = &textures.sprites[sprite_num];
if frame >= sprdef.frames.len() { return; }
let sprframe = &sprdef.frames[frame];
let lump = sprframe.lump[0];
if lump < 0 || lump as usize >= textures.sprite_info.len() { return; }
let si = &textures.sprite_info[lump as usize];
let Some(patch) = si.patch.as_ref() else { return; };
let pspritescale = FRACUNIT; let tx = psp_sx - 160 * FRACUNIT - si.left_offset;
let x1 = ((self.center_x_frac + fixed_mul(tx, pspritescale)) >> FRACBITS) as i32;
let tx2 = tx + si.width;
let x2 = (((self.center_x_frac + fixed_mul(tx2, pspritescale)) >> FRACBITS) - 1) as i32;
let base_y_center = 100 * FRACUNIT; let texturemid = base_y_center + FRACUNIT / 2 - (psp_sy - si.top_offset);
let cmap_idx = if fullbright { 0 } else {
0 };
let cmap = if cmap_idx < textures.colormaps.len() {
&textures.colormaps[cmap_idx]
} else {
return;
};
let x1c = x1.max(0);
let x2c = x2.min(SCREENWIDTH as i32 - 1);
if x1c > x2c { return; }
let iscale = FRACUNIT;
for x in x1c..=x2c {
let tex_col = ((x - x1) as i64 * patch.width as i64
/ (x2 - x1 + 1).max(1) as i64) as usize;
if tex_col >= patch.columns.len() { continue; }
for post in &patch.columns[tex_col] {
let top_delta = post.top_delta as i32;
let pixels = post.pixels(&patch.pixel_data);
for (py, &pixel) in pixels.iter().enumerate() {
let tex_y = top_delta + py as i32;
let screen_y = self.center_y
+ ((tex_y as i64 * iscale as i64 - texturemid as i64)
>> FRACBITS) as i32;
if screen_y >= 0 && screen_y < SCREENHEIGHT as i32 {
let idx = (screen_y * SCREENWIDTH as i32 + x) as usize;
if idx < self.screen.len() {
self.screen[idx] = cmap[pixel as usize];
}
}
}
}
}
}
}
pub fn classify_mobj_type(mobj_type: u16) -> SemanticClass {
match mobj_type {
3..=6 | 9 | 16 | 19..=22 | 31 | 40 | 49 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 71
| 84 | 85 | 87 | 88 => SemanticClass::Enemy,
10..=15 | 34..=39 | 51 => SemanticClass::Projectile,
0 => SemanticClass::Player, _ => SemanticClass::Item,
}
}