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 --- simple-raycaster/src/main.c | 285 ++++++++++++++++++++++++++++++++++++++++++ simple-raycaster/src/player.c | 44 +++++++ simple-raycaster/src/player.h | 15 +++ 3 files changed, 344 insertions(+) create mode 100644 simple-raycaster/src/main.c create mode 100644 simple-raycaster/src/player.c create mode 100644 simple-raycaster/src/player.h (limited to 'simple-raycaster/src') 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 -- cgit v1.2.3-18-g5258