summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSchark <jordan@schark.online>2023-05-11 01:57:35 -0700
committerSchark <jordan@schark.online>2023-05-11 01:57:35 -0700
commit70d9a26e5e80927f1c3f4b178b17037a7f6311e9 (patch)
tree8658a40d47998029147c47de5a68f7068b24263c
parent79173c530d1846082c631dd4fe81c6429fffa5f0 (diff)
downloadgamedev-70d9a26e5e80927f1c3f4b178b17037a7f6311e9.tar.gz
gamedev-70d9a26e5e80927f1c3f4b178b17037a7f6311e9.zip
Base implementation of raycasting engine
-rw-r--r--CMakeLists.txt3
-rw-r--r--readme18
-rw-r--r--src/main.c209
3 files changed, 226 insertions, 4 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c7f1de8..1d49822 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,8 +5,9 @@ set(CMAKE_C_STANDARD 99)
find_package(OpenGL 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} glfw)
+target_link_libraries(raycaster ${OPENGL_gl_LIBRARY} ${FREETYPE_LIBARIES} glfw m)
diff --git a/readme b/readme
index 22beb18..3f687fe 100644
--- a/readme
+++ b/readme
@@ -1,5 +1,17 @@
-Todo:
- - Update README
+Current Todo:
+ - Actually update README
+ - Use mouse to look around, as opposed to keys
+ - Y-Shearing eventually, but probably following refactor
+ - Adjustable window size
+ - 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)
+ - "Render distance" to support larger maps not losing performance
+ - And for aesthetics, of course
+ - Eventually clean code (move appropriate code to different files, headers)
+ - Level editor
Requirements:
- - SDL2
+ - OpenGL
+ - GLFW
+ - Freetype (not being used as of right now)
diff --git a/src/main.c b/src/main.c
index cb652fd..6e0b7d0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,9 +1,159 @@
#include <GLFW/glfw3.h>
#include <stdio.h>
+#include <math.h>
+
+// TODO: move to implicit map definition with level editor in future
+#define MAP_HEIGHT 10
+#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, 0, 0, 0, 0, 2, 2, 0, 1},
+ {1, 0, 0, 0, 0, 0, 2, 2, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 3, 3, 0, 0, 4, 4, 0, 1},
+ {1, 0, 3, 3, 0, 0, 4, 4, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 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; }
+
+ // 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;
+ }
+
+ // 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");
@@ -21,14 +171,73 @@ int main(int argc, char *argv[]) {
// 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;
}