Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl Cache {
}

pub fn get_sprite(&self, index: usize) -> &(CompShape, Vec<u8>) {
&self.sprites[index]
&self.sprites[index - 21]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this magic number mean?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason there's an offset between the number stored in the map tiles and the picnum in the sprites file. In wolf4sdl it's 23, and the extra +2 I don't remember where it's coming from, but it was necessary for it to match with the correct sprite. (we should make these constants)

}

pub fn get_sound(&self, index: usize) -> &Vec<u8> {
Expand Down
3 changes: 3 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub const STATUS_LINES: u32 = 40;
pub const BASE_WIDTH: u32 = 320;
pub const BASE_HEIGHT: u32 = 200;
pub const WALLPIC_WIDTH: usize = 64;
// where does this value come from?
pub const TILE_SIZE: f64 = 4.8;
Comment on lines +17 to +18
Copy link
Copy Markdown

@tsvehagen tsvehagen Mar 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also quite interested in the calculations behind this value :p

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc this was 4 in the initial implementation by @qhool. Not sure how he came up with that number, but the walls looked a bit short so I increased it in a previous PR until it seemed to roughly match the original game (there may be some accurate way to come up with this number)

pub const FIELD_OF_VIEW: f64 = PI / 2.0;

// ok this is not a constant, we may move it to an util module later, or rename this
pub fn norm_angle(a: f64) -> f64 {
Expand Down
187 changes: 178 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#![allow(dead_code)]
use cache::Picture;
use core::slice::Iter;
use std::time::Instant;
use map::Actor;
use ray_caster::RayHit;
use std::{
collections::{HashMap, HashSet},
time::Instant,
};

use clap::Parser;
use std::f64::consts::PI;

use minifb::{Key, KeyRepeat, Window, WindowOptions};

Expand Down Expand Up @@ -60,6 +66,7 @@ struct Game {
level: usize,
start_time: Instant,
cache: cache::Cache,
static_objects: HashMap<(usize, usize), u16>,
}

pub fn main() {
Expand All @@ -80,7 +87,11 @@ pub fn main() {
show_title(&game, &mut video, &mut window);

while process_input(&window, &mut game.player).is_ok() {
draw_world(&game, &mut video);
let (ray_hits, visible_tiles) =
ray_caster::draw_rays(video.pix_width, video.pix_height, &game.map, &game.player);

draw_world(&game, &mut video, &ray_hits);
draw_actors(&game, &mut video, &ray_hits, &visible_tiles);
draw_weapon(&game, &mut video);
draw_status(&game, &mut video);

Expand Down Expand Up @@ -122,11 +133,7 @@ fn show_title(game: &Game, video: &mut Video, window: &mut Window) {
}
}

fn draw_world(game: &Game, video: &mut Video) {
// TODO consider passing game as param here
let ray_hits =
ray_caster::draw_rays(video.pix_width, video.pix_height, &game.map, &game.player);

fn draw_world(game: &Game, video: &mut Video, ray_hits: &[RayHit]) {
// draw floor and ceiling
for x in 0..video.pix_width {
for y in 0..video.pix_height / 2 {
Expand Down Expand Up @@ -173,11 +180,61 @@ fn draw_world(game: &Game, video: &mut Video) {
}
}

fn draw_actors(
game: &Game,
video: &mut Video,
ray_hits: &[RayHit],
visible: &HashSet<(usize, usize)>,
) {
let mut statics = Vec::new();
for &(tx, ty) in visible {
if let Some(shapenum) = game.static_objects.get(&(tx, ty)) {
// https://dev.opera.com/articles/3d-games-with-canvas-and-raycasting-part-2/
// FIXME way too much calculation here, a lot of this should be fixed

// FIXME this area has some bugs I can't yet figure out,
// the heights are not correct, there's some sprite sliding when camera rotats
// and --probably related-- the sprites are hidden before they are occluded by a wall
let dx = (tx as f64 + 0.5) * MAP_SCALE_W as f64 - game.player.x;
let dy = (ty as f64 + 0.5) * MAP_SCALE_H as f64 - game.player.y;
let view_dist = TILE_SIZE * video.pix_width as f64;

let angle = dy.atan2(dx);
let offset = FIELD_OF_VIEW/2.0 - angle;
let ytemp = norm_angle(game.player.angle - offset);
let viewx = ytemp * video.pix_width as f64 / FIELD_OF_VIEW;

let distance = dx * offset.cos() - dy * offset.sin();
let height = (view_dist / distance)as u32;

let pos = statics
.binary_search_by_key(&height, |&(h, _, _)| h)
.unwrap_or_else(|x| x);
statics.insert(pos, (height, viewx, shapenum));
}
}

for (height, viewx, shapenum) in statics {
let (shape, data) = game.cache.get_sprite(*shapenum as usize);

// TODO pass the shape num instead of pieces of the shape?
video.scale_shape(
viewx as u32,
height,
shape.left_pix,
shape.right_pix,
&shape.dataofs,
data,
ray_hits,
);
}
}

fn draw_weapon(game: &Game, video: &mut Video) {
// FIXME use a constant for that 209
let (weapon_shape, weapon_data) = game.cache.get_sprite(209);
let (weapon_shape, weapon_data) = game.cache.get_sprite(230);

// TODO pass the shape num instead of pieces of the shape
// TODO pass the shape num instead of pieces of the shape?
video.simple_scale_shape(
weapon_shape.left_pix,
weapon_shape.right_pix,
Expand Down Expand Up @@ -208,6 +265,16 @@ impl Game {
let cache = cache::init();
let map = cache.get_map(0, level);
let player = map.find_player();

let mut static_objects = HashMap::new();
for x in 0..MAP_WIDTH {
for y in 0..MAP_HEIGHT {
if let Some(Actor::Static(shapenum)) = map.actor_at(x, y) {
static_objects.insert((x, y), shapenum);
}
}
}

Self {
cache,
map,
Expand All @@ -216,6 +283,7 @@ impl Game {
episode: 0,
level,
start_time: Instant::now(),
static_objects,
}
}
}
Expand Down Expand Up @@ -382,6 +450,107 @@ impl Video {
i += 1;
}
}

// FIXME this is almost identical to simple_scale_shape, except for the height check.
// refactor to reuse the code
fn scale_shape(
&mut self,
xcenter: u32,
height: u32,
left_pix: u16,
right_pix: u16,
dataofs: &[u16],
shape_bytes: &[u8],
ray_hits: &[RayHit],
) {
if height > self.pix_center {
return;
}

let sprite_scale_factor = 2;
let scale = height;
let pixheight = scale * sprite_scale_factor;
let actx = xcenter - scale;
let upperedge = self.pix_height / 2 - scale;
// cmdptr=(word *) shape->dataofs;
// cmdptr = iter(shape.dataofs)
let mut cmdptr = dataofs.iter();

let mut i = left_pix;
let mut pixcnt = i as u32 * pixheight;
let mut rpix = (pixcnt >> 6) + actx;

while i <= right_pix {
let mut lpix = rpix;
if lpix >= self.pix_width {
break;
}

pixcnt += pixheight;
rpix = (pixcnt >> 6) + actx;

if lpix != rpix && rpix > 0 {
if rpix > self.pix_width {
rpix = self.pix_width;
i = right_pix + 1;
}
let read_word = |line: &mut Iter<u8>| {
u16::from_le_bytes([*line.next().unwrap_or(&0), *line.next().unwrap_or(&0)])
};
let read_word_signed = |line: &mut Iter<u8>| {
i16::from_le_bytes([*line.next().unwrap_or(&0), *line.next().unwrap_or(&0)])
};

let cline = &shape_bytes[*cmdptr.next().unwrap() as usize..];
while lpix < rpix {
if ray_hits[lpix as usize].height <= height {
let mut line = cline.iter();
let mut endy = read_word(&mut line);
while endy > 0 {
endy >>= 1;
let newstart = read_word_signed(&mut line);
let starty = read_word(&mut line) >> 1;
let mut j = starty;
let mut ycnt = j as u32 * pixheight;
let mut screndy: i32 = (ycnt >> 6) as i32 + upperedge as i32;

let mut pixy = screndy as u32;
while j < endy {
let mut scrstarty = screndy;
ycnt += pixheight;
screndy = (ycnt >> 6) as i32 + upperedge as i32;
if scrstarty != screndy && screndy > 0 {
let index = newstart + j as i16;
let col = if index >= 0 {
shape_bytes[index as usize]
} else {
0
};
if scrstarty < 0 {
scrstarty = 0;
}
if screndy > self.pix_height as i32 {
screndy = self.pix_height as i32;
j = endy;
}

while scrstarty < screndy {
self.put_darkened_pixel(lpix, pixy, col as usize, height);
pixy += 1;
scrstarty += 1;
}
}
j += 1;
}
endy = read_word(&mut line);
}
}
lpix += 1;
}
}
i += 1;
}
}
}

/// Returns an array of colors that maps indexes as used by wolf3d graphics
Expand Down
22 changes: 11 additions & 11 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ pub enum Direction {

pub enum Actor {
Player(Direction),
Enemy, // TODO differentiate enemy types
Item, // TODO differentiate item types
DeadGuard,
Enemy(u16), // TODO differentiate enemy types
Static(u16), // TODO differentiate item types
PushWall,
}

Expand Down Expand Up @@ -61,16 +60,17 @@ impl Map {
}
}

pub fn actor_at(&self, x: u8, y: u8) -> Option<Actor> {
match self.plane1[x as usize][y as usize] {
pub fn actor_at(&self, x: usize, y: usize) -> Option<Actor> {
match self.plane1[x][y] {
19 => Some(Actor::Player(Direction::North)),
20 => Some(Actor::Player(Direction::East)),
21 => Some(Actor::Player(Direction::South)),
22 => Some(Actor::Player(Direction::West)),
n if (23..=72).contains(&n) => Some(Actor::Item),
n if (23..=72).contains(&n) => Some(Actor::Static(n)),
// FIXME why - 8 ?
124 => Some(Actor::Static(124 - 8)), //dead guard
98 => Some(Actor::PushWall),
124 => Some(Actor::DeadGuard),
n if n >= 108 => Some(Actor::Enemy),
n if n >= 108 => Some(Actor::Enemy(n)),
_ => None,
}
}
Expand All @@ -94,9 +94,9 @@ impl Map {
}
}

pub fn find_player_start(&self) -> (u8, u8, Direction) {
for x in 0..MAP_WIDTH as u8 {
for y in 0..MAP_HEIGHT as u8 {
fn find_player_start(&self) -> (usize, usize, Direction) {
for x in 0..MAP_WIDTH {
for y in 0..MAP_HEIGHT {
if let Some(Actor::Player(direction)) = self.actor_at(x, y) {
return (x, y, direction);
}
Expand Down
5 changes: 5 additions & 0 deletions src/player.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::constants;
use num::pow;

const ROTATE_SPEED: f64 = 0.02;
const MOVE_SPEED: f64 = 2.5;
Expand Down Expand Up @@ -29,4 +30,8 @@ impl Player {
self.angle -= ROTATE_SPEED;
self.angle = constants::norm_angle(self.angle);
}

pub fn distance_to(&self, x: f64, y: f64) -> f64 {
(pow(x - self.x, 2) + pow(y - self.y, 2)).sqrt()
}
}
Loading