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 ;
2019-02-10 00:23:26 +01:00
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 ;
2019-02-10 01:09:04 +01:00
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