From c356e96932b5d1a23bf41914934cf83e8b535994 Mon Sep 17 00:00:00 2001 From: Schark Date: Wed, 6 Mar 2024 02:00:59 -0800 Subject: Working barebones heightmap renderer --- .gitignore | 4 + CMakeLists.txt | 14 -- README.md | 15 -- height-map-display/CMakeLists.txt | 14 ++ height-map-display/README.md | 15 ++ height-map-display/height-map-display | Bin 0 -> 17512 bytes height-map-display/src/main.c | 185 ++++++++++++++++++++++ simple-raycaster/CMakeLists.txt | 14 ++ simple-raycaster/README.md | 15 ++ simple-raycaster/src/main.c | 285 ++++++++++++++++++++++++++++++++++ simple-raycaster/src/player.c | 44 ++++++ simple-raycaster/src/player.h | 15 ++ src/main.c | 285 ---------------------------------- src/player.c | 44 ------ src/player.h | 15 -- 15 files changed, 591 insertions(+), 373 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 README.md create mode 100644 height-map-display/CMakeLists.txt create mode 100644 height-map-display/README.md create mode 100755 height-map-display/height-map-display create mode 100644 height-map-display/src/main.c create mode 100644 simple-raycaster/CMakeLists.txt create mode 100644 simple-raycaster/README.md create mode 100644 simple-raycaster/src/main.c create mode 100644 simple-raycaster/src/player.c create mode 100644 simple-raycaster/src/player.h delete mode 100644 src/main.c delete mode 100644 src/player.c delete mode 100644 src/player.h diff --git a/.gitignore b/.gitignore index a007fea..6730e7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ build/* +*/CMakeFiles/* +CMakeCache.txt +cmake_install.cmake +Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 11c3bef..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(raycaster C) - -set(CMAKE_C_STANDARD 99) - -find_package(OpenGL REQUIRED) -find_package(GLEW REQUIRED) -find_package(glfw3 REQUIRED) -find_package(Freetype REQUIRED) - -file(GLOB SOURCES "src/*.c") - -add_executable(raycaster ${SOURCES}) -target_link_libraries(raycaster ${OPENGL_gl_LIBRARY} ${OPENGL_INCLUDE_DIRS} ${FREETYPE_LIBARIES} ${GLEW_LIBRARIES} glfw m) diff --git a/README.md b/README.md deleted file mode 100644 index 84a96cc..0000000 --- a/README.md +++ /dev/null @@ -1,15 +0,0 @@ -Current Todo: - - Actually update README - - Adjustable window size* - * Currently fullscreen, add options later - - Create bounds so player cannot enter undefined areas (outer walls, inner structures) - - Experiment with warp-wrapping, just for fun :) - - Move FPS counter from window title to rendered in-game (low priority) - - Eventually clean code (move appropriate code to different files, headers) - - Level editor - -Requirements: - - OpenGL - - GLFW - - GLEW - - Freetype (not being used as of right now) diff --git a/height-map-display/CMakeLists.txt b/height-map-display/CMakeLists.txt new file mode 100644 index 0000000..5861290 --- /dev/null +++ b/height-map-display/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) +project(raycaster C) + +set(CMAKE_C_STANDARD 99) + +find_package(OpenGL REQUIRED) +find_package(GLEW REQUIRED) +find_package(glfw3 REQUIRED) +find_package(Freetype REQUIRED) + +file(GLOB SOURCES "src/*.c") + +add_executable(height-map-display ${SOURCES}) +target_link_libraries(height-map-display ${OPENGL_gl_LIBRARY} ${OPENGL_INCLUDE_DIRS} ${FREETYPE_LIBARIES} ${GLEW_LIBRARIES} glfw m) diff --git a/height-map-display/README.md b/height-map-display/README.md new file mode 100644 index 0000000..84a96cc --- /dev/null +++ b/height-map-display/README.md @@ -0,0 +1,15 @@ +Current Todo: + - Actually update README + - Adjustable window size* + * Currently fullscreen, add options later + - Create bounds so player cannot enter undefined areas (outer walls, inner structures) + - Experiment with warp-wrapping, just for fun :) + - Move FPS counter from window title to rendered in-game (low priority) + - Eventually clean code (move appropriate code to different files, headers) + - Level editor + +Requirements: + - OpenGL + - GLFW + - GLEW + - Freetype (not being used as of right now) diff --git a/height-map-display/height-map-display b/height-map-display/height-map-display new file mode 100755 index 0000000..b67544a Binary files /dev/null and b/height-map-display/height-map-display differ diff --git a/height-map-display/src/main.c b/height-map-display/src/main.c new file mode 100644 index 0000000..5ad5c78 --- /dev/null +++ b/height-map-display/src/main.c @@ -0,0 +1,185 @@ +#include // must come before glfw3 +#include +#include +#include + +#define MAP_HEIGHT 10 +#define MAP_WIDTH 10 + +int height_map[MAP_WIDTH][MAP_HEIGHT] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 0}, + {0, 1, 3, 3, 3, 3, 3, 3, 1, 0}, + {0, 1, 3, 5, 5, 5, 5, 3, 1, 0}, + {0, 1, 3, 5, 8, 8, 5, 3, 1, 0}, + {0, 1, 3, 5, 8, 8, 5, 3, 1, 0}, + {0, 1, 3, 5, 5, 5, 5, 3, 1, 0}, + {0, 1, 3, 3, 3, 3, 3, 3, 1, 0}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +}; + +// TODO: move into dedicated camera struct +// Define the perspective projection matrix +void perspective(float fov, float aspect, float near, float far, float *mat) { + float f = 1.0f / tan(fov * 0.5f); + mat[0] = f / aspect; + mat[1] = 0.0f; + mat[2] = 0.0f; + mat[3] = 0.0f; + mat[4] = 0.0f; + mat[5] = f; + mat[6] = 0.0f; + mat[7] = 0.0f; + mat[8] = 0.0f; + mat[9] = 0.0f; + mat[10] = (far + near) / (near - far); + mat[11] = -1.0f; + mat[12] = 0.0f; + mat[13] = 0.0f; + mat[14] = (2.0f * far * near) / (near - far); + mat[15] = 0.0f; +} + +float camera_pos_x = 0.0f; +float camera_pos_y = 0.0f; +float camera_pos_z = 0.0f; +float camera_yaw = 0.0f; // rotation around y-axis +float camera_pitch = 0.0f; // rotation aroudn x-axis + +void renderHeightMap() { + float colors[3][3] = { + {0.0f, 1.0f, 0.0f}, // Green + {0.5f, 0.5f, 0.5f}, // Gray + {1.0f, 1.0f, 1.0f} // White + }; + + float scale_factor = 1.0f; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glTranslatef(-camera_pos_x, -camera_pos_y, -camera_pos_z); + glRotatef(-camera_pitch, 1.0f, 0.0f, 0.0f); + glRotatef(-camera_yaw, 0.0f, 1.0f, 0.0f); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-10.0, 10.0, -10.0, 10.0, -20.0, 20.0); + + for (int x = 0; x < MAP_WIDTH - 1; x++) { // Adjusted loop bounds + + glBegin(GL_TRIANGLE_STRIP); + + for (int y = 0; y < MAP_HEIGHT; y++) { + + float x0 = x; + float y0 = height_map[x][y] * scale_factor; + float z0 = y; + + float x1 = x + 1; // Adjusted x1 + float y1 = height_map[x+1][y] * scale_factor; // Adjusted indexing + float z1 = y; + + float* color = colors[0]; + if (y0 < 1.0f) { color = colors[1]; } + if (y0 < 0.5f) { color = colors[2]; } + glColor3fv(color); + + glVertex3f(x0, y0, z0); + glVertex3f(x1, y1, z1); + + } + glEnd(); + + } + + // draw grid + + glColor3f(0.0f, 0.0f, 0.0f); + glLineWidth(1.0f); + glBegin(GL_LINES); + + // Draw vertical grid lines + for (int x = 0; x <= MAP_WIDTH; x++) { + for (int y = 0; y < MAP_HEIGHT; y++) { + glVertex3f(x, height_map[x][y] * scale_factor, y); + glVertex3f(x, height_map[x][y + 1] * scale_factor, y + 1); + } + } + + // Draw horizontal grid lines + for (int y = 0; y <= MAP_HEIGHT; y++) { + for (int x = 0; x < MAP_WIDTH; x++) { + glVertex3f(x, height_map[x][y] * scale_factor, y); + glVertex3f(x + 1, height_map[x + 1][y] * scale_factor, y); + } + } + + glEnd(); + +} + +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { + + const float camera_speed = 0.5f; + + if (key == GLFW_KEY_W && action == GLFW_PRESS) { camera_pos_z -= camera_speed; } + else if (key == GLFW_KEY_S && action == GLFW_PRESS) { camera_pos_z += camera_speed; } + else if (key == GLFW_KEY_A && action == GLFW_PRESS) { camera_pos_x -= camera_speed; } + else if (key == GLFW_KEY_D && action == GLFW_PRESS) { camera_pos_x += camera_speed; } + else if (key == GLFW_KEY_Q && action == GLFW_PRESS) { camera_pitch -= camera_speed; } + else if (key == GLFW_KEY_E && action == GLFW_PRESS) { camera_pitch += camera_speed; } + else if (key == GLFW_KEY_Z && action == GLFW_PRESS) { camera_yaw -= camera_speed; } + else if (key == GLFW_KEY_C && action == GLFW_PRESS) { camera_yaw += camera_speed; } + +} + +int main(int argc, char *argv[]) { + GLFWwindow* window; + + // 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(mode->width, mode->height, "raycaster", primary, NULL); + if (!window){ + printf("Failed to create GLFW window.\n"); + glfwTerminate(); + return -1; + } + + // make window current + glfwMakeContextCurrent(window); + glewExperimental = GL_TRUE; + GLenum err = glewInit(); + if (err != GLEW_OK) { + printf("GLEW init error"); + return -1; + } + + // enable depth testing + glEnable(GL_DEPTH_TEST); + + glfwSetKeyCallback(window, key_callback); + + while (!glfwWindowShouldClose(window)) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderHeightMap(); + + glfwSwapBuffers(window); // Swap buffers to display the rendered image + + // Poll for and process events, wait for events if none are pending + glfwWaitEventsTimeout(0.01); // Add a slight delay to reduce CPU usage + } + + printf("Terminating GLFW...\n"); + glfwTerminate(); + return 0; +} diff --git a/simple-raycaster/CMakeLists.txt b/simple-raycaster/CMakeLists.txt new file mode 100644 index 0000000..11c3bef --- /dev/null +++ b/simple-raycaster/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) +project(raycaster C) + +set(CMAKE_C_STANDARD 99) + +find_package(OpenGL REQUIRED) +find_package(GLEW REQUIRED) +find_package(glfw3 REQUIRED) +find_package(Freetype REQUIRED) + +file(GLOB SOURCES "src/*.c") + +add_executable(raycaster ${SOURCES}) +target_link_libraries(raycaster ${OPENGL_gl_LIBRARY} ${OPENGL_INCLUDE_DIRS} ${FREETYPE_LIBARIES} ${GLEW_LIBRARIES} glfw m) diff --git a/simple-raycaster/README.md b/simple-raycaster/README.md new file mode 100644 index 0000000..84a96cc --- /dev/null +++ b/simple-raycaster/README.md @@ -0,0 +1,15 @@ +Current Todo: + - Actually update README + - Adjustable window size* + * Currently fullscreen, add options later + - Create bounds so player cannot enter undefined areas (outer walls, inner structures) + - Experiment with warp-wrapping, just for fun :) + - Move FPS counter from window title to rendered in-game (low priority) + - Eventually clean code (move appropriate code to different files, headers) + - Level editor + +Requirements: + - OpenGL + - GLFW + - GLEW + - Freetype (not being used as of right now) 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 // 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; + +} diff --git a/simple-raycaster/src/player.c b/simple-raycaster/src/player.c new file mode 100644 index 0000000..f2d9883 --- /dev/null +++ b/simple-raycaster/src/player.c @@ -0,0 +1,44 @@ +#include +#include + +#include "player.h" + +void move_player(Player *player, GLFWwindow* window, double* mouse_x, double* mouse_y, double width, double height) { + 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) { + player->pos_x += player->dir_y * player->move_speed; + player->pos_y -= player->dir_x * player->move_speed; + } + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { + player->pos_x -= player->dir_y * player->move_speed; + player->pos_y += player->dir_x * player->move_speed; + } + + // mouse controls (including y-shearing angle calculations + // DO NOT TOUCH, THIS TOOK A WHOLE NIGHT TO FIGURE OUT + double mouse_dx, mouse_dy; + double old_mouse_x, old_mouse_y; + old_mouse_x = *mouse_x; + old_mouse_y = *mouse_y; + glfwGetCursorPos(window, mouse_x, mouse_y); + mouse_dx = *mouse_x - old_mouse_x; + mouse_dy = *mouse_y - old_mouse_y; + double rot_speed = mouse_dx * player->sensitivity; + double old_dir_x = player->dir_x; + player->dir_x = player->dir_x * cos(rot_speed) - player->dir_y * sin(rot_speed); + player->dir_y = old_dir_x * sin(rot_speed) + player->dir_y * cos(rot_speed); + double old_plane_x = player->plane_x; + player->plane_x = player->plane_x * cos(rot_speed) - player->plane_y * sin(rot_speed); + player->plane_y = old_plane_x * sin(rot_speed) + player->plane_y * cos(rot_speed); + player->look_angle += mouse_dy * player->sensitivity; + if (player->look_angle > M_PI/4) { player->look_angle = M_PI/4; } + if (player->look_angle < -M_PI/4) { player->look_angle = -M_PI/4; } + +} diff --git a/simple-raycaster/src/player.h b/simple-raycaster/src/player.h new file mode 100644 index 0000000..2086c45 --- /dev/null +++ b/simple-raycaster/src/player.h @@ -0,0 +1,15 @@ +#ifndef PLAYER_H +#define PLAYER_H + +typedef struct Player { + float pos_x, pos_y; + float dir, dir_x, dir_y; + float plane_x, plane_y; + float move_speed; + float sensitivity; + float look_angle; +} Player; + +void move_player(Player *player, GLFWwindow* window, double* mouse_x, double* mouse_y, double width, double height); + +#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index cbe13d8..0000000 --- a/src/main.c +++ /dev/null @@ -1,285 +0,0 @@ -#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; - -} diff --git a/src/player.c b/src/player.c deleted file mode 100644 index f2d9883..0000000 --- a/src/player.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -#include "player.h" - -void move_player(Player *player, GLFWwindow* window, double* mouse_x, double* mouse_y, double width, double height) { - 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) { - player->pos_x += player->dir_y * player->move_speed; - player->pos_y -= player->dir_x * player->move_speed; - } - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { - player->pos_x -= player->dir_y * player->move_speed; - player->pos_y += player->dir_x * player->move_speed; - } - - // mouse controls (including y-shearing angle calculations - // DO NOT TOUCH, THIS TOOK A WHOLE NIGHT TO FIGURE OUT - double mouse_dx, mouse_dy; - double old_mouse_x, old_mouse_y; - old_mouse_x = *mouse_x; - old_mouse_y = *mouse_y; - glfwGetCursorPos(window, mouse_x, mouse_y); - mouse_dx = *mouse_x - old_mouse_x; - mouse_dy = *mouse_y - old_mouse_y; - double rot_speed = mouse_dx * player->sensitivity; - double old_dir_x = player->dir_x; - player->dir_x = player->dir_x * cos(rot_speed) - player->dir_y * sin(rot_speed); - player->dir_y = old_dir_x * sin(rot_speed) + player->dir_y * cos(rot_speed); - double old_plane_x = player->plane_x; - player->plane_x = player->plane_x * cos(rot_speed) - player->plane_y * sin(rot_speed); - player->plane_y = old_plane_x * sin(rot_speed) + player->plane_y * cos(rot_speed); - player->look_angle += mouse_dy * player->sensitivity; - if (player->look_angle > M_PI/4) { player->look_angle = M_PI/4; } - if (player->look_angle < -M_PI/4) { player->look_angle = -M_PI/4; } - -} diff --git a/src/player.h b/src/player.h deleted file mode 100644 index 2086c45..0000000 --- a/src/player.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef PLAYER_H -#define PLAYER_H - -typedef struct Player { - float pos_x, pos_y; - float dir, dir_x, dir_y; - float plane_x, plane_y; - float move_speed; - float sensitivity; - float look_angle; -} Player; - -void move_player(Player *player, GLFWwindow* window, double* mouse_x, double* mouse_y, double width, double height); - -#endif -- cgit v1.2.3-18-g5258