diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..256a2d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +all: + gcc -Iinclude/ src/app.c src/camera.c src/main.c src/game.c src/utils.c -lSDL2 -lGL -lm -lSDL2_image -o test -Wall +debug: + gcc -Iinclude/ src/app.c src/camera.c src/main.c src/game.c src/utils.c -lSDL2 -lGL -lm -lSDL2_image -o test -Wall -g diff --git a/assets/block.png b/assets/block.png new file mode 100644 index 0000000..e065bb6 Binary files /dev/null and b/assets/block.png differ diff --git a/include/app.h b/include/app.h new file mode 100644 index 0000000..256ebcd --- /dev/null +++ b/include/app.h @@ -0,0 +1,80 @@ +#ifndef APP_H +#define APP_H +#include +#include +#include +#include +#include +#include "camera.h" +#include + +#define SPEED 5 +#define GRAVITY 10 + +#define VIEWPORT_RATIO (16.0 / 9.0) +#define VIEWPORT_ASPECT 50.0 + +typedef unsigned int uint; + +#define CHUNK_MAX_X 16 +#define CHUNK_MAX_Y 16 +#define CHUNK_MAX_Z 128 + +typedef enum { + BLOCKTYPE_AIR, + BLOCKTYPE_GRASS, + BLOCKTYPE_DIRT, + BLOCKTYPE_STONE, +} Block_type; + +typedef struct +{ + Block_type type; + bool visible; +} Block; + +typedef struct { + vec3f start_pos; + Block blocks[CHUNK_MAX_X][CHUNK_MAX_Y][CHUNK_MAX_Z]; + GLuint id; +} Chunk; + + +typedef struct { + bool is_running; + SDL_Window *window; + SDL_GLContext context; + Camera camera; + double frame_time; + SDL_Surface *surface; + GLuint tid; + unsigned int chunk_count; + Chunk *chunks; +} App; + +bool chunk_is_block_neighboring_block(Chunk* chunk, vec3i pos); +void chunk_set_blocks_visibility(Chunk* chunk); +void chunk_create_displayl(App *app, Chunk *chunk); +void chunk_render(Chunk* chunk); + +bool is_block(Block b); + +int app_get_current_chunk_index(App *app); + +bool app_check_collision(App *app); + +void init_app(App *app, uint w, uint h); + +void init_opengl(); + +void handle_events(App *app); + +void draw_cube(App *app, float x, float y, float z); + +void reshape(GLsizei width, GLsizei height); + +void update_app(App* app); + +float calc_frame_time(struct timespec *start, struct timespec *end); + +#endif \ No newline at end of file diff --git a/include/camera.h b/include/camera.h new file mode 100644 index 0000000..128dbb6 --- /dev/null +++ b/include/camera.h @@ -0,0 +1,51 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include "utils.h" + +#include + +/** + * Camera, as a moving point with direction + */ + +typedef struct Camera +{ + vec3f position; + vec3f rotation; + vec3f speed; +} Camera; + +/** + * Initialize the camera to the start position. + */ +void init_camera(Camera* camera); + +/** + * Update the position of the camera. + */ +void update_camera(Camera* camera, double time); + +/** + * Apply the camera settings to the view transformation. + */ +void set_view(const Camera* camera); + +/** + * Set the horizontal and vertical rotation of the view angle. + */ +void rotate_camera(Camera* camera, double horizontal, double vertical); + +/** + * Set the speed of forward and backward motion. + */ +void set_camera_speed(Camera* camera, double speed); + +void camera_set_vertical_speed(Camera* camera, double speed); + +/** + * Set the speed of left and right side steps. + */ +void set_camera_side_speed(Camera* camera, double speed); + +#endif /* CAMERA_H */ diff --git a/include/game.h b/include/game.h new file mode 100644 index 0000000..ca67bb4 --- /dev/null +++ b/include/game.h @@ -0,0 +1,11 @@ +#ifndef GAME_H +#define GAME_H +#include "app.h" +#include "utils.h" +#include +#include + + + + +#endif \ No newline at end of file diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..d11d5a5 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,49 @@ +#ifndef UTILS_H +#define UTILS_H +#include + +/** + * GLSL-like three dimensional vector + */ +typedef struct vec3f +{ + float x; + float y; + float z; +} vec3f; + +typedef struct vec3i +{ + int x; + int y; + int z; +} vec3i; + +typedef struct vec4f +{ + float x; + float y; + float z; + float w; +} vec4f; + +/** + * Color with RGB components + */ +typedef struct Color +{ + float red; + float green; + float blue; +} Color; + +/** + * Calculates radian from degree. + */ +double degree_to_radian(double degree); + +bool is_oob(int i, int min, int max); + +void mult_matrix(float *matrix, vec4f vector, vec4f *ret); + +#endif /* UTILS_H */ diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..72bfc2f --- /dev/null +++ b/src/app.c @@ -0,0 +1,314 @@ +#include "../include/app.h" +#include + +void init_app(App *app, uint w, uint h) +{ + int err; + + err = SDL_Init(SDL_INIT_EVERYTHING); + if (err != 0) { + printf("Failed to initialize SDL. Error: %s\n", + SDL_GetError()); + exit(1); + } + + app->window = SDL_CreateWindow( + "test", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + w, h, SDL_WINDOW_OPENGL); + + if (app->window == NULL) { + printf("Failed to create window.\n"); + exit(1); + } + + app->context = SDL_GL_CreateContext(app->window); + if (app->context == NULL) { + printf("Failed to create OpenGL context.\n"); + exit(1); + } + app->is_running = true; + + //SDL_ShowCursor(SDL_DISABLE); + init_camera(&app->camera); + init_opengl(); + reshape(w, h); + + init_camera(&(app->camera)); + + app->surface = IMG_Load("assets/block.jpg"); + app->tid = 0; + + if(app->surface==NULL) { + printf("error\n"); + exit(1); + } + glGenTextures(1, &app->tid); + glBindTexture(GL_TEXTURE_2D, app->tid); + + int Mode = GL_RGB; + + if(app->surface->format->BytesPerPixel == 4) { + Mode = GL_RGBA; + } + + glTexImage2D(GL_TEXTURE_2D, 0, Mode, app->surface->w, app->surface->h, 0, Mode, GL_UNSIGNED_BYTE, app->surface->pixels); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_2D, app->tid); +} + +void update_app(App* app) +{ + app->camera.speed.z -= GRAVITY * (app->frame_time/1000); + + if(app_check_collision(app)) + camera_set_vertical_speed(&app->camera, 0.0); + update_camera(&(app->camera), app->frame_time/1000); +} + +void init_opengl() +{ + // https://gitlab.com/imre-piller/me-courses/ alapjan. + glShadeModel(GL_SMOOTH); + + glEnable(GL_NORMALIZE); + glEnable(GL_AUTO_NORMAL); + + glClearColor(0.1, 0.1, 0.1, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_DEPTH_TEST); + glClearDepth(1.0); + glEnable(GL_TEXTURE_2D); +} + +void reshape(GLsizei width, GLsizei height) +{ + int x, y, w, h; + double ratio; + + ratio = (double)width / height; + if (ratio > VIEWPORT_RATIO) { + w = (int)((double)height * VIEWPORT_RATIO); + h = height; + x = (width - w) / 2; + y = 0; + } + else { + w = width; + h = (int)((double)width / VIEWPORT_RATIO); + x = 0; + y = (height - h) / 2; + } + + glViewport(x, y, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum( + -.08, .08, + -.06, .06, + .1, 1000 + ); +} + +void handle_events(App* app) +{ + SDL_Event event; + static bool is_mouse_down = false; + static int mouse_x = 0; + static int mouse_y = 0; + int x; + int y; + + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_KEYDOWN: + switch (event.key.keysym.scancode) { + case SDL_SCANCODE_ESCAPE: + app->is_running = false; + break; + case SDL_SCANCODE_W: + set_camera_speed(&(app->camera), SPEED); + break; + case SDL_SCANCODE_S: + set_camera_speed(&(app->camera), -SPEED); + break; + case SDL_SCANCODE_A: + set_camera_side_speed(&(app->camera), SPEED); + break; + case SDL_SCANCODE_D: + set_camera_side_speed(&(app->camera), -SPEED); + break; + case SDL_SCANCODE_SPACE: + if (app_check_collision(app)) + app->camera.position.z+=0.3; + camera_set_vertical_speed(&app->camera, SPEED); + break; + case SDL_SCANCODE_LSHIFT: + camera_set_vertical_speed(&app->camera, -SPEED); + break; + default: + break; + } + break; + case SDL_KEYUP: + switch (event.key.keysym.scancode) { + case SDL_SCANCODE_W: + case SDL_SCANCODE_S: + set_camera_speed(&(app->camera), 0); + break; + case SDL_SCANCODE_A: + case SDL_SCANCODE_D: + set_camera_side_speed(&(app->camera), 0); + break; + case SDL_SCANCODE_SPACE: + case SDL_SCANCODE_LSHIFT: + camera_set_vertical_speed(&app->camera, -0); + default: + break; + } + break; + case SDL_MOUSEBUTTONDOWN: + is_mouse_down = true; + break; + case SDL_MOUSEMOTION: + SDL_GetMouseState(&x, &y); + if (is_mouse_down) { + rotate_camera(&(app->camera), mouse_x - x, mouse_y - y); + } + mouse_x = x; + mouse_y = y; + break; + case SDL_MOUSEBUTTONUP: + is_mouse_down = false; + break; + case SDL_QUIT: + app->is_running = false; + break; + default: + break; + } + } + +} + +void app_raycast(App *app) +{ + vec4f v = {0, 0, -1, 1}; + vec4f retv; + float projection[16]; + glGetFloatv(GL_PROJECTION_MATRIX, projection); + mult_matrix(projection, v, &retv); + retv.z = -1.0; + retv.w = 0.0; + +} + +void draw_cube(App *app, float x, float y, float z) +{ + /*x*=2; + y*=2; + z*=2; + */ + glBegin( GL_QUADS ); + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x+1.0, y, z-1.0 ); + glTexCoord2f(0, 1); glVertex3f(x, y, z-1.0); + glTexCoord2f(1, 1); glVertex3f(x, y, z); + glTexCoord2f(1, 0); glVertex3f(x+1.0, y, z); + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x+1.0, y-1.0, z-1.0); + glTexCoord2f(0, 1); glVertex3f(x, y-1.0, z-1.0); + glTexCoord2f(1, 1); glVertex3f(x, y-1.0, z); + glTexCoord2f(1, 0); glVertex3f(x+1.0, y-1.0, z); + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x+1.0, y, z); + glTexCoord2f(0, 1); glVertex3f(x, y, z); + glTexCoord2f(1, 1); glVertex3f(x, y-1.0, z); + glTexCoord2f(1, 0); glVertex3f(x+1.0, y-1.0, z); + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x, y, z-1.0); // Top-Right of back face + glTexCoord2f(0, 1); glVertex3f(x+1.0, y, z-1.0); // Top-Left of back face + glTexCoord2f(1, 1); glVertex3f(x+1.0, y-1.0, z-1.0); // Bottom-Left of back face + glTexCoord2f(1, 0); glVertex3f(x, y-1.0, z-1.0); // Bottom-Right of back face + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x, y, z); // Top-Right of left face + glTexCoord2f(0, 1); glVertex3f(x, y, z-1.0); // Top-Left of left face + glTexCoord2f(1, 1); glVertex3f(x, y-1.0, z-1.0); // Bottom-Left of left face + glTexCoord2f(1, 0); glVertex3f(x, y-1.0, z); // Bottom-Right of left face + + glColor3f(1, 1, 1); + glTexCoord2f(0, 0); glVertex3f(x+1.0, y, z); // Top-Right of left face + glTexCoord2f(0, 1); glVertex3f(x+1.0, y, z-1.0); // Top-Left of left face + glTexCoord2f(1, 1); glVertex3f(x+1.0, y-1.0, z-1.0); // Bottom-Left of left face + glTexCoord2f(1, 0); glVertex3f(x+1.0, y-1.0, z); // Bottom-Right of left face + glEnd(); + glFlush(); +} + +float calc_frame_time(struct timespec *start, struct timespec *end) +{ + float elapsed_time = (end->tv_sec - start->tv_sec)*1000; + + elapsed_time += (float)(end->tv_nsec - start->tv_nsec)/1000000; + + return elapsed_time; +} + +int app_get_current_chunk_index(App *app) +{ + for (int i = 0; i < app->chunk_count; i++) { + for (int x = 0; x < CHUNK_MAX_X; x++) { + for (int y = 0; y < CHUNK_MAX_Y; y++) { + for (int z = 0; z < CHUNK_MAX_Z; z++) { + if (app->camera.position.x < app->chunks[i].start_pos.x + CHUNK_MAX_X && + app->camera.position.x > app->chunks[i].start_pos.x && + app->camera.position.y < app->chunks[i].start_pos.y + CHUNK_MAX_Y && + app->camera.position.y > app->chunks[i].start_pos.y) { + return i; + } + } + } + } + } + + return -1; +} + +bool is_block(Block b) +{ + if(b.type != BLOCKTYPE_AIR) + return true; + return false; +} + +bool app_check_collision(App *app) +{ + int i = app_get_current_chunk_index(app); + + if (i < 0) + return false; + + for (int x = 0; x < CHUNK_MAX_X; x++) { + for (int y = 0; y < CHUNK_MAX_Y; y++) { + for (int z = 0; z < CHUNK_MAX_Z; z++) { + if (!is_block(app->chunks[i].blocks[x][y][z])) + continue; + if (app->camera.position.z <= z+2) + return true; + } + } + } + return false; +} diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 0000000..ff3f2ea --- /dev/null +++ b/src/camera.c @@ -0,0 +1,83 @@ +#include "../include/camera.h" + +#include +#include + +#include + +void init_camera(Camera* camera) +{ + camera->position.x = 5.0; + camera->position.y = 5.0; + camera->position.z = 60.0; + camera->rotation.x = 0.0; + camera->rotation.y = 0.0; + camera->rotation.z = 0.0; + camera->speed.x = 0.0; + camera->speed.y = 0.0; + camera->speed.z = 0.0; +} + +void update_camera(Camera* camera, double time) +{ + double angle; + double side_angle; + + angle = degree_to_radian(camera->rotation.z); + side_angle = degree_to_radian(camera->rotation.z + 90.0); + + camera->position.x += cos(angle) * camera->speed.y * time; + camera->position.y += sin(angle) * camera->speed.y * time; + camera->position.z += camera->speed.z * time; + + camera->position.x += cos(side_angle) * camera->speed.x * time; + camera->position.y += sin(side_angle) * camera->speed.x * time; + camera->position.z += camera->speed.z * time; +} + +void set_view(const Camera* camera) +{ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glRotatef(-(camera->rotation.x), 1.0, 0, 0); + glRotatef(-(camera->rotation.z - 90), 0, 0, 1.0); + glTranslatef(-camera->position.x, -camera->position.y, -camera->position.z); +} + +void rotate_camera(Camera* camera, double horizontal, double vertical) +{ + camera->rotation.z += horizontal; + camera->rotation.x += vertical; + + if (camera->rotation.z < 0) { + camera->rotation.z += 360.0; + } + + if (camera->rotation.z > 360.0) { + camera->rotation.z -= 360.0; + } + + if (camera->rotation.x < 0.0) { + camera->rotation.x = 0.0; + } + + if (camera->rotation.x > 180.0) { + camera->rotation.x = 180.0; + } +} + +void set_camera_speed(Camera* camera, double speed) +{ + camera->speed.y = speed; +} + +void camera_set_vertical_speed(Camera* camera, double speed) +{ + camera->speed.z = speed; +} + +void set_camera_side_speed(Camera* camera, double speed) +{ + camera->speed.x = speed; +} diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..3dcd84d --- /dev/null +++ b/src/game.c @@ -0,0 +1,61 @@ +#include "../include/game.h" +#include + +bool chunk_is_block_neighboring_block(Chunk* chunk, vec3i pos) +{ + if (is_oob(pos.x-1, 0, CHUNK_MAX_X) || is_oob(pos.x+1, 0, CHUNK_MAX_X)) + return false; + if (is_oob(pos.y-1, 0, CHUNK_MAX_Y) || is_oob(pos.y+1, 0, CHUNK_MAX_Y)) + return false; + if (is_oob(pos.z-1, 0, CHUNK_MAX_Z) || is_oob(pos.z+1, 0, CHUNK_MAX_Z)) + return false; + + if (chunk->blocks[pos.x-1][pos.y][pos.z].type != BLOCKTYPE_AIR && + chunk->blocks[pos.x+1][pos.y][pos.z].type != BLOCKTYPE_AIR && + chunk->blocks[pos.x][pos.y-1][pos.z].type != BLOCKTYPE_AIR && + chunk->blocks[pos.x][pos.y+1][pos.z].type != BLOCKTYPE_AIR && + chunk->blocks[pos.x][pos.y][pos.z-1].type != BLOCKTYPE_AIR && + chunk->blocks[pos.x][pos.y][pos.z+1].type != BLOCKTYPE_AIR) + return true; + + return false; +} + +void chunk_set_blocks_visibility(Chunk* chunk) +{ + for (int x = 0; x < CHUNK_MAX_X; x++) { + for (int y = 0; y < CHUNK_MAX_Y; y++) { + for (int z = 0; z < CHUNK_MAX_Z; z++) { + vec3i v = {x, y, z}; + if (chunk_is_block_neighboring_block(chunk, v)) + chunk->blocks[x][y][z].visible = false; + else + chunk->blocks[x][y][z].visible = true; + + if (chunk->blocks[x][y][z].type == BLOCKTYPE_AIR) + chunk->blocks[x][y][z].visible = false; + } + } + } +} + +void chunk_create_displayl(App *app, Chunk *chunk) +{ + chunk->id = glGenLists(1); + + glNewList(chunk->id, GL_COMPILE); + for (int x = 0; x < CHUNK_MAX_X; x++) { + for (int y = 0; y < CHUNK_MAX_Y; y++) { + for (int z = 0; z < CHUNK_MAX_Z; z++) { + if (chunk->blocks[x][y][z].visible) + draw_cube(app, chunk->start_pos.x + x, chunk->start_pos.y + y, chunk->start_pos.z + z); + } + } + } + glEndList(); +} + +void chunk_render(Chunk* chunk) +{ + glCallList(chunk->id); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..eb83d15 --- /dev/null +++ b/src/main.c @@ -0,0 +1,80 @@ +#include "../include/app.h" +#include +#include +#include + +int main() +{ + App app; + init_app(&app, 1280, 720); + + Chunk *chunks = malloc(100*sizeof(Chunk)); + memset(chunks, 0, 100*sizeof(Chunk)); + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + Chunk c; + c.start_pos.x = i*CHUNK_MAX_X; + c.start_pos.y = j*CHUNK_MAX_Y; + c.start_pos.z = 0; + for (int x = 0; x < CHUNK_MAX_X; x++) { + for (int y = 0; y < CHUNK_MAX_Y; y++) { + for (int z = 0; z < CHUNK_MAX_Z; z++) { + if (z < 40) + c.blocks[x][y][z].type = BLOCKTYPE_STONE; + else + c.blocks[x][y][z].type = BLOCKTYPE_AIR; + } + } + } + chunk_set_blocks_visibility(&c); + chunk_create_displayl(&app, &c); + + chunks[i+(j*10)] = c; + } + } + + app.chunks = chunks; + app.chunk_count = 100; + + while(app.is_running) { + struct timespec start; + struct timespec end; + + clock_gettime(CLOCK_MONOTONIC_RAW, &start); + + handle_events(&app); + update_app(&app); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + set_view(&app.camera); + + + printf("camera x: %f y: %f z: %f\n", app.camera.position.x, app.camera.position.y, app.camera.position.z); + + for (int i = 0; i < 100; i++) { + chunk_render(&chunks[i]); + } + + vec3f dir = {cos(degree_to_radian(app.camera.rotation.z)), sin(degree_to_radian(app.camera.rotation.z)), -cos(degree_to_radian(app.camera.rotation.x))}; + vec3f pos = {app.camera.position.x, app.camera.position.y, app.camera.position.z }; + + glLineWidth(20); + glBegin(GL_LINES); + glColor3f(1.0, 0.0, 0.0); + glVertex3f(pos.x, pos.y, pos.z); + glVertex3f(pos.x+5*dir.x, pos.y+5*dir.y, pos.z+5*dir.z); + glEnd(); + + SDL_GL_SwapWindow(app.window); + + clock_gettime(CLOCK_MONOTONIC_RAW, &end); + + printf("fps: %f\n", 1000/calc_frame_time(&start, &end)); + printf("lkat pos x: %f y: %f z: %f\n", pos.x+5*dir.x, pos.y+5*dir.y, pos.z+5*dir.z); + printf("lkat x: %f y: %f z: %f\n", dir.x, dir.y, dir.z); + app.frame_time = calc_frame_time(&start, &end); + } + + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..93ef9f0 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,31 @@ +#include "utils.h" + +#include +#include + +double degree_to_radian(double degree) +{ + return degree * M_PI / 180.0; +} + +bool is_oob(int i, int min, int max) +{ + if (i < min || i >= max) + return true; + return false; +} + +void mult_matrix(float *matrix, vec4f vector, vec4f *ret) +{ + float vec[] = {vector.x, vector.y, vector.z, vector.w}; + float ret1[4]; + for(int i = 0; i <= 12; i+=4) { + float line_sum = 0.0; + for(int j = 0; j < 4; j++){ + matrix[i+j] *= vec[j]; + line_sum += matrix[i+j]; + } + ret1[i/4] = line_sum; + } + ret->x = ret1[0]; ret->y = ret1[1]; ret->z=ret1[2]; ret->w = ret1[3]; +} \ No newline at end of file