From ba2f810f0752bdecf8253b34bd245c7939f23534 Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Thu, 4 Dec 2025 13:16:21 -0500 Subject: initial chunk rendering --- .clang-format | 107 +++++++++++++++++++++++++++++ .gitignore | 1 + Makefile | 42 ++++++++++++ assets/fragment.glsl | 22 ++++++ assets/vertex.glsl | 14 ++++ compile_flags.txt | 5 ++ flake.lock | 61 +++++++++++++++++ flake.nix | 47 +++++++++++++ src/camera.c | 146 ++++++++++++++++++++++++++++++++++++++++ src/chunk.c | 138 ++++++++++++++++++++++++++++++++++++++ src/cube.h | 60 +++++++++++++++++ src/list.c | 71 ++++++++++++++++++++ src/list.h | 26 +++++++ src/main.c | 50 ++++++++++++++ src/mesh.c | 78 +++++++++++++++++++++ src/mesh.h | 23 +++++++ src/shader.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/shader.h | 22 ++++++ src/voxel.h | 59 ++++++++++++++++ src/window.c | 147 ++++++++++++++++++++++++++++++++++++++++ src/window.h | 46 +++++++++++++ 21 files changed, 1351 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 assets/fragment.glsl create mode 100755 assets/vertex.glsl create mode 100644 compile_flags.txt create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/camera.c create mode 100644 src/chunk.c create mode 100644 src/cube.h create mode 100644 src/list.c create mode 100644 src/list.h create mode 100644 src/main.c create mode 100644 src/mesh.c create mode 100644 src/mesh.h create mode 100644 src/shader.c create mode 100644 src/shader.h create mode 100644 src/voxel.h create mode 100644 src/window.c create mode 100644 src/window.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..acfa476 --- /dev/null +++ b/.clang-format @@ -0,0 +1,107 @@ +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 92 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c8e3fc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +### Copyright (c) 2025 Freya Murphy + +CC ?= cc + +CFLAGS += -O2 +CFLAGS += -std=gnu23 +CFLAGS += -Wall -Wextra -pedantic + +LDFLAGS += -lglfw -lGL -lGLEW -lcglm -lm -lc + +.PHONY: build clean run fmt +.SILENT: + +SRC := src +BIN := bin +OUT := voxel + +H_SRC := $(shell find $(SRC) -type f -name "*.h") +C_SRC := $(shell find $(SRC) -type f -name "*.c") +C_OBJ := $(patsubst %.c,$(BIN)/%.o,$(C_SRC)) + +build: $(BIN)/$(OUT) + +clean: + rm -fr $(BIN) + +run: build + $(BIN)/$(OUT) + +fmt: + clang-format -i $(shell find -type f -name "*.[ch]") + sed -i 's/[ \t]*$$//' $(shell find -type f -name "*.[chS]" -or -name "*.glsl") + +$(C_OBJ): $(BIN)/%.o : %.c $(H_SRC) + mkdir -p $(@D) + printf "\033[34m CC \033[0m%s\n" $< + $(CC) -c $(CFLAGS) -o $@ $< + +$(BIN)/$(OUT): $(C_OBJ) + mkdir -p $(@D) + printf "\033[32m LD \033[0m%s\n" $@ + $(CC) $(LDFLAGS) -o $(BIN)/$(OUT) $(C_OBJ) diff --git a/assets/fragment.glsl b/assets/fragment.glsl new file mode 100755 index 0000000..831a8ab --- /dev/null +++ b/assets/fragment.glsl @@ -0,0 +1,22 @@ +#version 330 + +flat in uint pass_data; + +out vec4 color; + +const float TINT[6] = float[]( + // px, nx + 0.9, 0.9, + // py, ny + 1, 0.7, + // pz, nz + 0.8, 0.8 +); + +void main(void) +{ + uint face = pass_data >> 2u; + uint block = pass_data & 3u; + float tint = TINT[face]; + color = vec4(tint, tint, tint, 1); +} diff --git a/assets/vertex.glsl b/assets/vertex.glsl new file mode 100755 index 0000000..a0af427 --- /dev/null +++ b/assets/vertex.glsl @@ -0,0 +1,14 @@ +#version 330 + +in vec3 position; +in uint data; + +flat out uint pass_data; + +uniform mat4 proj_view; + +void main(void) +{ + gl_Position = proj_view * vec4(position, 1.0); + pass_data = data; +} diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..c5bde68 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,5 @@ +-c +-std=gnu2y +-Wall +-Wextra +-Wpedantic diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..333e23c --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1763421233, + "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8d562ce --- /dev/null +++ b/flake.nix @@ -0,0 +1,47 @@ +{ + description = "unnamed game"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: let + lib = nixpkgs.lib; + supportedSystems = let + inherit (flake-utils.lib) system; + in [ + system.aarch64-linux + system.x86_64-linux + ]; + in + flake-utils.lib.eachSystem supportedSystems (system: let + pkgs = import nixpkgs {inherit system;}; + libs = with pkgs; [ + libGL + glew + cglm + glfw + wayland + libxkbcommon + ]; + in { + devShell = + pkgs.mkShell + { + packages = with pkgs; + [ + mangohud + valgrind + ] + ++ libs; + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + }; + + formatter = pkgs.alejandra; + }); +} diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 0000000..ec486d7 --- /dev/null +++ b/src/camera.c @@ -0,0 +1,146 @@ +#include "cglm/vec3.h" +#include + +#include "voxel.h" +#include "window.h" + +Camera camera_init(void) +{ + Camera camera = { 0 }; + camera.fov = 70; + camera.near = 0.1; + camera.far = 1000; + camera.look_speed = 100; + camera.move_speed = 4.317; + return camera; +} + +void camera_proj(Camera *camera, mat4 proj) +{ + float aspect, tan_half_foxy; + + aspect = (float)window.width / (float)window.height; + tan_half_foxy = tanf(camera->fov * (M_PI / 180)) / 2.0; + + glm_mat4_zero(proj); + proj[0][0] = 1.0 / (aspect * tan_half_foxy); + proj[1][1] = 1.0 / tan_half_foxy; + proj[2][2] = camera->far / (camera->far - camera->near); + proj[2][3] = 1; + proj[3][2] = -(camera->far * camera->near) / (camera->far - camera->near); +} + +void camera_view(Camera *camera, mat4 view) +{ + float c3, s3, c2, s2, c1, s1; + vec3 u, v, w; + + c3 = cosf(camera->rotation[2] * (M_PI / 180)); + s3 = sinf(camera->rotation[2] * (M_PI / 180)); + c2 = cosf(camera->rotation[0] * (M_PI / 180)); + s2 = sin(camera->rotation[0] * (M_PI / 180)); + c1 = cosf(camera->rotation[1] * (M_PI / 180)); + s1 = sinf(camera->rotation[1] * (M_PI / 180)); + + u[0] = c1 * c3 + s1 * s2 * s3; + u[1] = c2 * s3; + u[2] = c1 * s2 * s3 - c3 * s1; + + v[0] = c3 * s1 * s2 - c1 * s3; + v[1] = c2 * c3; + v[2] = c1 * c3 * s2 + s1 * s3; + + w[0] = c2 * s1; + w[1] = -s2; + w[2] = c1 * c2; + + view[0][0] = u[0]; + view[0][1] = v[0]; + view[0][2] = w[0]; + view[0][3] = 0; + view[1][0] = u[1]; + view[1][1] = v[1]; + view[1][2] = w[1]; + view[1][3] = 0; + view[2][0] = u[2]; + view[2][1] = v[2]; + view[2][2] = w[2]; + view[2][3] = 0; + view[3][0] = -glm_dot(u, camera->position); + view[3][1] = -glm_dot(v, camera->position); + view[3][2] = -glm_dot(w, camera->position); + view[3][3] = 1; +} + +void camera_proj_view(Camera *camera, mat4 proj_view) +{ + mat4 proj, view; + camera_proj(camera, proj); + camera_view(camera, view); + glm_mat4_mul(proj, view, proj_view); +} + +static void camera_update_rotation(Camera *camera) +{ + vec3 rotate; + + glm_vec3_zero(rotate); + if (key_down(GLFW_KEY_RIGHT)) + rotate[1] += 1; + if (key_down(GLFW_KEY_LEFT)) + rotate[1] -= 1; + if (key_down(GLFW_KEY_UP)) + rotate[0] -= 1; + if (key_down(GLFW_KEY_DOWN)) + rotate[0] += 1; + glm_normalize(rotate); + + camera->rotation[0] += camera->look_speed * delta_time * rotate[0]; + camera->rotation[1] += camera->look_speed * delta_time * rotate[1]; + + camera->rotation[0] = glm_clamp(camera->rotation[0], -90, 90); + //camera->rotation[1] = camera->rotation[0] % 360.0; +} + +static void camera_update_movement(Camera *camera) +{ + vec3 forward, left, up, move; + float yaw; + + yaw = camera->rotation[1] * (M_PI / 180); + + glm_vec3_zero(forward); + forward[0] = sinf(yaw); + forward[2] = cosf(yaw); + + glm_vec3_zero(left); + left[0] = -forward[2]; + left[2] = forward[0]; + + glm_vec3_zero(up); + up[1] = 1; + + glm_vec3_zero(move); + if (key_down(GLFW_KEY_W)) + glm_vec3_add(move, forward, move); + if (key_down(GLFW_KEY_S)) + glm_vec3_sub(move, forward, move); + if (key_down(GLFW_KEY_A)) + glm_vec3_add(move, left, move); + if (key_down(GLFW_KEY_D)) + glm_vec3_sub(move, left, move); + if (key_down(GLFW_KEY_E)) + glm_vec3_add(move, up, move); + if (key_down(GLFW_KEY_Q)) + glm_vec3_sub(move, up, move); + glm_normalize(move); + glm_vec3_scale(move, camera->move_speed * delta_time, move); + + glm_vec3_add(camera->position, move, camera->position); +} + +void camera_update(Camera *camera) +{ + camera_update_rotation(camera); + camera_update_movement(camera); +} diff --git a/src/chunk.c b/src/chunk.c new file mode 100644 index 0000000..50fe5c3 --- /dev/null +++ b/src/chunk.c @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "voxel.h" +#include "list.h" +#include "cube.h" +#include "mesh.h" + +Chunk *chunk_init(int x, int y, int z) +{ + Chunk *chunk = malloc(sizeof(Chunk)); + chunk->x = x; + chunk->y = y; + chunk->z = z; + memset(chunk->blocks, AIR, sizeof(chunk->blocks)); + return chunk; +} + +void chunk_free(Chunk *chunk) +{ + free(chunk); +} + +void chunk_generate(Chunk *chunk) +{ + int size = sizeof(chunk->blocks); + for (int i = 0; i < size; i++) { + int x = i % CHUNK_SIZE; + int z = (i / CHUNK_SIZE) % CHUNK_SIZE; + int y = i / (CHUNK_SIZE * CHUNK_SIZE); + + char block = AIR; + int temp = x + z - y; + if (temp > 16) + block = DIRT; + + chunk->blocks[i] = block; + } +} + +typedef struct { + Chunk *chunk; + List pos; + List data; + int vertex_count; +} MeshState; + +static void add_vertex(MeshState *state, const vec3 pos, const vec3 xyz, unsigned int data) +{ + list_pushf(&state->pos, pos[0] + xyz[0]); + list_pushf(&state->pos, pos[1] + xyz[1]); + list_pushf(&state->pos, pos[2] + xyz[2]); + list_pushu(&state->data, data); + state->vertex_count++; +} + +static void add_quad(MeshState *state, const vec3 xyz, Face face, Block block) +{ + unsigned int data = (face << 2) | block; + const vec3 *verts = CUBE[face]; + + add_vertex(state, verts[0], xyz, data); + add_vertex(state, verts[1], xyz, data); + add_vertex(state, verts[2], xyz, data); + add_vertex(state, verts[3], xyz, data); + add_vertex(state, verts[4], xyz, data); + add_vertex(state, verts[5], xyz, data); +} + +Mesh chunk_mesh(Chunk *chunk) +{ + MeshState state; + state.chunk = chunk; + state.vertex_count = 0; + list_initf(&state.pos); + list_initu(&state.data); + + int size = sizeof(chunk->blocks); + for (int i = 0; i < size; i++) { + int x = i % CHUNK_SIZE; + int z = (i / CHUNK_SIZE) % CHUNK_SIZE; + int y = i / (CHUNK_SIZE * CHUNK_SIZE); + vec3 xyz = { x, y, z }; + + Block block = chunk->blocks[i]; + if (block == AIR) + continue; + + Block px = chunk_at(chunk, x + 1, y, z); + Block nx = chunk_at(chunk, x - 1, y, z); + Block py = chunk_at(chunk, x, y + 1, z); + Block ny = chunk_at(chunk, x, y - 1, z); + Block pz = chunk_at(chunk, x, y, z + 1); + Block nz = chunk_at(chunk, x, y, z - 1); + + if (px == AIR) + add_quad(&state, xyz, POS_X, block); + if (nx == AIR) + add_quad(&state, xyz, NEG_X, block); + if (py == AIR) + add_quad(&state, xyz, POS_Y, block); + if (ny == AIR) + add_quad(&state, xyz, NEG_Y, block); + if (pz == AIR) + add_quad(&state, xyz, POS_Z, block); + if (nz == AIR) + add_quad(&state, xyz, NEG_Z, block); + } + + Mesh mesh; + mesh_init(&mesh, state.vertex_count); + mesh_storef(&mesh, state.pos.fdata, state.pos.len, 3); + mesh_storeu(&mesh, state.data.udata, state.data.len, 1); + mesh_finish(); + + list_free(&state.pos); + list_free(&state.data); + + return mesh; +} + +Block chunk_at(Chunk *chunk, int x, int y, int z) +{ + if (x < 0 || x >= CHUNK_SIZE) + return AIR; + if (y < 0 || y >= CHUNK_SIZE) + return AIR; + if (z < 0 || z >= CHUNK_SIZE) + return AIR; + + int i = 0; + i += x; + i += z * CHUNK_SIZE; + i += y * CHUNK_SIZE * CHUNK_SIZE; + + return chunk->blocks[i]; +} diff --git a/src/cube.h b/src/cube.h new file mode 100644 index 0000000..fd071d0 --- /dev/null +++ b/src/cube.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +static const vec3 CUBE[6][6] = { + { + // PX + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 0, 0 }, + { 1, 0, 0 }, + { 1, 1, 0 }, + { 1, 1, 1 }, + }, + { + // NX + { 0, 1, 0 }, + { 0, 0, 0 }, + { 0, 0, 1 }, + { 0, 0, 1 }, + { 0, 1, 1 }, + { 0, 1, 0 }, + }, + { + // PY + { 1, 1, 0 }, + { 0, 1, 0 }, + { 0, 1, 1 }, + { 0, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 0 }, + }, + { + // NY + { 0, 0, 1 }, + { 0, 0, 0 }, + { 1, 0, 0 }, + { 1, 0, 0 }, + { 1, 0, 1 }, + { 0, 0, 1 }, + }, + { + // PZ + { 0, 1, 1 }, + { 0, 0, 1 }, + { 1, 0, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + { 0, 1, 1 }, + }, + { + // NZ + { 1, 1, 0 }, + { 1, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 1, 0 }, + { 1, 1, 0 }, + }, +}; diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..0f00cdd --- /dev/null +++ b/src/list.c @@ -0,0 +1,71 @@ +#include +#include + +#include "list.h" + +static void list_init(List *list, int elmSize) +{ + list->data = NULL; + list->capacity = 0; + list->len = 0; + list->elmSize = elmSize; +} + +void list_initf(List *list) +{ + list_init(list, sizeof(float)); +} + +void list_initi(List *list) +{ + list_init(list, sizeof(int)); +} + +void list_initu(List *list) +{ + list_init(list, sizeof(unsigned int)); +} + +void list_initb(List *list) +{ + list_init(list, sizeof(unsigned char)); +} + +static void list_push(List *list, const void *elm) +{ + if (list->len == list->capacity) { + list->capacity *= 2; + if (!list->capacity) + list->capacity = 8; + list->data = realloc(list->data, list->elmSize * list->capacity); + } + + void *ptr = ((char *)list->data) + list->elmSize * list->len; + memcpy(ptr, elm, list->elmSize); + list->len++; +} + +void list_pushf(List *list, float f) +{ + list_push(list, &f); +} + +void list_pushi(List *list, int i) +{ + list_push(list, &i); +} + +void list_pushu(List *list, unsigned int u) +{ + list_push(list, &u); +} + +void list_pushb(List *list, unsigned char b) +{ + list_push(list, &b); +} + +void list_free(List *list) +{ + free(list->data); +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..6d2211b --- /dev/null +++ b/src/list.h @@ -0,0 +1,26 @@ +#pragma once + +typedef struct { + union { + float *fdata; + int *idata; + unsigned int *udata; + unsigned char *bdata; + void *data; + }; + int len; + int capacity; + int elmSize; +} List; + +void list_initf(List *list); +void list_initi(List *list); +void list_initu(List *list); +void list_initb(List *list); + +void list_pushf(List *list, float f); +void list_pushi(List *list, int i); +void list_pushu(List *list, unsigned int u); +void list_pushb(List *list, unsigned char u); + +void list_free(List *list); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2ed1a49 --- /dev/null +++ b/src/main.c @@ -0,0 +1,50 @@ +#include + +#include "voxel.h" +#include "window.h" +#include "shader.h" + +int main(void) +{ + Shader *shader; + Chunk *chunk; + Mesh mesh; + Camera camera; + mat4 proj_view; + + if (window_init()) + return 1; + + shader = shader_init("assets/vertex.glsl", "assets/fragment.glsl"); + if (!shader) + return 1; + + chunk = chunk_init(0, 0, 0); + chunk_generate(chunk); + mesh = chunk_mesh(chunk); + + camera = camera_init(); + + while (!window_closed()) { + window_update(); + + if (key_down(GLFW_KEY_ESCAPE)) + break; + + camera_update(&camera); + camera_proj_view(&camera, proj_view); + + shader_bind(shader); + shader_loadm4f(0, proj_view); + mesh_bind(&mesh); + mesh_draw(&mesh); + mesh_unbind(&mesh); + shader_unbind(); + + // main game loop + window_swap(); + } + + window_close(); + return 0; +} diff --git a/src/mesh.c b/src/mesh.c new file mode 100644 index 0000000..6b2951b --- /dev/null +++ b/src/mesh.c @@ -0,0 +1,78 @@ +#include + +#include "mesh.h" + +void mesh_init(Mesh *mesh, int vertex_count) +{ + glGenVertexArrays(1, &mesh->vao); + mesh->vbos_count = 0; + mesh->vertex_count = vertex_count; + glBindVertexArray(mesh->vao); +} + +static void mesh_store(Mesh *mesh, void *data, int data_len, int dimensions, GLenum type) +{ + GLuint vbo; + GLint index = mesh->vbos_count; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, data_len, data, GL_STATIC_DRAW); + glEnableVertexAttribArray(index); + if (type == GL_FLOAT) + glVertexAttribPointer(index, dimensions, type, GL_FALSE, 0, 0); + else + glVertexAttribIPointer(index, dimensions, type, 0, 0); + mesh->vbos[mesh->vbos_count++] = vbo; +} + +void mesh_storef(Mesh *mesh, float *data, int data_len, int dimensions) +{ + mesh_store(mesh, data, data_len * sizeof(float), dimensions, GL_FLOAT); +} + +void mesh_storei(Mesh *mesh, int *data, int data_len, int dimensions) +{ + mesh_store(mesh, data, data_len * sizeof(int), dimensions, GL_INT); +} + +void mesh_storeu(Mesh *mesh, unsigned int *data, int data_len, int dimensions) +{ + mesh_store(mesh, data, data_len * sizeof(unsigned int), dimensions, + GL_UNSIGNED_INT); +} + +void mesh_storeb(Mesh *mesh, unsigned char *data, int data_len, int dimensions) +{ + mesh_store(mesh, data, data_len * sizeof(unsigned char), dimensions, + GL_UNSIGNED_BYTE); +} + +void mesh_finish(void) +{ + glBindVertexArray(0); +} + +void mesh_bind(Mesh *mesh) +{ + glBindVertexArray(mesh->vao); + for (int i = 0; i < mesh->vbos_count; i++) + glEnableVertexAttribArray(i); +} + +void mesh_unbind(Mesh *mesh) +{ + for (int i = 0; i < mesh->vbos_count; i++) + glDisableVertexAttribArray(i); + glBindVertexArray(0); +} + +void mesh_draw(Mesh *mesh) +{ + glDrawArrays(GL_TRIANGLES, 0, mesh->vertex_count); +} + +void mesh_free(Mesh *mesh) +{ + glDeleteBuffers(mesh->vbos_count, mesh->vbos); + glDeleteVertexArrays(1, &mesh->vao); +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..e15b222 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#define MAX_VBOS 4 + +typedef struct { + GLuint vao; + GLuint vbos[MAX_VBOS]; + int vbos_count; + int vertex_count; +} Mesh; + +void mesh_init(Mesh *mesh, int vertex_count); +void mesh_storef(Mesh *mesh, float *data, int data_len, int dimensions); +void mesh_storei(Mesh *mesh, int *data, int data_len, int dimensions); +void mesh_storeu(Mesh *mesh, unsigned int *data, int data_len, int dimensions); +void mesh_storeb(Mesh *mesh, unsigned char *data, int data_len, int dimensions); +void mesh_finish(void); +void mesh_bind(Mesh *mesh); +void mesh_unbind(Mesh *mesh); +void mesh_draw(Mesh *mesh); +void mesh_free(Mesh *mesh); diff --git a/src/shader.c b/src/shader.c new file mode 100644 index 0000000..eefcfc2 --- /dev/null +++ b/src/shader.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include + +#include "voxel.h" +#include "shader.h" + +static char *read_file(const char *filename) +{ + FILE *file; + long length, read; + char *buffer; + + file = fopen(filename, "r"); + if (file == NULL) { + ERROR("could not read file: %s", filename); + return NULL; + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + fseek(file, 0, SEEK_SET); + + buffer = malloc(length + 1); + read = fread(buffer, 1, length, file); + buffer[length] = 0; + + if (read < length) { + ERROR("could not read file: %s", filename); + free(buffer); + return NULL; + } + + fclose(file); + return buffer; +} + +static void print_shader_log(const char *filename, GLuint id) +{ + GLint log_len; + char *log; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_len); + log = malloc(log_len + 1); + glGetShaderInfoLog(id, log_len, &log_len, log); + log[log_len] = 0; + ERROR("failed to compile shader: '%s'\n%s", filename, log); + free(log); +} + +static int compile_shader(GLuint *out, const char *filename, const char *code, GLenum type) +{ + GLuint id; + GLint status; + int code_len; + + id = glCreateShader(type); + code_len = strlen(code); + glShaderSource(id, 1, &code, &code_len); + glCompileShader(id); + glGetShaderiv(id, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + print_shader_log(filename, id); + glDeleteShader(id); + return 1; + } + + *out = id; + return 0; +} + +static void parse_bind_attributes(Shader *shader, char *code) +{ + char *line, *last_line; + char *token, *last_token; + int attribute = 0; + + line = strtok_r(code, "\n", &last_line); + for (; line != NULL; line = strtok_r(NULL, "\n", &last_line)) { + token = strtok_r(line, " \t", &last_token); + if (strcmp(token, "in") != 0) + continue; + + token = strtok_r(NULL, " \t", &last_token); + token = strtok_r(NULL, " \t", &last_token); + *strchr(token, ';') = 0; + + glBindAttribLocation(shader->program_id, attribute, token); + attribute++; + } +} + +Shader *shader_init(const char *vertex_file, const char *fragment_file) +{ + Shader *shader; + char *vertex, *fragment; + + shader = malloc(sizeof(Shader)); + memset(shader, 0, sizeof(Shader)); + + // read shader code from file + vertex = read_file(vertex_file); + fragment = read_file(fragment_file); + if (!vertex || !fragment) + goto failure; + + // compile shaders + if (compile_shader(&shader->vertex_id, vertex_file, vertex, GL_VERTEX_SHADER)) + goto failure; + if (compile_shader(&shader->fragment_id, fragment_file, fragment, + GL_FRAGMENT_SHADER)) + goto failure; + + shader->program_id = glCreateProgram(); + glAttachShader(shader->program_id, shader->vertex_id); + glAttachShader(shader->program_id, shader->fragment_id); + parse_bind_attributes(shader, vertex); + glLinkProgram(shader->program_id); + glValidateProgram(shader->program_id); + + if (vertex) + free(vertex); + if (fragment) + free(fragment); + + return shader; + +failure: + if (vertex) + free(vertex); + if (fragment) + free(fragment); + free(shader); + return NULL; +} + +void shader_bind(Shader *shader) +{ + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CW); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(shader->program_id); +} + +void shader_unbind(void) +{ + glDisable(GL_CULL_FACE); + glUseProgram(0); +} + +void shader_free(Shader *shader) +{ + glDetachShader(shader->program_id, shader->vertex_id); + glDetachShader(shader->program_id, shader->fragment_id); + glDeleteShader(shader->vertex_id); + glDeleteShader(shader->fragment_id); + glDeleteProgram(shader->program_id); + free(shader); +} + +GLint shader_uniform_location(Shader *shader, const char *name) +{ + return glGetUniformLocation(shader->program_id, name); +} + +void shader_loadf(GLint location, float value) +{ + glUniform1f(location, value); +} + +void shader_loadi(GLint location, int value) +{ + glUniform1i(location, value); +} + +void shader_loadv3f(GLint location, vec3 value) +{ + glUniform3f(location, value[0], value[1], value[2]); +} + +void shader_loadm4f(GLint location, mat4 value) +{ + glUniformMatrix4fv(location, 1, GL_FALSE, (float *)value); +} diff --git a/src/shader.h b/src/shader.h new file mode 100644 index 0000000..931dd33 --- /dev/null +++ b/src/shader.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +typedef struct { + GLuint program_id; + GLuint vertex_id; + GLuint fragment_id; + int attributes; +} Shader; + +Shader *shader_init(const char *vertexFile, const char *fragmentFile); +void shader_bind(Shader *shader); +void shader_unbind(void); +void shader_free(Shader *shader); + +GLint shader_uniform_location(Shader *shader, const char *name); +void shader_loadf(GLint location, float value); +void shader_loadi(GLint location, int value); +void shader_loadv3f(GLint location, vec3 value); +void shader_loadm4f(GLint location, mat4 value); diff --git a/src/voxel.h b/src/voxel.h new file mode 100644 index 0000000..9a3b237 --- /dev/null +++ b/src/voxel.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include "mesh.h" + +#define LOG(level, ...) \ + do { \ + printf("%s", level); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } while (0) + +#define INFO(...) LOG("INFO ", __VA_ARGS__) +#define WARN(...) LOG("WARN ", __VA_ARGS__) +#define ERROR(...) LOG("ERROR ", __VA_ARGS__) + +typedef enum : char { + POS_X = 0, + NEG_X = 1, + POS_Y = 2, + NEG_Y = 3, + POS_Z = 4, + NEG_Z = 5, +} Face; + +typedef enum : char { AIR = 0, DIRT } Block; + +#define CHUNK_SIZE 32 + +typedef struct { + int x; + int y; + int z; + Block blocks[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE]; +} Chunk; + +Chunk *chunk_init(int x, int y, int z); +void chunk_free(Chunk *chunk); +void chunk_generate(Chunk *chunk); +Mesh chunk_mesh(Chunk *chunk); +Block chunk_at(Chunk *chunk, int x, int y, int z); + +typedef struct { + vec3 position; + vec3 rotation; + int fov; + float near; + float far; + float look_speed; + float move_speed; +} Camera; + +Camera camera_init(void); +void camera_proj(Camera *camera, mat4 proj); +void camera_view(Camera *camera, mat4 view); +void camera_proj_view(Camera *camera, mat4 proj_view); +void camera_update(Camera *camera); diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..c7e83c6 --- /dev/null +++ b/src/window.c @@ -0,0 +1,147 @@ +#include +#include +#include + +#include "voxel.h" +#include "window.h" + +Window window; +double delta_time; + +static void key_callback(GLFWwindow *, int key, int, int action, int) +{ + switch (action) { + case GLFW_PRESS: + window.key_down[key] = true; + window.key_pressed[key] = true; + break; + case GLFW_RELEASE: + window.key_down[key] = false; + break; + } +} + +static void mouse_move_callback(GLFWwindow *, double xpos, double ypos) +{ + window.mouse_x_last = window.mouse_x; + window.moues_y_last = window.mouse_y; + window.mouse_x = xpos; + window.mouse_y = ypos; +} + +static void mouse_button_callback(GLFWwindow *, int button, int action, int) +{ + switch (action) { + case GLFW_PRESS: + window.btn_down[button] = true; + window.btn_pressed[button] = true; + break; + case GLFW_RELEASE: + window.btn_down[button] = false; + break; + } +} + +static void framebuffer_callback(GLFWwindow *, int width, int height) +{ + window.width = width; + window.height = height; + glViewport(0, 0, width, height); +} + +int window_init(void) +{ + if (glfwInit() == GLFW_FALSE) { + ERROR("GLFW failed to initalize"); + return 1; + } + + memset(&window, 0, sizeof(Window)); + delta_time = 0; + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + window.monitor = glfwGetPrimaryMonitor(); + window.window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, + window.monitor, NULL); + if (window.window == NULL) { + ERROR("failed to create window"); + return 1; + } + + glfwMakeContextCurrent(window.window); + glfwSwapInterval(0); + + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + ERROR("GLEW failed to initalize"); + return 1; + } + + glfwSetKeyCallback(window.window, key_callback); + glfwSetCursorPosCallback(window.window, mouse_move_callback); + glfwSetMouseButtonCallback(window.window, mouse_button_callback); + glfwSetFramebufferSizeCallback(window.window, framebuffer_callback); + + return 0; +} + +bool window_closed(void) +{ + return glfwWindowShouldClose(window.window) == GLFW_TRUE; +} + +void window_update(void) +{ + // reset "pressed" + memset(&window.key_pressed, 0, sizeof(window.key_pressed)); + memset(&window.btn_pressed, 0, sizeof(window.btn_pressed)); + + // update delta time + double time = glfwGetTime(); + delta_time = time - window.last_time; + window.last_time = time; + + // check for errors + GLenum error; + while ((error = glGetError()) != GL_NO_ERROR) + ERROR("OpenGL error: %d", error); + + glfwPollEvents(); +} + +void window_swap(void) +{ + glfwSwapBuffers(window.window); +} + +void window_close(void) +{ + glfwDestroyWindow(window.window); + glfwTerminate(); +} + +int window_grab_cursor(void); +int window_release_cursor(void); + +bool key_down(int key) +{ + return window.key_down[key]; +} + +bool key_pressed(int key) +{ + return window.key_pressed[key]; +} + +bool btn_down(int btn) +{ + return window.btn_down[btn]; +} + +bool btn_pressed(int btn) +{ + return window.btn_pressed[btn]; +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..3690781 --- /dev/null +++ b/src/window.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define WINDOW_TITLE "Voxel Engine" + +typedef struct { + // screen size + int width; + int height; + // input + bool key_down[GLFW_KEY_LAST]; + bool key_pressed[GLFW_KEY_LAST]; + bool btn_down[GLFW_MOUSE_BUTTON_LAST]; + bool btn_pressed[GLFW_MOUSE_BUTTON_LAST]; + // cursor + double mouse_x; + double mouse_y; + double mouse_x_last; + double moues_y_last; + // time + double last_time; + // glfw + GLFWwindow *window; + GLFWmonitor *monitor; +} Window; + +extern Window window; +extern double delta_time; + +int window_init(void); +bool window_closed(void); +void window_update(void); +void window_swap(void); +void window_close(void); + +int window_grab_cursor(void); +int window_release_cursor(void); + +bool key_down(int key); +bool key_pressed(int key); + +bool btn_down(int button); +bool btn_pressed(int button); -- cgit v1.2.3-freya