summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format107
-rw-r--r--.gitignore1
-rw-r--r--Makefile42
-rwxr-xr-xassets/fragment.glsl22
-rwxr-xr-xassets/vertex.glsl14
-rw-r--r--compile_flags.txt5
-rw-r--r--flake.lock61
-rw-r--r--flake.nix47
-rw-r--r--src/camera.c146
-rw-r--r--src/chunk.c138
-rw-r--r--src/cube.h60
-rw-r--r--src/list.c71
-rw-r--r--src/list.h26
-rw-r--r--src/main.c50
-rw-r--r--src/mesh.c78
-rw-r--r--src/mesh.h23
-rw-r--r--src/shader.c186
-rw-r--r--src/shader.h22
-rw-r--r--src/voxel.h59
-rw-r--r--src/window.c147
-rw-r--r--src/window.h46
21 files changed, 1351 insertions, 0 deletions
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 <freya@freyacat.org>
+
+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 <math.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <cglm/cglm.h>
+
+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 <stdlib.h>
+#include <string.h>
+
+#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 <GLFW/glfw3.h>
+
+#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 <GL/glew.h>
+
+#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 <GL/gl.h>
+
+#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 <GL/glew.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <GL/gl.h>
+#include <cglm/cglm.h>
+
+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 <cglm/cglm.h>
+#include <stdio.h>
+
+#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 <GL/glew.h>
+#include <GLFW/glfw3.h>
+#include <string.h>
+
+#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 <GLFW/glfw3.h>
+
+#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);