diff --git a/doc/011.png b/doc/011.png new file mode 100644 index 0000000..c864d2c Binary files /dev/null and b/doc/011.png differ diff --git a/doc/012.gif b/doc/012.gif new file mode 100644 index 0000000..88a0dfe Binary files /dev/null and b/doc/012.gif differ diff --git a/doc/012.mkv b/doc/012.mkv new file mode 100644 index 0000000..2a297ed Binary files /dev/null and b/doc/012.mkv differ diff --git a/framebuffer.cpp b/framebuffer.cpp new file mode 100644 index 0000000..860679d --- /dev/null +++ b/framebuffer.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include "framebuffer.h" +#include "utils.h" + +void FrameBuffer::set_pixel(const size_t x, const size_t y, const uint32_t color) { + assert(img.size()==w*h && x(w*h, color); +} + + diff --git a/framebuffer.h b/framebuffer.h new file mode 100644 index 0000000..6acf5fc --- /dev/null +++ b/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef FRAMEBUFFER_H +#define FRAMEBUFFER_H + +#include +#include + +struct FrameBuffer { + size_t w, h; // image dimensions + std::vector img; // storage container + + void clear(const uint32_t color); + void set_pixel(const size_t x, const size_t y, const uint32_t color); + void draw_rectangle(const size_t x, const size_t y, const size_t w, const size_t h, const uint32_t color); +}; + +#endif // FRAMEBUFFER_H + diff --git a/map.cpp b/map.cpp new file mode 100644 index 0000000..4d71a98 --- /dev/null +++ b/map.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "map.h" + +static const char map[] = "0000222222220000"\ + "1 0"\ + "1 11111 0"\ + "1 0 0"\ + "0 0 1110000"\ + "0 3 0"\ + "0 10000 0"\ + "0 3 11100 0"\ + "5 4 0 0"\ + "5 4 1 00000"\ + "0 1 0"\ + "2 1 0"\ + "0 0 0"\ + "0 0000000 0"\ + "0 0"\ + "0002222222200000"; + +Map::Map() : w(16), h(16) { + assert(sizeof(map) == w*h+1); // +1 for the null terminated string +} + +int Map::get(const size_t i, const size_t j) { + assert(i + +struct Map { + size_t w, h; // overall map dimensions + Map(); + int get(const size_t i, const size_t j); + bool is_empty(const size_t i, const size_t j); +}; + +#endif // MAP_H + diff --git a/player.h b/player.h new file mode 100644 index 0000000..cdf8870 --- /dev/null +++ b/player.h @@ -0,0 +1,11 @@ +#ifndef PLAYER_H +#define PLAYER_H + +struct Player { + float x, y; // position + float a; // view direction + float fov; // field of view +}; + +#endif // PLAYER_H + diff --git a/textures.cpp b/textures.cpp new file mode 100644 index 0000000..37504c8 --- /dev/null +++ b/textures.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#include "utils.h" +#include "textures.h" + +Texture::Texture(const std::string filename) : img_w(0), img_h(0), count(0), size(0), img() { + int nchannels = -1, w, h; + unsigned char *pixmap = stbi_load(filename.c_str(), &w, &h, &nchannels, 0); + if (!pixmap) { + std::cerr << "Error: can not load the textures" << std::endl; + return; + } + + if (4!=nchannels) { + std::cerr << "Error: the texture must be a 32 bit image" << std::endl; + stbi_image_free(pixmap); + return; + } + + if (w!=h*int(w/h)) { + std::cerr << "Error: the texture file must contain N square textures packed horizontally" << std::endl; + stbi_image_free(pixmap); + return; + } + count = w/h; + size = w/count; + img_w = w; + img_h = h; + + img = std::vector(w*h); + for (int j=0; j Texture::get_scaled_column(const size_t texture_id, const size_t tex_coord, const size_t column_height) { + assert(tex_coord column(column_height); + for (size_t y=0; y img; // textures storage container + + Texture(const std::string filename); + uint32_t &get(const size_t i, const size_t j, const size_t idx); // get the pixel (i,j) from the textrue idx + std::vector get_scaled_column(const size_t texture_id, const size_t tex_coord, const size_t column_height); // retrieve one column (tex_coord) from the texture texture_id and scale it to the destination size +}; + +#endif // TEXTURES_H + diff --git a/tinyraycaster.cpp b/tinyraycaster.cpp index 8ed98a2..d5ff79f 100644 --- a/tinyraycaster.cpp +++ b/tinyraycaster.cpp @@ -1,185 +1,91 @@ #define _USE_MATH_DEFINES #include #include -#include #include #include #include -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" +#include +#include -uint32_t pack_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a=255) { - return (a<<24) + (b<<16) + (g<<8) + r; +#include "map.h" +#include "utils.h" +#include "player.h" +#include "framebuffer.h" +#include "textures.h" + +int wall_x_texcoord(const float x, const float y, Texture &tex_walls) { + float hitx = x - floor(x+.5); // hitx and hity contain (signed) fractional parts of x and y, + float hity = y - floor(y+.5); // they vary between -0.5 and +0.5, and one of them is supposed to be very close to 0 + int tex = hitx*tex_walls.size; + if (std::abs(hity)>std::abs(hitx)) // we need to determine whether we hit a "vertical" or a "horizontal" wall (w.r.t the map) + tex = hity*tex_walls.size; + 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; } -void unpack_color(const uint32_t &color, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a) { - r = (color >> 0) & 255; - g = (color >> 8) & 255; - b = (color >> 16) & 255; - a = (color >> 24) & 255; -} +void render(FrameBuffer &fb, Map &map, Player &player, Texture &tex_walls) { + fb.clear(pack_color(255, 255, 255)); // clear the screen -void drop_ppm_image(const std::string filename, const std::vector &image, const size_t w, const size_t h) { - assert(image.size() == w*h); - std::ofstream ofs(filename); - ofs << "P6\n" << w << " " << h << "\n255\n"; - for (size_t i = 0; i < h*w; ++i) { - uint8_t r, g, b, a; - unpack_color(image[i], r, g, b, a); - ofs << static_cast(r) << static_cast(g) << static_cast(b); - } - ofs.close(); -} - -void draw_rectangle(std::vector &img, const size_t img_w, const size_t img_h, const size_t x, const size_t y, const size_t w, const size_t h, const uint32_t color) { - assert(img.size()==img_w*img_h); - for (size_t i=0; i=img_w || cy>=img_h) continue; // no need to check for negative values (unsigned variables) - img[cx + cy*img_w] = color; + const size_t rect_w = fb.w/(map.w*2); // size of one map cell on the screen + const size_t rect_h = fb.h/map.h; + for (size_t j=0; j &texture, size_t &text_size, size_t &text_cnt) { - int nchannels = -1, w, h; - unsigned char *pixmap = stbi_load(filename.c_str(), &w, &h, &nchannels, 0); - if (!pixmap) { - std::cerr << "Error: can not load the textures" << std::endl; - return false; - } + for (size_t i=0; i(w*h); - for (int j=0; j texture_column(const std::vector &img, const size_t texsize, const size_t ntextures, const size_t texid, const size_t texcoord, const size_t column_height) { - const size_t img_w = texsize*ntextures; - const size_t img_h = texsize; - assert(img.size()==img_w*img_h && texcoord column(column_height); - for (size_t y=0; y 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=0 && pix_y<(int)fb.h) { + fb.set_pixel(pix_x, pix_y, column[j]); + } + } + break; + } // ray marching loop + } // field of view ray sweeping } int main() { - const size_t win_w = 1024; // image width - const size_t win_h = 512; // image height - std::vector framebuffer(win_w*win_h, pack_color(255, 255, 255)); // the image itself, initialized to white - - const size_t map_w = 16; // map width - const size_t map_h = 16; // map height - const char map[] = "0000222222220000"\ - "1 0"\ - "1 11111 0"\ - "1 0 0"\ - "0 0 1110000"\ - "0 3 0"\ - "0 10000 0"\ - "0 3 11100 0"\ - "5 4 0 0"\ - "5 4 1 00000"\ - "0 1 0"\ - "2 1 0"\ - "0 0 0"\ - "0 0000000 0"\ - "0 0"\ - "0002222222200000"; // our game map - assert(sizeof(map) == map_w*map_h+1); // +1 for the null terminated string - float player_x = 3.456; // player x position - float player_y = 2.345; // player y position - float player_a = 1.523; // player view direction - const float fov = M_PI/3.; // field of view - - std::vector walltext; // textures for the walls - size_t walltext_size; // texture dimensions (it is a square) - size_t walltext_cnt; // number of different textures in the image - if (!load_texture("../walltext.png", walltext, walltext_size, walltext_cnt)) { + FrameBuffer fb{1024, 512, std::vector(1024*512, pack_color(255, 255, 255))}; + Player player{3.456, 2.345, 1.523, M_PI/3.}; + Map map; + Texture tex_walls("../walltext.png"); + if (!tex_walls.count) { std::cerr << "Failed to load wall textures" << std::endl; return -1; } - const size_t rect_w = win_w/(map_w*2); - const size_t rect_h = win_h/map_h; - for (size_t j=0; jstd::abs(hitx)) { // we need to determine whether we hit a "vertical" or a "horizontal" wall (w.r.t the map) - x_texcoord = hity*walltext_size; - } - if (x_texcoord<0) x_texcoord += walltext_size; // do not forget x_texcoord can be negative, fix that - assert(x_texcoord>=0 && x_texcoord<(int)walltext_size); - - std::vector column = texture_column(walltext, walltext_size, walltext_cnt, texid, x_texcoord, column_height); - pix_x = win_w/2+i; - for (size_t j=0; j=(int)win_h) continue; - framebuffer[pix_x + pix_y*win_w] = column[j]; - } - break; - } - } - } - - drop_ppm_image("./out.ppm", framebuffer, win_w, win_h); - return 0; } diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..2d49fb9 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +#include "utils.h" + +uint32_t pack_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) { + return (a<<24) + (b<<16) + (g<<8) + r; +} + +void unpack_color(const uint32_t &color, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a) { + r = (color >> 0) & 255; + g = (color >> 8) & 255; + b = (color >> 16) & 255; + a = (color >> 24) & 255; +} + +void drop_ppm_image(const std::string filename, const std::vector &image, const size_t w, const size_t h) { + assert(image.size() == w*h); + std::ofstream ofs(filename); + ofs << "P6\n" << w << " " << h << "\n255\n"; + for (size_t i = 0; i < h*w; ++i) { + uint8_t r, g, b, a; + unpack_color(image[i], r, g, b, a); + ofs << static_cast(r) << static_cast(g) << static_cast(b); + } + ofs.close(); +} + diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..6386189 --- /dev/null +++ b/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +uint32_t pack_color(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a=255); +void unpack_color(const uint32_t &color, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a); +void drop_ppm_image(const std::string filename, const std::vector &image, const size_t w, const size_t h); + +#endif // UTILS_H +