diff options
Diffstat (limited to 'simple-raycaster/src/main.c')
-rw-r--r-- | simple-raycaster/src/main.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/simple-raycaster/src/main.c b/simple-raycaster/src/main.c new file mode 100644 index 0000000..cbe13d8 --- /dev/null +++ b/simple-raycaster/src/main.c @@ -0,0 +1,285 @@ +#include <GL/glew.h> // must come before glfw3 +#include <GLFW/glfw3.h> +#include <stdio.h> +#include <math.h> + +#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; + +} |