1
0
mirror of https://github.com/ssloy/tinyraycaster.git synced 2025-01-17 13:18:22 +01:00
tinyraycaster/tinyraycaster.cpp

108 lines
5.2 KiB
C++
Raw Normal View History

2019-02-09 12:44:35 +01:00
#define _USE_MATH_DEFINES
#include <cmath>
#include <iostream>
#include <cassert>
2019-02-09 20:02:38 +01:00
#include "utils.h"
2019-02-09 23:24:22 +01:00
#include "tinyraycaster.h"
2019-02-09 20:02:38 +01:00
2019-02-09 23:24:22 +01:00
int wall_x_texcoord(const float hitx, const float hity, const Texture &tex_walls) {
2019-02-09 21:38:13 +01:00
float x = hitx - floor(hitx+.5); // x and y contain (signed) fractional parts of hitx and hity,
float y = hity - floor(hity+.5); // they vary between -0.5 and +0.5, and one of them is supposed to be very close to 0
int tex = x*tex_walls.size;
if (std::abs(y)>std::abs(x)) // we need to determine whether we hit a "vertical" or a "horizontal" wall (w.r.t the map)
tex = y*tex_walls.size;
2019-02-09 20:02:38 +01:00
if (tex<0) // do not forget x_texcoord can be negative, fix that
tex += tex_walls.size;
assert(tex>=0 && tex<(int)tex_walls.size);
return tex;
2019-02-09 12:47:15 +01:00
}
2019-02-09 23:24:22 +01:00
void draw_map(FrameBuffer &fb, const std::vector<Sprite> &sprites, const Texture &tex_walls, const Map &map, const size_t cell_w, const size_t cell_h) {
for (size_t j=0; j<map.h; j++) { // draw the map itself
for (size_t i=0; i<map.w; i++) {
if (map.is_empty(i, j)) continue; // skip empty spaces
size_t rect_x = i*cell_w;
size_t rect_y = j*cell_h;
size_t texid = map.get(i, j);
assert(texid<tex_walls.count);
fb.draw_rectangle(rect_x, rect_y, cell_w, cell_h, tex_walls.get(0, 0, texid)); // the color is taken from the upper left pixel of the texture #texid
}
}
for (size_t i=0; i<sprites.size(); i++) { // show the monsters
fb.draw_rectangle(sprites[i].x*cell_w-3, sprites[i].y*cell_h-3, 6, 6, pack_color(255, 0, 0));
}
2019-02-09 21:38:13 +01:00
}
2019-02-09 23:24:22 +01:00
void draw_sprite(FrameBuffer &fb, const Sprite &sprite, const std::vector<float> &depth_buffer, const Player &player, const Texture &tex_sprites) {
2019-02-09 21:39:59 +01:00
// absolute direction from the player to the sprite (in radians)
float sprite_dir = atan2(sprite.y - player.y, sprite.x - player.x);
while (sprite_dir - player.a > M_PI) sprite_dir -= 2*M_PI; // remove unncesessary periods from the relative direction
while (sprite_dir - player.a < -M_PI) sprite_dir += 2*M_PI;
size_t sprite_screen_size = std::min(2000, static_cast<int>(fb.h/sprite.player_dist)); // screen sprite size
int h_offset = (sprite_dir - player.a)*(fb.w/2)/(player.fov) + (fb.w/2)/2 - sprite_screen_size/2; // do not forget the 3D view takes only a half of the framebuffer, thus fb.w/2 for the screen width
2019-02-09 21:39:59 +01:00
int v_offset = fb.h/2 - sprite_screen_size/2;
for (size_t i=0; i<sprite_screen_size; i++) {
2019-02-09 21:42:35 +01:00
if (h_offset+int(i)<0 || h_offset+i>=fb.w/2) continue;
2019-02-09 21:54:56 +01:00
if (depth_buffer[h_offset+i]<sprite.player_dist) continue; // this sprite column is occluded
2019-02-09 21:39:59 +01:00
for (size_t j=0; j<sprite_screen_size; j++) {
2019-02-09 21:42:35 +01:00
if (v_offset+int(j)<0 || v_offset+j>=fb.h) continue;
uint32_t color = tex_sprites.get(i*tex_sprites.size/sprite_screen_size, j*tex_sprites.size/sprite_screen_size, sprite.tex_id);
uint8_t r,g,b,a;
unpack_color(color, r, g, b, a);
if (a>128)
fb.set_pixel(fb.w/2 + h_offset+i, v_offset+j, color);
2019-02-09 21:39:59 +01:00
}
}
}
2019-02-09 23:24:22 +01:00
void render(FrameBuffer &fb, const GameState &gs) {
const Map &map = gs.map;
const Player &player = gs.player;
const std::vector<Sprite> &sprites = gs.monsters;
const Texture &tex_walls = gs.tex_walls;
const Texture &tex_monst = gs.tex_monst;
2019-02-09 16:47:05 +01:00
2019-02-09 23:24:22 +01:00
fb.clear(pack_color(255, 255, 255)); // clear the screen
2019-02-09 16:47:05 +01:00
2019-02-09 23:24:22 +01:00
const size_t cell_w = fb.w/(map.w*2); // size of one map cell on the screen
const size_t cell_h = fb.h/map.h;
2019-02-09 21:42:35 +01:00
std::vector<float> depth_buffer(fb.w/2, 1e3);
2019-02-09 23:24:22 +01:00
2019-02-09 20:02:38 +01:00
for (size_t i=0; i<fb.w/2; i++) { // draw the visibility cone AND the "3D" view
float angle = player.a-player.fov/2 + player.fov*i/float(fb.w/2);
for (float t=0; t<20; t+=.01) { // ray marching loop
float x = player.x + t*cos(angle);
float y = player.y + t*sin(angle);
2019-02-09 23:24:22 +01:00
fb.set_pixel(x*cell_w, y*cell_h, pack_color(190, 190, 190)); // this draws the visibility cone
2019-02-09 20:02:38 +01:00
if (map.is_empty(x, y)) continue;
size_t texid = map.get(x, y); // our ray touches a wall, so draw the vertical column to create an illusion of 3D
assert(texid<tex_walls.count);
2019-02-09 23:24:22 +01:00
float dist = t*cos(angle-player.a);
2019-02-09 21:42:35 +01:00
depth_buffer[i] = dist;
size_t column_height = std::min(2000, int(fb.h/dist));
2019-02-09 20:02:38 +01:00
int x_texcoord = wall_x_texcoord(x, y, tex_walls);
std::vector<uint32_t> column = tex_walls.get_scaled_column(texid, x_texcoord, column_height);
int pix_x = i + fb.w/2; // we are drawing at the right half of the screen, thus +fb.w/2
for (size_t j=0; j<column_height; j++) { // copy the texture column to the framebuffer
int pix_y = j + fb.h/2 - column_height/2;
if (pix_y>=0 && pix_y<(int)fb.h) {
fb.set_pixel(pix_x, pix_y, column[j]);
}
}
break;
} // ray marching loop
} // field of view ray sweeping
2019-02-09 21:38:13 +01:00
2019-02-09 23:24:22 +01:00
draw_map(fb, sprites, tex_walls, map, cell_w, cell_h);
2019-02-09 21:54:56 +01:00
for (size_t i=0; i<sprites.size(); i++) { // draw the sprites
2019-02-09 23:24:22 +01:00
draw_sprite(fb, sprites[i], depth_buffer, player, tex_monst);
2019-02-09 21:38:13 +01:00
}
2019-02-09 17:05:45 +01:00
}
2019-02-09 16:47:05 +01:00