summaryrefslogtreecommitdiff
path: root/simple-raycaster/src/main.c
diff options
context:
space:
mode:
authorSchark <jordan@schark.online>2024-03-06 02:00:59 -0800
committerSchark <jordan@schark.online>2024-03-06 02:00:59 -0800
commitc356e96932b5d1a23bf41914934cf83e8b535994 (patch)
tree9ee15f23c0ae584443d8af63b473d5c2eee094ba /simple-raycaster/src/main.c
parentce811be0799bfee8355aa56d3ab566fb5b5dab55 (diff)
downloadgamedev-c356e96932b5d1a23bf41914934cf83e8b535994.tar.gz
gamedev-c356e96932b5d1a23bf41914934cf83e8b535994.zip
Working barebones heightmap renderer
Diffstat (limited to 'simple-raycaster/src/main.c')
-rw-r--r--simple-raycaster/src/main.c285
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;
+
+}