#include // must come before glfw3 #include #include #include #include "player.h" // TODO: move to implicit map definition with level editor in future #define MAP_HEIGHT 20 #define MAP_WIDTH 10 int world_map[MAP_HEIGHT][MAP_WIDTH] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 2, 2, 2, 0, 0, 0, 0, 1}, {1, 0, 2, 2, 2, 0, 0, 0, 0, 1}, {1, 0, 2, 2, 2, 0, 0, 0, 0, 1}, {1, 0, 2, 2, 2, 0, 0, 0, 0, 1}, {1, 0, 2, 2, 2, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 4, 4, 0, 1}, {1, 0, 3, 3, 3, 0, 4, 4, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, }; typedef struct { unsigned char r, g, b; } ColorRGB; // honest- this function can from chatgpt- cross-reference this with other raycasting guides void raycast(Player *player, GLubyte *pixels, int width, int height) { int screen_width = width; int screen_height = height; for (int x = 0; x < screen_width; ++x) { // Calculate ray position and direction double camera_x = 2.0 * x / (double)screen_width - 1; // x-coordinate in camera space double ray_dir_x = player->dir_x + player->plane_x * camera_x; double ray_dir_y = player->dir_y + player->plane_y * camera_x; // Which box of the map we're in int map_x = (int)player->pos_x; int map_y = (int)player->pos_y; // Length of ray from one x or y-side to next x or y-side double delta_dist_x = fabs(1 / ray_dir_x); double delta_dist_y = fabs(1 / ray_dir_y); // Length of ray from current position to next x or y-side double side_dist_x; double side_dist_y; // Direction to step in x or y-direction (either +1 or -1) int step_x; int step_y; // Was a wall hit? int hit = 0; // Was the wall hit a wall facing north, east-west, or south? int side; if (ray_dir_x < 0) { step_x = -1; side_dist_x = (player->pos_x - map_x) * delta_dist_x; } else { step_x = 1; side_dist_x = (map_x + 1.0 - player->pos_x) * delta_dist_x; } if (ray_dir_y < 0) { step_y = -1; side_dist_y = (player->pos_y - map_y) * delta_dist_y; } else { step_y = 1; side_dist_y = (map_y + 1.0 - player->pos_y) * delta_dist_y; } // Perform DDA while (hit == 0) { // Jump to next map square if (side_dist_x < side_dist_y) { side_dist_x += delta_dist_x; map_x += step_x; side = (ray_dir_x > 0) ? 0 : 2; } else { side_dist_y += delta_dist_y; map_y += step_y; side = 1; } // Check if ray has hit a wall if (world_map[map_x][map_y] > 0) { hit = 1; } } // calculate distance projected on camera direction double perp_wall_dist; if (side == 0 || side == 2) { perp_wall_dist = (map_x - player->pos_x + (1 - step_x) / 2) / ray_dir_x; } else { perp_wall_dist = (map_y - player->pos_y + (1 - step_y) / 2) / ray_dir_y; } // fog effect float fog_distance = 15.0f; float scale_factor = 1.0f - perp_wall_dist / fog_distance; if (scale_factor < 0.0f) { scale_factor = 0.0f; } // calculate height of line on screen int line_height = (int)(screen_height / perp_wall_dist); //int draw_start = -line_height / 2 + screen_height / 2; //if (draw_start < 0) { draw_start = 0; } //int draw_end = line_height / 2 + screen_height / 2; //if (draw_end >= screen_height) { draw_end = screen_height - 1; } int pitch = screen_height / 2 * player->look_angle; int start_y = -line_height / 2 + screen_height / 2 + pitch; if (start_y < 0) { start_y = 0; } int end_y = line_height / 2 + screen_height / 2 + pitch; if (end_y >= screen_height) { end_y = screen_height - 1; } // colors :) ColorRGB color; if (world_map[map_x][map_y] == 1) color = (ColorRGB){.r = 255, .g = 255, .b = 255}; else if (world_map[map_x][map_y] == 2) color = (ColorRGB){.r = 255, .g = 0, .b = 0}; else if (world_map[map_x][map_y] == 3) color = (ColorRGB){.r = 0, .g = 255, .b = 0}; else if (world_map[map_x][map_y] == 4) color = (ColorRGB){.r = 0, .g = 0, .b = 255}; else color = (ColorRGB){.r = 0, .g = 0, .b = 0}; if (side == 1) { color.r /= 2; color.g /= 2; color.b /= 2; } else if (side == 2) { color.r /= 4; color.g /= 4; color.b /= 4; } // scale brightness in accordance with distance (for fog) color.r *= scale_factor; color.g *= scale_factor; color.b *= scale_factor; int index; for (int y = 0; y < screen_height; y++) { index = y * screen_width * 3 + x * 3; if (y >= start_y && y <= end_y) { // draw something pixels[index] = color.r; pixels[index+1] = color.g; pixels[index+2] = color.b; } else { // fill blank space pixels[index] = 0; pixels[index+1] = 0; pixels[index+2] = 0; } } } } int main(int argc, char *argv[]) { GLFWwindow* window; double mouse_x; double mouse_y; // init player Player player; player.pos_y = 2.0f; player.pos_x = 9.0f; player.dir_x = 1.0f; player.dir_y = 0.0f; player.plane_x = 0.20f; // FOV related player.plane_y = 0.90f; // FOV related player.look_angle = 0.0f; player.move_speed = 0.035f; player.sensitivity = 0.001f; // initiate glfw library if (!glfwInit()){ printf("Failed to initiate GLFW.\n"); return -1; } // create glfw window GLFWmonitor* primary = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(primary); //window = glfwCreateWindow(800, 600, "raycaster", NULL, NULL); window = glfwCreateWindow(mode->width, mode->height, "raycaster", primary, NULL); if (!window){ printf("Failed to create GLFW window.\n"); glfwTerminate(); return -1; } // make window current glfwMakeContextCurrent(window); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // update mouse position // glfwGetCursorPos(window, &mouse_x, &mouse_y); mouse_x -= mode->width / 2; mouse_y -= mode->height / 2; // setup orthographic projection camera glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, mode->width, mode->height, 0.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); // TODO: move timers into a header double last_time = glfwGetTime(); int nb_frames = 0; // TODO: render FPS with freetype or something as opposed to updating title char title[200]; // setup pbo for optimized performance if (glewInit() != GLEW_OK) { printf("Failed to initiate GLEW.\n"); return -1; } GLuint pbo = 0; glGenBuffers(1, &pbo); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); glBufferData(GL_PIXEL_UNPACK_BUFFER, mode->width * mode->height * 3, 0, GL_STREAM_DRAW); int index = 0; glViewport(0, 0, mode->width, mode->height); while (!glfwWindowShouldClose(window)) { // Stop loop if window isn't focused if(!glfwGetWindowAttrib(window, GLFW_FOCUSED)){ continue; } // timing section double current_time = glfwGetTime(); nb_frames++; if (current_time - last_time >= 1.0) { sprintf(title, "FPS: %d", nb_frames); glfwSetWindowTitle(window, title); nb_frames = 0; last_time += 1.0; } // clear window glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); // main raycast function float prev_position[2] = {player.pos_x, player.pos_y}; move_player(&player, window, &mouse_x, &mouse_y, mode->width, mode->height); if (world_map[(int)player.pos_x][(int)player.pos_y] != 0) { player.pos_x = prev_position[0]; player.pos_y = prev_position[1]; } GLubyte* pixels = (GLubyte*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); raycast(&player, pixels, mode->width, mode->height); // pbo glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ROW_LENGTH, mode->width); glWindowPos2i(0, 0); glDrawPixels(mode->width, mode->height, GL_RGB, GL_UNSIGNED_BYTE, 0); glfwSwapBuffers(window); glfwPollEvents(); } printf("Terminating GLFW...\n"); glfwTerminate(); printf("Clearing buffer...\n"); glDeleteBuffers(1, &pbo); return 0; }