-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.cpp
346 lines (304 loc) · 13.4 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#include <iostream>
#include <fstream>
#include <sstream>
#include <chrono>
#include <thread>
#include <vector>
#include <cstdint>
#include <cassert>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include <SDL2/SDL.h>
uint32_t pack_color(uint32_t r, uint32_t g, uint32_t b, uint32_t a = 255) {
return r + (g << 8) + (b << 16) + (a << 24);
}
void unpack_color(uint32_t c, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& a) {
r = uint8_t(c & 255);
g = uint8_t((c >> 8) & 255);
b = uint8_t((c >> 16) & 255);
a = uint8_t((c >> 24) & 255);
}
void draw_tile(std::vector<uint32_t>& img, int w, int h, int tx, int ty, int tw, int th, uint32_t color) {
for (int i = tx; i < tx+tw; ++i) {
for (int j = ty; j < ty+th; ++j) {
if (i < 0 || i >= w || j < 0 || j >= h) continue;
img[i+j*w] = color;
}
}
}
//The class represent a texture altas which contains a collection of images (texture). This class is responsible
//for loading atlas from file using stb_image library, figuring out the amount of textures the atlas has and the size of
//each texture etc. This class also provided an API that allows one to extract pixel color of specific texture in the atlas
class TextureAtlas {
//w,h,c correspond to width, height and channel count of the input image file respectively.
//rows, cols are user provided parameters used to specify how many rows and columns
//the input image has, those are used to calculate the quantity and size of textures.
//assume all texture in a texture atlas is the same size.
int w,h,c,rows,cols;
//texture quantity
int tex_cnt;
//texture size
int tex_w, tex_h;
//storing input texture altas image pixel data in rgba
std::vector<uint32_t> data;
//load image from file and initialze all data members.
//the input image must have 4 channels (r,g,b,a). put
//asserts to check all neccesary prerequisits.
void load_img(const char* fname, int rows, int cols) {
uint8_t* img_data = stbi_load(fname, &w, &h, &c, 4);
assert(img_data != nullptr && "Failed to load image");
assert(c == 4 && "Input image must have 4 channels (RGBA)");
assert(rows > 0 && cols > 0 && "Rows and columns must be positive");
assert(w % cols == 0 && h % rows == 0 && "Image dimensions must be divisible by rows and columns");
this->rows = rows;
this->cols = cols;
tex_cnt = rows * cols;
tex_w = w / cols;
tex_h = h / rows;
data.resize(w * h);
for (int i = 0; i < w * h; ++i) {
uint8_t r = img_data[i * 4];
uint8_t g = img_data[i * 4 + 1];
uint8_t b = img_data[i * 4 + 2];
uint8_t a = img_data[i * 4 + 3];
data[i] = pack_color(r,g,b,a);
}
stbi_image_free(img_data);
}
public:
TextureAtlas(const char* filename, int rows, int cols) {
load_img(filename, rows, cols);
}
size_t texture_count() {
return tex_cnt;
}
size_t texture_width() {
return tex_w;
}
size_t texture_height() {
return tex_h;
}
//return texture color by the reference parameter 'color' indexed by
//row(r) and column(c). the floating point numbers x, y are in range [0-1]
//which indicates the coordinates inside the texture.
uint32_t texture_color(int r, int c, float x, float y) {
assert(r >= 0 && r < rows && "Row index out of range");
assert(c >= 0 && c < cols && "Column index out of range");
assert(x >= 0 && x <= 1 && "x must be in range [0, 1]");
assert(y >= 0 && y <= 1 && "y must be in range [0, 1]");
int tex_x = static_cast<int>(x * tex_w);
int tex_y = static_cast<int>(y * tex_h);
int index = (r * tex_h + tex_y) * w + c * tex_w + tex_x;
return data[index];
}
};
struct Pawn {
float x, y;
TextureAtlas *texture;
int tex_id;
};
void draw_sprite(std::vector<uint32_t>& img, int w, int h, std::vector<float>&depth, float dist, int tx, int ty, int tw, int th, TextureAtlas& tex, int tex_id) {
auto left = std::max(w/2, std::min(tx, w));
auto right = std::max(w/2, std::min(w, tx+tw));
auto bottom = std::max(0, std::min(ty, h));
auto top = std::max(0, std::min(ty+th, h));
for (int i = left; i < right; ++i) {
for (int j = bottom; j < top; ++j) {
if (depth[i-w/2] < dist) continue;//w/2 because the 3D view is on the right part
depth[i-w/2] = dist;
float sample_x = (i-tx)/(float)tw;
float sample_y = (j-ty)/(float)th;
uint32_t color = tex.texture_color(0, tex_id, sample_x, sample_y);
if (color & 0xFF000000) img[i+j*w] = color;
}
}
}
void draw_foes(
std::vector<uint32_t>&fb, int w, int h,
std::vector<float>& depth,
std::vector<Pawn>& foes,
float player_x, float player_y,
float fov,
float player_a) {
for (auto& foe : foes) {
//draw foes on mini map
auto mx = (foe.x / 16.0f) * (w/2.0f);
auto my = (foe.y / 16.0f) * h;
draw_tile(fb, w, h, int(mx-2), int(my-2), 4, 4, pack_color(255,255,255));
//draw foe on 3D view
float foe_a = atan2(foe.y - player_y, foe.x - player_x);
//the angle between player_a which is the center of the view and the foe
float a = foe_a - player_a;
while (a > M_PI) a-= 2*M_PI;
while (a < -M_PI) a+= 2*M_PI;
//if player_a map to the center of 3D view
//then a is mapping to center + (a/fov)*win_w
float offset = w/4 + a*(w/2)/fov;//offset from the center of the 3D view
float sa = w/2 + offset;
//sa is the center screen coordinate of foe, to get the top-left of foe's texture:
float dist = sqrt(powf(foe.x-player_x, 2)+powf(foe.y-player_y, 2));
auto sw = std::min((float)h, h/dist);
auto sh = std::min((float)h, h/dist);;
auto sx = sa - sw/2.0f;
auto sy = h/2 - sh/2.0f;
if (sx+sw < w/2 || sx > w) continue;//outside of view cone
draw_sprite(fb, w, h, depth, dist, int(sx), int(sy), sw, sh, *foe.texture, foe.tex_id);
}
}
int main() {
const size_t win_w = 512*2;
const size_t win_h = 512;
std::vector<uint32_t> framebuffer(win_w*win_h, pack_color(60,60,60));
std::vector<float> depth(win_w/2, 0.0f);
const int map_w = 16;
const int map_h = 16;
const char map[] = "0000222322220000"\
"1 0"\
"1 11111 0"\
"1 0 0"\
"0 0 1110000"\
"5 3 0"\
"5 10000 0"\
"5 4 11100 0"\
"5 3 0 0"\
"0 4 1 00000"\
"0 1 4"\
"2 1 4"\
"0 0 4"\
"0 4000000 0"\
"0 4"\
"0002222222200000";
assert(sizeof(map) == map_w*map_h+1);
//randomly picking n colors;
srand(123456);
std::vector<uint32_t> ncolors(10, 0);
for (int i = 0; i < ncolors.size(); i++) {
ncolors[i] = pack_color(rand()%255, rand()%255, rand()%255);
}
//load wall texture
TextureAtlas wall("../walltext.png", 1, 6);
//monster texture
TextureAtlas monster("../monsters.png", 1, 4);
int tile_w = win_w/(map_w*2);//the width of a tile
int tile_h = win_h/map_h;//the height of a tile
float player_x = 3.456; // player x position in map space
float player_y = 2.345; // player y position in map space
float player_a = M_PI / 2.05f; // the angle between player direction and positive x-axis
float fov = M_PI / 3.0f;
std::vector<Pawn> foes = {
{5, 2, &monster, 2},
{1.834, 8.765, &monster, 0},
{2.834, 6.765, &monster, 3},
{5.323, 5.365, &monster, 1},
{4.123, 10.265, &monster, 1}};
if (SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
return -1;
}
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
if (SDL_CreateWindowAndRenderer(win_w, win_h, SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS, &window, &renderer)) {
std::cerr << "Failed to create window and renderer: " << SDL_GetError() << std::endl;
return -1;
}
SDL_Texture *framebuffer_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, win_w, win_h);
if (!framebuffer_texture) {
std::cerr << "Failed to create SDL texture: " << SDL_GetError() << std::endl;
return -1;
}
using namespace std::chrono_literals;
bool game_will_stop = false;
float player_turn = 0.0f;
float player_walk = 0.0f;
auto last_time = std::chrono::high_resolution_clock::now();
while (!game_will_stop) {
auto curr_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> elapsed_time = curr_time - last_time;
if (elapsed_time.count() < 33) {
std::this_thread::sleep_for(33ms - elapsed_time);
continue;
}
last_time = curr_time;
{ // poll events and update player's state (walk/turn flags); TODO: move this block to a more appropriate place
SDL_Event event;
if (SDL_PollEvent(&event)) {
if (SDL_QUIT==event.type || (SDL_KEYDOWN==event.type && SDLK_ESCAPE==event.key.keysym.sym)) {game_will_stop = true;};
if (SDL_KEYUP==event.type) {
if ('a'==event.key.keysym.sym || 'd'==event.key.keysym.sym) player_turn = 0;
if ('w'==event.key.keysym.sym || 's'==event.key.keysym.sym) player_walk = 0;
}
if (SDL_KEYDOWN==event.type) {
if ('a'==event.key.keysym.sym) player_turn = -1;
if ('d'==event.key.keysym.sym) player_turn = 1;
if ('w'==event.key.keysym.sym) player_walk = 1;
if ('s'==event.key.keysym.sym) player_walk = -1;
}
}
}
float dt = elapsed_time.count() / 1000.0f;
//update player position and facing
player_a += player_turn * dt * 2.0f;
while (player_a > M_PI) player_a -= 2*M_PI;
while (player_a < -M_PI) player_a += 2*M_PI;
player_x += player_walk * cosf(player_a) * dt * 1.5f;
player_y += player_walk * sinf(player_a) * dt * 1.5f;
#define map2win(X) int(X*win_w/((float)map_w*2.0f))
//render
for (int i = 0; i < map_w; i++) {
for (int j = 0; j < map_h; j++) {
int tile_x = i * tile_w;
int tile_y = j * tile_h;
if (map[i+j*map_w] != ' ') {
uint32_t c = ncolors[map[i+j*map_w]-'0'];
draw_tile(framebuffer, win_w, win_h, tile_x, tile_y, tile_w, tile_h, c);
}
//draw player
int px = map2win(player_x);//player x in window space
int py = map2win(player_y);//player y in window space
draw_tile(framebuffer, win_w, win_h, px-2, py-2, 4, 4, pack_color(255,0,0));
}
}
//cast rays between fov
for (int i = 0; i < 512; i++) {
//cast 512 rays across fov centered around player_a
float a = player_a - fov/2.0f + (i / 512.f) * fov;
for (float c = 0.01/*prevent divide by 0 */; c < 20.0; c+=0.01f) {
float cx = player_x + c * cos(a);
float cy = player_y + c * sin(a);
//draw rays on map view (left)
framebuffer[map2win(cx) + map2win(cy)*win_w] = pack_color(170,170,170);
//one ray generate one colum of 3D view (right)
if (map[int(cx)+int(cy)*map_w] != ' ') {
float dist = c*cos(a-player_a);
depth[i] = dist;
int l = std::min(2000, int(win_h/dist));//prevent the l goes extremly big
//check which kind of wall we are hitting
auto gx = cx - floor(cx);
auto gy = cy - floor(cy);
bool vertical = int(cx + 0.01*cos(M_PI-a)) != int(cx);
float tex_x = vertical ? gy: gx;
for (int j = 0; j < l; j++) {
if ((win_h/2 - l/2 + j) >= win_h) continue;
uint32_t c = wall.texture_color(0, map[int(cx)+int(cy)*map_w]-'0', tex_x, j/(float)l);
framebuffer[win_w/2 + i + (win_h/2 - l/2 + j)*win_w] = c;
}
break;
}
}
}
draw_foes(framebuffer, win_w, win_h, depth, foes, player_x, player_y, fov, player_a);
SDL_UpdateTexture(framebuffer_texture, NULL, reinterpret_cast<void*>(framebuffer.data()), win_w*4);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, framebuffer_texture, NULL, NULL);
SDL_RenderPresent(renderer);
std::fill(framebuffer.begin(), framebuffer.end(), pack_color(60,60,60));
std::fill(depth.begin(), depth.end(), 10000.0f);
}
SDL_DestroyTexture(framebuffer_texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}