#include #include #include // TODO: move to implicit map definition with level editor in future #define MAP_HEIGHT 20 #define MAP_WIDTH 20 int world_map[MAP_HEIGHT][MAP_WIDTH] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 1}, {1, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, }; // TODO: move player code to dedicated file after complication demands it // TODO: implement y-shearing to allow players to look vertically typedef struct { float pos_x, pos_y; float dir_x, dir_y; float plane_x, plane_y; float move_speed; float rot_speed; } Player; 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) { int screen_width = 800; int screen_height = 600; 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 = 20.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; } // colors :) // TODO: currently, this effects both inside and outside faces // which creates some awkward coloring issues with the outer walls. // this may not be an issue in the final release, depending how walls // are handled, but worth noting now. 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; // TODO: recommended to put this in its own function or something at some point, // as it deals with library calls outside of this function's scope glBegin(GL_LINES); glColor3ub(color.r, color.g, color.b); glVertex2i(x, draw_start); glVertex2i(x, draw_end); glEnd(); } } int main(int argc, char *argv[]) { GLFWwindow* window; // init player Player player; player.pos_y = 2.0f; player.pos_x = 2.0f; player.dir_x = 1.0f; // facing right player.dir_y = 0.0f; // facing right player.plane_x = 0.0f; // FOV related player.plane_y = 0.66f; // FOV related player.move_speed = 0.05f; player.rot_speed = 0.1f; // initiate glfw library if (!glfwInit()){ printf("Failed to initiate GLFW.\n"); return -1; } // create glfw window window = glfwCreateWindow(800, 600, "raycaster", NULL, NULL); if (!window){ printf("Failed to create GLFW window.\n"); glfwTerminate(); return -1; } // make window current glfwMakeContextCurrent(window); // setup orthographic projection camera glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 800, 600, 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]; while (!glfwWindowShouldClose(window)) { // 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 raycast(&player); // movement and camera rotation // TODO: eventually translate movement from a header if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { player.pos_x += player.dir_x * player.move_speed; player.pos_y += player.dir_y * player.move_speed; } if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { player.pos_x -= player.dir_x * player.move_speed; player.pos_y -= player.dir_y * player.move_speed; } if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { // this changes where we're casting our rays double old_dir_x = player.dir_x; player.dir_x = player.dir_x * cos(-player.rot_speed) - player.dir_y * sin(-player.rot_speed); player.dir_y = old_dir_x * sin(-player.rot_speed) + player.dir_y * cos(-player.rot_speed); // this updates perspective double old_plane_x = player.plane_x; player.plane_x = player.plane_x * cos(-player.rot_speed) - player.plane_y * sin(-player.rot_speed); player.plane_y = old_plane_x * sin(-player.rot_speed) + player.plane_y * cos(-player.rot_speed); } if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { double old_dir_x = player.dir_x; player.dir_x = player.dir_x * cos(player.rot_speed) - player.dir_y * sin(player.rot_speed); player.dir_y = old_dir_x * sin(player.rot_speed) + player.dir_y * cos(player.rot_speed); double old_plane_x = player.plane_x; player.plane_x = player.plane_x * cos(player.rot_speed) - player.plane_y * sin(player.rot_speed); player.plane_y = old_plane_x * sin(player.rot_speed) + player.plane_y * cos(player.rot_speed); } glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); printf("Terminating program...\n"); return 0; }