Stub of a game
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
SRC=src/math_3d.c src/main.c src/mesh.c src/program.c src/resources.c src/camera.c \
|
||||
src/chunk/chunk.c src/chunk/noise.c src/chunk/chunkmesh.c src/flags.c
|
||||
|
||||
OBJ=$(SRC:.c=.o)
|
||||
CFLAGS=-g -Werror -Wextra -Wall `pkg-config --cflags sdl3 gl`
|
||||
LDFLAGS=`pkg-config --libs sdl3 gl` -lm
|
||||
TARGET=wschod
|
||||
CC=gcc
|
||||
LD=gcc
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(LD) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $^ -o $@ $(CFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) $(TARGET) $(OBJ)
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 100
|
||||
|
||||
precision lowp float;
|
||||
|
||||
varying vec2 f_uv;
|
||||
varying float f_height;
|
||||
|
||||
void main ()
|
||||
{
|
||||
gl_FragColor = vec4 (f_uv.x, 1.0, f_uv.y, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#version 100
|
||||
|
||||
attribute vec3 position;
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
|
||||
varying vec2 f_uv;
|
||||
varying float f_height;
|
||||
|
||||
void main ()
|
||||
{
|
||||
f_uv = position.xy / 8.0;
|
||||
f_height = position.y;
|
||||
|
||||
gl_Position = projection * view * vec4 (position, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
void main ()
|
||||
{
|
||||
gl_FragColor = vec4 (0.5, 1.0, 0.5, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 100
|
||||
|
||||
attribute vec4 position;
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
|
||||
void main ()
|
||||
{
|
||||
gl_Position = projection * view * position;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
let
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.05";
|
||||
pkgs = import nixpkgs { config = {}; overlays = []; };
|
||||
in
|
||||
|
||||
|
||||
pkgs.mkShellNoCC {
|
||||
packages = with pkgs; [
|
||||
gcc
|
||||
pkg-config
|
||||
sdl3
|
||||
gdb
|
||||
cglm
|
||||
glew
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "camera.h"
|
||||
|
||||
const vec3_t CAMERA_UP = { 0.0f, 1.0f, 0.0f };
|
||||
|
||||
Camera
|
||||
Camera_default ()
|
||||
{
|
||||
Camera cam = {
|
||||
.position = { 0.0f, 0.0f, 0.0f },
|
||||
.rotation = { 0.0f, 0.0f, 0.0f },
|
||||
.fov = 80,
|
||||
.near = 0.1,
|
||||
.far = 1000.0
|
||||
};
|
||||
|
||||
return cam;
|
||||
}
|
||||
|
||||
vec3_t
|
||||
Camera_get_direction (Camera *camera)
|
||||
{
|
||||
return v3_norm
|
||||
(vec3
|
||||
(sinf (camera->rotation.x) * cosf (camera->rotation.y),
|
||||
sinf (camera->rotation.y),
|
||||
cosf (camera->rotation.x) * cosf (camera->rotation.y)));
|
||||
}
|
||||
|
||||
vec3_t
|
||||
Camera_get_right (Camera *camera)
|
||||
{
|
||||
return v3_norm (v3_cross (Camera_get_direction (camera), CAMERA_UP));
|
||||
}
|
||||
|
||||
mat4_t
|
||||
Camera_get_view (Camera *camera)
|
||||
{
|
||||
return m4_look_at
|
||||
(camera->position, v3_add (camera->position, Camera_get_direction (camera)),
|
||||
CAMERA_UP);
|
||||
}
|
||||
|
||||
mat4_t
|
||||
Camera_get_projection (Camera *camera, float aspect)
|
||||
{
|
||||
return m4_perspective
|
||||
(camera->fov, aspect, camera->near, camera->far);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "math_3d.h"
|
||||
#include "util.h"
|
||||
|
||||
typedef struct Camera
|
||||
{
|
||||
vec3_t position;
|
||||
vec3_t rotation;
|
||||
int fov;
|
||||
float near;
|
||||
float far;
|
||||
} Camera;
|
||||
|
||||
Camera Camera_default ();
|
||||
|
||||
vec3_t Camera_get_direction (Camera *camera);
|
||||
vec3_t Camera_get_right (Camera *camera);
|
||||
mat4_t Camera_get_view (Camera *camera);
|
||||
mat4_t Camera_get_projection (Camera *camera, float aspect);
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "chunk.h"
|
||||
|
||||
Chunk
|
||||
Chunk_new ()
|
||||
{
|
||||
Chunk chunk;
|
||||
chunk.heightmap = malloc (sizeof (float) * CHUNK_SIZE);
|
||||
chunk.materialmap = malloc (sizeof (char) * CHUNK_SIZE);
|
||||
INFO ("Created new Chunk. Memory size: %zu bytes",
|
||||
sizeof (float) * CHUNK_SIZE + sizeof (char) * CHUNK_SIZE);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void
|
||||
Chunk_noise (Chunk *chunk, int xoffset, int yoffset)
|
||||
{
|
||||
for (int y = 0; y < CHUNK_DIM; ++y)
|
||||
{
|
||||
for (int x = 0; x < CHUNK_DIM; ++x)
|
||||
{
|
||||
float val = noise2 ((x + xoffset * CHUNK_DIM), (y + yoffset * CHUNK_DIM)) * 8;
|
||||
chunk->heightmap[INDEX (x, y)] = val;
|
||||
chunk->materialmap[INDEX (x, y)] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Chunk_free (Chunk *chunk)
|
||||
{
|
||||
free (chunk->heightmap);
|
||||
free (chunk->materialmap);
|
||||
}
|
||||
|
||||
void
|
||||
Chunk_heightmap_print (Chunk *chunk)
|
||||
{
|
||||
INFO ("== Chunk heightmap ==");
|
||||
|
||||
for (int y = 0; y < CHUNK_DIM; ++y)
|
||||
{
|
||||
char row[CHUNK_DIM+1];
|
||||
|
||||
for (int x = 0; x < CHUNK_DIM; ++x)
|
||||
{
|
||||
row[x] = (int) chunk->heightmap[INDEX (x, y)] + 48;
|
||||
}
|
||||
|
||||
row[CHUNK_DIM] = '\0';
|
||||
INFO ("%s", row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../util.h"
|
||||
#include "noise.h"
|
||||
|
||||
#define CHUNK_DIM 16
|
||||
#define CHUNK_SIZE (CHUNK_DIM*CHUNK_DIM)
|
||||
|
||||
#define INDEX(x, y) (CHUNK_DIM * y + x)
|
||||
|
||||
typedef struct Chunk
|
||||
{
|
||||
float *heightmap;
|
||||
char *materialmap;
|
||||
} Chunk;
|
||||
|
||||
Chunk Chunk_new ();
|
||||
void Chunk_noise (Chunk *chunk, int xoffset, int yoffset);
|
||||
void Chunk_free (Chunk *chunk);
|
||||
void Chunk_heightmap_print (Chunk *chunk);
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "chunkmesh.h"
|
||||
|
||||
extern GameFlags game_flags;
|
||||
|
||||
ChunkMesh
|
||||
ChunkMesh_from_Chunk_and_neighbors (Chunk *chunk, Chunk *nd, Chunk *nr, Chunk *nc)
|
||||
/* nd --- down neighbor; nr --- right neighbor; nc --- corner neighbor */
|
||||
{
|
||||
vec3_t *positions = malloc (sizeof (vec3_t) * CHUNKMESH_SIZE);
|
||||
unsigned int *indices = malloc (sizeof (unsigned int) * CHUNKMESH_SIZE * 6);
|
||||
|
||||
int i_i = 0;
|
||||
for (int y = 0; y < CHUNKMESH_DIM; ++y)
|
||||
{
|
||||
for (int x = 0; x < CHUNKMESH_DIM; ++x)
|
||||
{
|
||||
float height = chunk->heightmap[INDEX (x, y)];
|
||||
|
||||
if (x == CHUNK_DIM && y < CHUNKMESH_DIM && nr != NULL)
|
||||
{
|
||||
height = nr->heightmap[INDEX (0, y)];
|
||||
}
|
||||
|
||||
if (y == CHUNK_DIM && x < CHUNKMESH_DIM && nd != NULL)
|
||||
{
|
||||
height = nd->heightmap[INDEX (x, 0)];
|
||||
}
|
||||
|
||||
if (x == CHUNK_DIM && y == CHUNK_DIM && nc != NULL)
|
||||
{
|
||||
height = nc->heightmap[INDEX (0, 0)];
|
||||
}
|
||||
|
||||
positions[CMINDEX (x, y)] = vec3 (x, height, y);
|
||||
|
||||
if (x < CHUNK_DIM && y < CHUNK_DIM)
|
||||
{
|
||||
int i0 = CMINDEX (x, y);
|
||||
int i1 = CMINDEX (x + 1, y);
|
||||
int i2 = CMINDEX (x, y + 1);
|
||||
int i3 = CMINDEX (x + 1, y + 1);
|
||||
|
||||
/*
|
||||
i0----i1
|
||||
| |
|
||||
| |
|
||||
i2----i3
|
||||
*/
|
||||
|
||||
indices[i_i++] = i0;
|
||||
indices[i_i++] = i2;
|
||||
indices[i_i++] = i3;
|
||||
indices[i_i++] = i0;
|
||||
indices[i_i++] = i3;
|
||||
indices[i_i++] = i1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChunkMesh mesh;
|
||||
glGenBuffers (1, &mesh.pbo);
|
||||
glGenBuffers (1, &mesh.ebo);
|
||||
|
||||
glBindBuffer (GL_ARRAY_BUFFER, mesh.pbo);
|
||||
glBufferData (GL_ARRAY_BUFFER, sizeof (vec3_t) * CHUNKMESH_SIZE, positions, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray (0);
|
||||
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, sizeof (vec3_t), 0);
|
||||
|
||||
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, mesh.ebo);
|
||||
glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (unsigned int) * CHUNKMESH_SIZE * 6, indices, GL_STATIC_DRAW);
|
||||
|
||||
free (positions);
|
||||
free (indices);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void
|
||||
ChunkMesh_render (ChunkMesh *mesh)
|
||||
{
|
||||
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, mesh->ebo);
|
||||
|
||||
if (game_flags.wireframe_enabled)
|
||||
{
|
||||
glDrawElements (GL_LINES, CHUNKMESH_SIZE * 6, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
glDrawElements (GL_TRIANGLES, CHUNKMESH_SIZE * 6, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_opengles2.h>
|
||||
|
||||
#include "../flags.h"
|
||||
#include "../math_3d.h"
|
||||
#include "chunk.h"
|
||||
|
||||
#define CHUNKMESH_DIM (CHUNK_SIZE+1)
|
||||
#define CHUNKMESH_SIZE (CHUNKMESH_DIM*CHUNKMESH_DIM)
|
||||
#define CMINDEX(x, y) (CHUNKMESH_DIM * y + x)
|
||||
|
||||
typedef struct ChunkMesh
|
||||
{
|
||||
unsigned int pbo;
|
||||
/* Element Buffer Object, the indices. */
|
||||
unsigned int ebo;
|
||||
} ChunkMesh;
|
||||
|
||||
ChunkMesh ChunkMesh_from_Chunk_and_neighbors (Chunk *chunk, Chunk *nd, Chunk *nr, Chunk *nc);
|
||||
void ChunkMesh_render (ChunkMesh *mesh);
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "noise.h"
|
||||
|
||||
static u_int8_t *perm;
|
||||
|
||||
void
|
||||
noise_seed (int seed)
|
||||
{
|
||||
srand (seed);
|
||||
perm = malloc (sizeof (u_int8_t) * 256);
|
||||
|
||||
for (size_t i = 0; i < 256; ++i) {
|
||||
perm[i] = i;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 255; ++i) {
|
||||
size_t j = i + rand() / (RAND_MAX / (256 - i) + 1);
|
||||
u_int8_t t = perm[j];
|
||||
perm[j] = perm[i];
|
||||
perm[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
float
|
||||
noise2 (float x, float y)
|
||||
{
|
||||
int X = (int) FASTFLOOR(x) & 0xff;
|
||||
int Y = (int) FASTFLOOR(y) & 0xff;
|
||||
x -= FASTFLOOR(x);
|
||||
y -= FASTFLOOR(y);
|
||||
float u = FADE(x);
|
||||
float v = FADE(y);
|
||||
int A = (perm[X] + Y) & 0xff;
|
||||
int B = (perm[X + 1] + Y) & 0xff;
|
||||
|
||||
return LERP
|
||||
(v, LERP
|
||||
(u, GRAD2
|
||||
(perm[A], x, y),
|
||||
GRAD2(perm[B], x - 1, y)),
|
||||
LERP(u, GRAD2
|
||||
(perm[A + 1], x, y - 1),
|
||||
GRAD2(perm[B + 1], x - 1, y - 1)));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define FADE(t) (((6 * t - 15) * t + 10) * t * t * t)
|
||||
#define LERP(t, a, b) (a + t * (b - a))
|
||||
#define FASTFLOOR(x) (((int) (x) < (x)) ? ((int) x) : ((int) x - 1))
|
||||
|
||||
static inline float
|
||||
GRAD2 (int hash, float x, float y)
|
||||
{
|
||||
return ((hash & 1) ? -x : x) + ((hash & 2) ? -y : y);
|
||||
}
|
||||
|
||||
void noise_seed (int seed);
|
||||
float noise2 (float x, float y);
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "flags.h"
|
||||
|
||||
GameFlags game_flags = { 0 };
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct GameFlags
|
||||
{
|
||||
/* Volatile state flags, zeroed at start. */
|
||||
int in_main_menu: 1;
|
||||
int in_pause_menu: 1;
|
||||
int in_game: 1;
|
||||
|
||||
int can_free_look: 1;
|
||||
int moving_forward: 1;
|
||||
int moving_backward: 1;
|
||||
int moving_left: 1;
|
||||
int moving_right: 1;
|
||||
|
||||
int wireframe_enabled: 1;
|
||||
/* Saved state flags, loaded from game save/map. */
|
||||
} GameFlags;
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
#define SDL_MAIN_USE_CALLBACKS 1
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "chunk/chunkmesh.h"
|
||||
#include "state.h"
|
||||
#include "flags.h"
|
||||
#include "camera.h"
|
||||
#include "mesh.h"
|
||||
#include "resources.h"
|
||||
|
||||
/* TODO: switch back to cglm.h, since it's faster or something */
|
||||
|
||||
static SDL_Window *window = NULL;
|
||||
static SDL_GLContext context = NULL;
|
||||
|
||||
extern GameState game_state;
|
||||
extern GameFlags game_flags;
|
||||
extern Resources resources;
|
||||
|
||||
static ChunkMesh test_chunkmesh;
|
||||
static Camera camera;
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppInit (void **appstate, int argc, char **argv)
|
||||
{
|
||||
(void) appstate; (void) argc; (void) argv;
|
||||
|
||||
SDL_SetAppMetadata (GAME_NAME, GAME_VERSION, "eur.chopininteractive.wschod-game");
|
||||
|
||||
if (!SDL_Init (SDL_INIT_VIDEO))
|
||||
{
|
||||
FATAL ("Couldn't initialize SDL: %s", SDL_GetError ());
|
||||
}
|
||||
|
||||
window = SDL_CreateWindow (GAME_NAME, 640, 480, SDL_WINDOW_OPENGL);
|
||||
|
||||
if (!window)
|
||||
{
|
||||
FATAL ("Couldn't create window: %s", SDL_GetError ());
|
||||
}
|
||||
|
||||
/* We use OpenGL ES 2.0. It should support everything, and it's more
|
||||
than enough for the game. */
|
||||
SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
|
||||
context = SDL_GL_CreateContext (window);
|
||||
|
||||
if (!context)
|
||||
{
|
||||
FATAL ("Couldn't create OpenGL context: %s", SDL_GetError ());
|
||||
}
|
||||
|
||||
SDL_GL_MakeCurrent (window, context);
|
||||
SDL_SetWindowRelativeMouseMode (window, true);
|
||||
|
||||
INFO ("Renderer: %s", glGetString (GL_RENDERER));
|
||||
INFO ("OpenGL version: %s", glGetString (GL_VERSION));
|
||||
INFO ("GLSL version: %s", glGetString (GL_SHADING_LANGUAGE_VERSION));
|
||||
INFO ("Max vertex attributes: %d", GL_MAX_VERTEX_ATTRIBS);
|
||||
|
||||
glViewport(0, 0, 640, 480);
|
||||
glClearColor (0, 0.3, 0.8, 1);
|
||||
|
||||
/* Initialize and load resources. */
|
||||
Resources_init ();
|
||||
noise_seed (1337);
|
||||
|
||||
/* Generate a test chunk. */
|
||||
Chunk chunk = Chunk_new ();
|
||||
Chunk_noise (&chunk, 0, 0);
|
||||
test_chunkmesh = ChunkMesh_from_Chunk_and_neighbors (&chunk, NULL, NULL, NULL);
|
||||
|
||||
program_use (resources.programs[1]);
|
||||
program_print (resources.programs[1]);
|
||||
|
||||
/* Set up game state. */
|
||||
game_state.last_time = SDL_GetTicks ();
|
||||
|
||||
/* Set some basic game flags for testing. */
|
||||
game_flags.can_free_look = 1;
|
||||
|
||||
/* Initialize the default camera. */
|
||||
camera = Camera_default ();
|
||||
camera.position.z = -1.0;
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppEvent (void *appstate, SDL_Event *event)
|
||||
{
|
||||
(void) appstate;
|
||||
|
||||
if (event->type == SDL_EVENT_QUIT)
|
||||
{
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
if (event->type == SDL_EVENT_MOUSE_MOTION && game_flags.can_free_look)
|
||||
{
|
||||
camera.rotation.x -= event->motion.xrel / 100.f;
|
||||
camera.rotation.y -= event->motion.yrel / 100.f;
|
||||
camera.rotation.y =
|
||||
CLAMP (camera.rotation.y, -M_PI / 2 + 0.025, M_PI / 2 - 0.025);
|
||||
}
|
||||
|
||||
if (event->type == SDL_EVENT_KEY_DOWN)
|
||||
{
|
||||
switch (event->key.key)
|
||||
{
|
||||
case SDLK_W:
|
||||
game_flags.moving_forward = 1;
|
||||
break;
|
||||
case SDLK_A:
|
||||
game_flags.moving_left = 1;
|
||||
break;
|
||||
case SDLK_S:
|
||||
game_flags.moving_backward = 1;
|
||||
break;
|
||||
case SDLK_D:
|
||||
game_flags.moving_right = 1;
|
||||
break;
|
||||
case SDLK_F1:
|
||||
game_flags.wireframe_enabled = !game_flags.wireframe_enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type == SDL_EVENT_KEY_UP)
|
||||
{
|
||||
switch (event->key.key)
|
||||
{
|
||||
case SDLK_W:
|
||||
game_flags.moving_forward = 0;
|
||||
break;
|
||||
case SDLK_A:
|
||||
game_flags.moving_left = 0;
|
||||
break;
|
||||
case SDLK_S:
|
||||
game_flags.moving_backward = 0;
|
||||
break;
|
||||
case SDLK_D:
|
||||
game_flags.moving_right = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppIterate (void *appstate)
|
||||
{
|
||||
(void) appstate;
|
||||
|
||||
/* Caluclate stuff per frame. */
|
||||
int now = SDL_GetTicks ();
|
||||
game_state.delta_time = (now - game_state.last_time) / 1000.0;
|
||||
game_state.last_time = now;
|
||||
|
||||
if (game_flags.moving_forward)
|
||||
{
|
||||
camera.position = v3_add
|
||||
(camera.position, v3_muls
|
||||
(Camera_get_direction (&camera), game_state.delta_time));
|
||||
}
|
||||
|
||||
if (game_flags.moving_backward)
|
||||
{
|
||||
camera.position = v3_sub
|
||||
(camera.position, v3_muls
|
||||
(Camera_get_direction (&camera), game_state.delta_time));
|
||||
}
|
||||
|
||||
if (game_flags.moving_right)
|
||||
{
|
||||
camera.position = v3_add
|
||||
(camera.position, v3_muls
|
||||
(Camera_get_right (&camera), game_state.delta_time));
|
||||
}
|
||||
|
||||
if (game_flags.moving_left)
|
||||
{
|
||||
camera.position = v3_sub
|
||||
(camera.position, v3_muls
|
||||
(Camera_get_right (&camera), game_state.delta_time));
|
||||
}
|
||||
|
||||
/* Render stuff. */
|
||||
|
||||
glClear (GL_COLOR_BUFFER_BIT);
|
||||
|
||||
mat4_t view = Camera_get_view (&camera);
|
||||
mat4_t projection = Camera_get_projection (&camera, 640.0 / 480.0);
|
||||
|
||||
program_use (resources.programs[1]);
|
||||
program_set_mat4 (resources.programs[1], "view", view);
|
||||
program_set_mat4 (resources.programs[1], "projection", projection);
|
||||
ChunkMesh_render (&test_chunkmesh);
|
||||
|
||||
SDL_GL_SwapWindow (window);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void
|
||||
SDL_AppQuit (void *appstate, SDL_AppResult result)
|
||||
{
|
||||
(void) appstate; (void) result;
|
||||
Resources_free ();
|
||||
SDL_GL_DestroyContext (context);
|
||||
SDL_DestroyWindow (window);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#define MATH_3D_IMPLEMENTATION
|
||||
#include "math_3d.h"
|
||||
@@ -0,0 +1,627 @@
|
||||
/**
|
||||
|
||||
Math 3D v1.0
|
||||
By Stephan Soller <stephan.soller@helionweb.de> and Tobias Malmsheimer
|
||||
Licensed under the MIT license
|
||||
|
||||
Math 3D is a compact C99 library meant to be used with OpenGL. It provides basic
|
||||
3D vector and 4x4 matrix operations as well as functions to create transformation
|
||||
and projection matrices. The OpenGL binary layout is used so you can just upload
|
||||
vectors and matrices into shaders and work with them without any conversions.
|
||||
|
||||
It's an stb style single header file library. Define MATH_3D_IMPLEMENTATION
|
||||
before you include this file in *one* C file to create the implementation.
|
||||
|
||||
|
||||
QUICK NOTES
|
||||
|
||||
- If not explicitly stated by a parameter name all angles are in radians.
|
||||
- The matrices use column-major indices. This is the same as in OpenGL and GLSL.
|
||||
The matrix documentation below for details.
|
||||
- Matrices are passed by value. This is probably a bit inefficient but
|
||||
simplifies code quite a bit. Most operations will be inlined by the compiler
|
||||
anyway so the difference shouldn't matter that much. A matrix fits into 4 of
|
||||
the 16 SSE2 registers anyway. If profiling shows significant slowdowns the
|
||||
matrix type might change but ease of use is more important than every last
|
||||
percent of performance.
|
||||
- When combining matrices with multiplication the effects apply right to left.
|
||||
This is the convention used in mathematics and OpenGL. Source:
|
||||
https://en.wikipedia.org/wiki/Transformation_matrix#Composing_and_inverting_transformations
|
||||
Direct3D does it differently.
|
||||
- The `m4_mul_pos()` and `m4_mul_dir()` functions do a correct perspective
|
||||
divide (division by w) when necessary. This is a bit slower but ensures that
|
||||
the functions will properly work with projection matrices. If profiling shows
|
||||
this is a bottleneck special functions without perspective division can be
|
||||
added. But the normal multiplications should avoid any surprises.
|
||||
- The library consistently uses a right-handed coordinate system. The old
|
||||
`glOrtho()` broke that rule and `m4_ortho()` has be slightly modified so you
|
||||
can always think of right-handed cubes that are projected into OpenGLs
|
||||
normalized device coordinates.
|
||||
- Special care has been taken to document all complex operations and important
|
||||
sources. Most code is covered by test cases that have been manually calculated
|
||||
and checked on the whiteboard. Since indices and math code is prone to be
|
||||
confusing we used pair programming to avoid mistakes.
|
||||
|
||||
|
||||
FURTHER IDEARS
|
||||
|
||||
These are ideas for future work on the library. They're implemented as soon as
|
||||
there is a proper use case and we can find good names for them.
|
||||
|
||||
- bool v3_is_null(vec3_t v, float epsilon)
|
||||
To check if the length of a vector is smaller than `epsilon`.
|
||||
- vec3_t v3_length_default(vec3_t v, float default_length, float epsilon)
|
||||
Returns `default_length` if the length of `v` is smaller than `epsilon`.
|
||||
Otherwise same as `v3_length()`.
|
||||
- vec3_t v3_norm_default(vec3_t v, vec3_t default_vector, float epsilon)
|
||||
Returns `default_vector` if the length of `v` is smaller than `epsilon`.
|
||||
Otherwise the same as `v3_norm()`.
|
||||
- mat4_t m4_invert(mat4_t matrix)
|
||||
Matrix inversion that works with arbitrary matrices. `m4_invert_affine()` can
|
||||
already invert translation, rotation, scaling, mirroring, reflection and
|
||||
shearing matrices. So a general inversion might only be useful to invert
|
||||
projection matrices for picking. But with orthographic and perspective
|
||||
projection it's probably simpler to calculate the ray into the scene directly
|
||||
based on the screen coordinates.
|
||||
|
||||
|
||||
VERSION HISTORY
|
||||
|
||||
v1.0 2016-02-15 Initial release
|
||||
|
||||
**/
|
||||
|
||||
#ifndef MATH_3D_HEADER
|
||||
#define MATH_3D_HEADER
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
// Define PI directly because we would need to define the _BSD_SOURCE or
|
||||
// _XOPEN_SOURCE feature test macros to get it from math.h. That would be a
|
||||
// rather harsh dependency. So we define it directly if necessary.
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
|
||||
//
|
||||
// 3D vectors
|
||||
//
|
||||
// Use the `vec3()` function to create vectors. All other vector functions start
|
||||
// with the `v3_` prefix.
|
||||
//
|
||||
// The binary layout is the same as in GLSL and everything else (just 3 floats).
|
||||
// So you can just upload the vectors into shaders as they are.
|
||||
//
|
||||
|
||||
typedef struct { float x, y, z; } vec3_t;
|
||||
static inline vec3_t vec3(float x, float y, float z) { return (vec3_t){ x, y, z }; }
|
||||
|
||||
static inline vec3_t v3_add (vec3_t a, vec3_t b) { return (vec3_t){ a.x + b.x, a.y + b.y, a.z + b.z }; }
|
||||
static inline vec3_t v3_adds (vec3_t a, float s) { return (vec3_t){ a.x + s, a.y + s, a.z + s }; }
|
||||
static inline vec3_t v3_sub (vec3_t a, vec3_t b) { return (vec3_t){ a.x - b.x, a.y - b.y, a.z - b.z }; }
|
||||
static inline vec3_t v3_subs (vec3_t a, float s) { return (vec3_t){ a.x - s, a.y - s, a.z - s }; }
|
||||
static inline vec3_t v3_mul (vec3_t a, vec3_t b) { return (vec3_t){ a.x * b.x, a.y * b.y, a.z * b.z }; }
|
||||
static inline vec3_t v3_muls (vec3_t a, float s) { return (vec3_t){ a.x * s, a.y * s, a.z * s }; }
|
||||
static inline vec3_t v3_div (vec3_t a, vec3_t b) { return (vec3_t){ a.x / b.x, a.y / b.y, a.z / b.z }; }
|
||||
static inline vec3_t v3_divs (vec3_t a, float s) { return (vec3_t){ a.x / s, a.y / s, a.z / s }; }
|
||||
static inline float v3_length(vec3_t v) { return sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); }
|
||||
static inline vec3_t v3_norm (vec3_t v);
|
||||
static inline float v3_dot (vec3_t a, vec3_t b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
|
||||
static inline vec3_t v3_proj (vec3_t v, vec3_t onto);
|
||||
static inline vec3_t v3_cross (vec3_t a, vec3_t b);
|
||||
static inline float v3_angle_between(vec3_t a, vec3_t b);
|
||||
|
||||
|
||||
//
|
||||
// 4x4 matrices
|
||||
//
|
||||
// Use the `mat4()` function to create a matrix. You can write the matrix
|
||||
// members in the same way as you would write them on paper or on a whiteboard:
|
||||
//
|
||||
// mat4_t m = mat4(
|
||||
// 1, 0, 0, 7,
|
||||
// 0, 1, 0, 5,
|
||||
// 0, 0, 1, 3,
|
||||
// 0, 0, 0, 1
|
||||
// )
|
||||
//
|
||||
// This creates a matrix that translates points by vec3(7, 5, 3). All other
|
||||
// matrix functions start with the `m4_` prefix. Among them functions to create
|
||||
// identity, translation, rotation, scaling and projection matrices.
|
||||
//
|
||||
// The matrix is stored in column-major order, just as OpenGL expects. Members
|
||||
// can be accessed by indices or member names. When you write a matrix on paper
|
||||
// or on the whiteboard the indices and named members correspond to these
|
||||
// positions:
|
||||
//
|
||||
// | m[0][0] m[1][0] m[2][0] m[3][0] |
|
||||
// | m[0][1] m[1][1] m[2][1] m[3][1] |
|
||||
// | m[0][2] m[1][2] m[2][2] m[3][2] |
|
||||
// | m[0][3] m[1][3] m[2][3] m[3][3] |
|
||||
//
|
||||
// | m00 m10 m20 m30 |
|
||||
// | m01 m11 m21 m31 |
|
||||
// | m02 m12 m22 m32 |
|
||||
// | m03 m13 m23 m33 |
|
||||
//
|
||||
// The first index or number in a name denotes the column, the second the row.
|
||||
// So m[i][j] denotes the member in the ith column and the jth row. This is the
|
||||
// same as in GLSL (source: GLSL v1.3 specification, 5.6 Matrix Components).
|
||||
//
|
||||
|
||||
typedef union {
|
||||
// The first index is the column index, the second the row index. The memory
|
||||
// layout of nested arrays in C matches the memory layout expected by OpenGL.
|
||||
float m[4][4];
|
||||
// OpenGL expects the first 4 floats to be the first column of the matrix.
|
||||
// So we need to define the named members column by column for the names to
|
||||
// match the memory locations of the array elements.
|
||||
struct {
|
||||
float m00, m01, m02, m03;
|
||||
float m10, m11, m12, m13;
|
||||
float m20, m21, m22, m23;
|
||||
float m30, m31, m32, m33;
|
||||
};
|
||||
} mat4_t;
|
||||
|
||||
static inline mat4_t mat4(
|
||||
float m00, float m10, float m20, float m30,
|
||||
float m01, float m11, float m21, float m31,
|
||||
float m02, float m12, float m22, float m32,
|
||||
float m03, float m13, float m23, float m33
|
||||
);
|
||||
|
||||
static inline mat4_t m4_identity ();
|
||||
static inline mat4_t m4_translation (vec3_t offset);
|
||||
static inline mat4_t m4_scaling (vec3_t scale);
|
||||
static inline mat4_t m4_rotation_x (float angle_in_rad);
|
||||
static inline mat4_t m4_rotation_y (float angle_in_rad);
|
||||
static inline mat4_t m4_rotation_z (float angle_in_rad);
|
||||
mat4_t m4_rotation (float angle_in_rad, vec3_t axis);
|
||||
|
||||
mat4_t m4_ortho (float left, float right, float bottom, float top, float back, float front);
|
||||
mat4_t m4_perspective (float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance);
|
||||
mat4_t m4_look_at (vec3_t from, vec3_t to, vec3_t up);
|
||||
|
||||
static inline mat4_t m4_transpose (mat4_t matrix);
|
||||
static inline mat4_t m4_mul (mat4_t a, mat4_t b);
|
||||
mat4_t m4_invert_affine(mat4_t matrix);
|
||||
vec3_t m4_mul_pos (mat4_t matrix, vec3_t position);
|
||||
vec3_t m4_mul_dir (mat4_t matrix, vec3_t direction);
|
||||
|
||||
void m4_print (mat4_t matrix);
|
||||
void m4_printp (mat4_t matrix, int width, int precision);
|
||||
void m4_fprint (FILE* stream, mat4_t matrix);
|
||||
void m4_fprintp (FILE* stream, mat4_t matrix, int width, int precision);
|
||||
|
||||
|
||||
|
||||
//
|
||||
// 3D vector functions header implementation
|
||||
//
|
||||
|
||||
static inline vec3_t v3_norm(vec3_t v) {
|
||||
float len = v3_length(v);
|
||||
if (len > 0)
|
||||
return (vec3_t){ v.x / len, v.y / len, v.z / len };
|
||||
else
|
||||
return (vec3_t){ 0, 0, 0};
|
||||
}
|
||||
|
||||
static inline vec3_t v3_proj(vec3_t v, vec3_t onto) {
|
||||
return v3_muls(onto, v3_dot(v, onto) / v3_dot(onto, onto));
|
||||
}
|
||||
|
||||
static inline vec3_t v3_cross(vec3_t a, vec3_t b) {
|
||||
return (vec3_t){
|
||||
a.y * b.z - a.z * b.y,
|
||||
a.z * b.x - a.x * b.z,
|
||||
a.x * b.y - a.y * b.x
|
||||
};
|
||||
}
|
||||
|
||||
static inline float v3_angle_between(vec3_t a, vec3_t b) {
|
||||
return acosf( v3_dot(a, b) / (v3_length(a) * v3_length(b)) );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Matrix functions header implementation
|
||||
//
|
||||
|
||||
static inline mat4_t mat4(
|
||||
float m00, float m10, float m20, float m30,
|
||||
float m01, float m11, float m21, float m31,
|
||||
float m02, float m12, float m22, float m32,
|
||||
float m03, float m13, float m23, float m33
|
||||
) {
|
||||
return (mat4_t){
|
||||
.m[0][0] = m00, .m[1][0] = m10, .m[2][0] = m20, .m[3][0] = m30,
|
||||
.m[0][1] = m01, .m[1][1] = m11, .m[2][1] = m21, .m[3][1] = m31,
|
||||
.m[0][2] = m02, .m[1][2] = m12, .m[2][2] = m22, .m[3][2] = m32,
|
||||
.m[0][3] = m03, .m[1][3] = m13, .m[2][3] = m23, .m[3][3] = m33
|
||||
};
|
||||
}
|
||||
|
||||
static inline mat4_t m4_identity() {
|
||||
return mat4(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_translation(vec3_t offset) {
|
||||
return mat4(
|
||||
1, 0, 0, offset.x,
|
||||
0, 1, 0, offset.y,
|
||||
0, 0, 1, offset.z,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_scaling(vec3_t scale) {
|
||||
float x = scale.x, y = scale.y, z = scale.z;
|
||||
return mat4(
|
||||
x, 0, 0, 0,
|
||||
0, y, 0, 0,
|
||||
0, 0, z, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_rotation_x(float angle_in_rad) {
|
||||
float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
|
||||
return mat4(
|
||||
1, 0, 0, 0,
|
||||
0, c, -s, 0,
|
||||
0, s, c, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_rotation_y(float angle_in_rad) {
|
||||
float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
|
||||
return mat4(
|
||||
c, 0, s, 0,
|
||||
0, 1, 0, 0,
|
||||
-s, 0, c, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_rotation_z(float angle_in_rad) {
|
||||
float s = sinf(angle_in_rad), c = cosf(angle_in_rad);
|
||||
return mat4(
|
||||
c, -s, 0, 0,
|
||||
s, c, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
static inline mat4_t m4_transpose(mat4_t matrix) {
|
||||
return mat4(
|
||||
matrix.m00, matrix.m01, matrix.m02, matrix.m03,
|
||||
matrix.m10, matrix.m11, matrix.m12, matrix.m13,
|
||||
matrix.m20, matrix.m21, matrix.m22, matrix.m23,
|
||||
matrix.m30, matrix.m31, matrix.m32, matrix.m33
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplication of two 4x4 matrices.
|
||||
*
|
||||
* Implemented by following the row times column rule and illustrating it on a
|
||||
* whiteboard with the proper indices in mind.
|
||||
*
|
||||
* Further reading: https://en.wikipedia.org/wiki/Matrix_multiplication
|
||||
* But note that the article use the first index for rows and the second for
|
||||
* columns.
|
||||
*/
|
||||
static inline mat4_t m4_mul(mat4_t a, mat4_t b) {
|
||||
mat4_t result;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
for(int j = 0; j < 4; j++) {
|
||||
float sum = 0;
|
||||
for(int k = 0; k < 4; k++) {
|
||||
sum += a.m[k][j] * b.m[i][k];
|
||||
}
|
||||
result.m[i][j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // MATH_3D_HEADER
|
||||
|
||||
|
||||
#ifdef MATH_3D_IMPLEMENTATION
|
||||
|
||||
/**
|
||||
* Creates a matrix to rotate around an axis by a given angle. The axis doesn't
|
||||
* need to be normalized.
|
||||
*
|
||||
* Sources:
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
|
||||
*/
|
||||
mat4_t m4_rotation(float angle_in_rad, vec3_t axis) {
|
||||
vec3_t normalized_axis = v3_norm(axis);
|
||||
float x = normalized_axis.x, y = normalized_axis.y, z = normalized_axis.z;
|
||||
float c = cosf(angle_in_rad), s = sinf(angle_in_rad);
|
||||
|
||||
return mat4(
|
||||
c + x*x*(1-c), x*y*(1-c) - z*s, x*z*(1-c) + y*s, 0,
|
||||
y*x*(1-c) + z*s, c + y*y*(1-c), y*z*(1-c) - x*s, 0,
|
||||
z*x*(1-c) - y*s, z*y*(1-c) + x*s, c + z*z*(1-c), 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an orthographic projection matrix. It maps the right handed cube
|
||||
* defined by left, right, bottom, top, back and front onto the screen and
|
||||
* z-buffer. You can think of it as a cube you move through world or camera
|
||||
* space and everything inside is visible.
|
||||
*
|
||||
* This is slightly different from the traditional glOrtho() and from the linked
|
||||
* sources. These functions require the user to negate the last two arguments
|
||||
* (creating a left-handed coordinate system). We avoid that here so you can
|
||||
* think of this function as moving a right-handed cube through world space.
|
||||
*
|
||||
* The arguments are ordered in a way that for each axis you specify the minimum
|
||||
* followed by the maximum. Thats why it's bottom to top and back to front.
|
||||
*
|
||||
* Implementation details:
|
||||
*
|
||||
* To be more exact the right-handed cube is mapped into normalized device
|
||||
* coordinates, a left-handed cube where (-1 -1) is the lower left corner,
|
||||
* (1, 1) the upper right corner and a z-value of -1 is the nearest point and
|
||||
* 1 the furthest point. OpenGL takes it from there and puts it on the screen
|
||||
* and into the z-buffer.
|
||||
*
|
||||
* Sources:
|
||||
*
|
||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/dd373965(v=vs.85).aspx
|
||||
* https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
|
||||
*/
|
||||
mat4_t m4_ortho(float left, float right, float bottom, float top, float back, float front) {
|
||||
float l = left, r = right, b = bottom, t = top, n = front, f = back;
|
||||
float tx = -(r + l) / (r - l);
|
||||
float ty = -(t + b) / (t - b);
|
||||
float tz = -(f + n) / (f - n);
|
||||
return mat4(
|
||||
2 / (r - l), 0, 0, tx,
|
||||
0, 2 / (t - b), 0, ty,
|
||||
0, 0, 2 / (f - n), tz,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a perspective projection matrix for a camera.
|
||||
*
|
||||
* The camera is at the origin and looks in the direction of the negative Z axis.
|
||||
* `near_view_distance` and `far_view_distance` have to be positive and > 0.
|
||||
* They are distances from the camera eye, not values on an axis.
|
||||
*
|
||||
* `near_view_distance` can be small but not 0. 0 breaks the projection and
|
||||
* everything ends up at the max value (far end) of the z-buffer. Making the
|
||||
* z-buffer useless.
|
||||
*
|
||||
* The matrix is the same as `gluPerspective()` builds. The view distance is
|
||||
* mapped to the z-buffer with a reciprocal function (1/x). Therefore the z-buffer
|
||||
* resolution for near objects is very good while resolution for far objects is
|
||||
* limited.
|
||||
*
|
||||
* Sources:
|
||||
*
|
||||
* https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/
|
||||
*/
|
||||
mat4_t m4_perspective(float vertical_field_of_view_in_deg, float aspect_ratio, float near_view_distance, float far_view_distance) {
|
||||
float fovy_in_rad = vertical_field_of_view_in_deg / 180 * M_PI;
|
||||
float f = 1.0f / tanf(fovy_in_rad / 2.0f);
|
||||
float ar = aspect_ratio;
|
||||
float nd = near_view_distance, fd = far_view_distance;
|
||||
|
||||
return mat4(
|
||||
f / ar, 0, 0, 0,
|
||||
0, f, 0, 0,
|
||||
0, 0, (fd+nd)/(nd-fd), (2*fd*nd)/(nd-fd),
|
||||
0, 0, -1, 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a transformation matrix for a camera that looks from `from` towards
|
||||
* `to`. `up` defines the direction that's upwards for the camera. All three
|
||||
* vectors are given in world space and `up` doesn't need to be normalized.
|
||||
*
|
||||
* Sources: Derived on whiteboard.
|
||||
*
|
||||
* Implementation details:
|
||||
*
|
||||
* x, y and z are the right-handed base vectors of the cameras subspace.
|
||||
* x has to be normalized because the cross product only produces a normalized
|
||||
* output vector if both input vectors are orthogonal to each other. And up
|
||||
* probably isn't orthogonal to z.
|
||||
*
|
||||
* These vectors are then used to build a 3x3 rotation matrix. This matrix
|
||||
* rotates a vector by the same amount the camera is rotated. But instead we
|
||||
* need to rotate all incoming vertices backwards by that amount. That's what a
|
||||
* camera matrix is for: To move the world so that the camera is in the origin.
|
||||
* So we take the inverse of that rotation matrix and in case of an rotation
|
||||
* matrix this is just the transposed matrix. That's why the 3x3 part of the
|
||||
* matrix are the x, y and z vectors but written horizontally instead of
|
||||
* vertically.
|
||||
*
|
||||
* The translation is derived by creating a translation matrix to move the world
|
||||
* into the origin (thats translate by minus `from`). The complete lookat matrix
|
||||
* is then this translation followed by the rotation. Written as matrix
|
||||
* multiplication:
|
||||
*
|
||||
* lookat = rotation * translation
|
||||
*
|
||||
* Since we're right-handed this equals to first doing the translation and after
|
||||
* that doing the rotation. During that multiplication the rotation 3x3 part
|
||||
* doesn't change but the translation vector is multiplied with each rotation
|
||||
* axis. The dot product is just a more compact way to write the actual
|
||||
* multiplications.
|
||||
*/
|
||||
mat4_t m4_look_at(vec3_t from, vec3_t to, vec3_t up) {
|
||||
vec3_t z = v3_muls(v3_norm(v3_sub(to, from)), -1);
|
||||
vec3_t x = v3_norm(v3_cross(up, z));
|
||||
vec3_t y = v3_cross(z, x);
|
||||
|
||||
return mat4(
|
||||
x.x, x.y, x.z, -v3_dot(from, x),
|
||||
y.x, y.y, y.z, -v3_dot(from, y),
|
||||
z.x, z.y, z.z, -v3_dot(from, z),
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inverts an affine transformation matrix. That are translation, scaling,
|
||||
* mirroring, reflection, rotation and shearing matrices or any combination of
|
||||
* them.
|
||||
*
|
||||
* Implementation details:
|
||||
*
|
||||
* - Invert the 3x3 part of the 4x4 matrix to handle rotation, scaling, etc.
|
||||
* correctly (see source).
|
||||
* - Invert the translation part of the 4x4 matrix by multiplying it with the
|
||||
* inverted rotation matrix and negating it.
|
||||
*
|
||||
* When a 3D point is multiplied with a transformation matrix it is first
|
||||
* rotated and then translated. The inverted transformation matrix is the
|
||||
* inverse translation followed by the inverse rotation. Written as a matrix
|
||||
* multiplication (remember, the effect applies right to left):
|
||||
*
|
||||
* inv(matrix) = inv(rotation) * inv(translation)
|
||||
*
|
||||
* The inverse translation is a translation into the opposite direction, just
|
||||
* the negative translation. The rotation part isn't changed by that
|
||||
* multiplication but the translation part is multiplied by the inverse rotation
|
||||
* matrix. It's the same situation as with `m4_look_at()`. But since we don't
|
||||
* store the rotation matrix as 3D vectors we can't use the dot product and have
|
||||
* to write the matrix multiplication operations by hand.
|
||||
*
|
||||
* Sources for 3x3 matrix inversion:
|
||||
*
|
||||
* https://www.khanacademy.org/math/precalculus/precalc-matrices/determinants-and-inverses-of-large-matrices/v/inverting-3x3-part-2-determinant-and-adjugate-of-a-matrix
|
||||
*/
|
||||
mat4_t m4_invert_affine(mat4_t matrix) {
|
||||
// Create shorthands to access matrix members
|
||||
float m00 = matrix.m00, m10 = matrix.m10, m20 = matrix.m20, m30 = matrix.m30;
|
||||
float m01 = matrix.m01, m11 = matrix.m11, m21 = matrix.m21, m31 = matrix.m31;
|
||||
float m02 = matrix.m02, m12 = matrix.m12, m22 = matrix.m22, m32 = matrix.m32;
|
||||
|
||||
// Invert 3x3 part of the 4x4 matrix that contains the rotation, etc.
|
||||
// That part is called R from here on.
|
||||
|
||||
// Calculate cofactor matrix of R
|
||||
float c00 = m11*m22 - m12*m21, c10 = -(m01*m22 - m02*m21), c20 = m01*m12 - m02*m11;
|
||||
float c01 = -(m10*m22 - m12*m20), c11 = m00*m22 - m02*m20, c21 = -(m00*m12 - m02*m10);
|
||||
float c02 = m10*m21 - m11*m20, c12 = -(m00*m21 - m01*m20), c22 = m00*m11 - m01*m10;
|
||||
|
||||
// Caclculate the determinant by using the already calculated determinants
|
||||
// in the cofactor matrix.
|
||||
// Second sign is already minus from the cofactor matrix.
|
||||
float det = m00*c00 + m10*c10 + m20 * c20;
|
||||
if (fabsf(det) < 0.00001)
|
||||
return m4_identity();
|
||||
|
||||
// Calcuate inverse of R by dividing the transposed cofactor matrix by the
|
||||
// determinant.
|
||||
float i00 = c00 / det, i10 = c01 / det, i20 = c02 / det;
|
||||
float i01 = c10 / det, i11 = c11 / det, i21 = c12 / det;
|
||||
float i02 = c20 / det, i12 = c21 / det, i22 = c22 / det;
|
||||
|
||||
// Combine the inverted R with the inverted translation
|
||||
return mat4(
|
||||
i00, i10, i20, -(i00*m30 + i10*m31 + i20*m32),
|
||||
i01, i11, i21, -(i01*m30 + i11*m31 + i21*m32),
|
||||
i02, i12, i22, -(i02*m30 + i12*m31 + i22*m32),
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a 4x4 matrix with a 3D vector representing a point in 3D space.
|
||||
*
|
||||
* Before the matrix multiplication the vector is first expanded to a 4D vector
|
||||
* (x, y, z, 1). After the multiplication the vector is reduced to 3D again by
|
||||
* dividing through the 4th component (if it's not 0 or 1).
|
||||
*/
|
||||
vec3_t m4_mul_pos(mat4_t matrix, vec3_t position) {
|
||||
vec3_t result = vec3(
|
||||
matrix.m00 * position.x + matrix.m10 * position.y + matrix.m20 * position.z + matrix.m30,
|
||||
matrix.m01 * position.x + matrix.m11 * position.y + matrix.m21 * position.z + matrix.m31,
|
||||
matrix.m02 * position.x + matrix.m12 * position.y + matrix.m22 * position.z + matrix.m32
|
||||
);
|
||||
|
||||
float w = matrix.m03 * position.x + matrix.m13 * position.y + matrix.m23 * position.z + matrix.m33;
|
||||
if (w != 0 && w != 1)
|
||||
return vec3(result.x / w, result.y / w, result.z / w);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a 4x4 matrix with a 3D vector representing a direction in 3D space.
|
||||
*
|
||||
* Before the matrix multiplication the vector is first expanded to a 4D vector
|
||||
* (x, y, z, 0). For directions the 4th component is set to 0 because directions
|
||||
* are only rotated, not translated. After the multiplication the vector is
|
||||
* reduced to 3D again by dividing through the 4th component (if it's not 0 or
|
||||
* 1). This is necessary because the matrix might contains something other than
|
||||
* (0, 0, 0, 1) in the bottom row which might set w to something other than 0
|
||||
* or 1.
|
||||
*/
|
||||
vec3_t m4_mul_dir(mat4_t matrix, vec3_t direction) {
|
||||
vec3_t result = vec3(
|
||||
matrix.m00 * direction.x + matrix.m10 * direction.y + matrix.m20 * direction.z,
|
||||
matrix.m01 * direction.x + matrix.m11 * direction.y + matrix.m21 * direction.z,
|
||||
matrix.m02 * direction.x + matrix.m12 * direction.y + matrix.m22 * direction.z
|
||||
);
|
||||
|
||||
float w = matrix.m03 * direction.x + matrix.m13 * direction.y + matrix.m23 * direction.z;
|
||||
if (w != 0 && w != 1)
|
||||
return vec3(result.x / w, result.y / w, result.z / w);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void m4_print(mat4_t matrix) {
|
||||
m4_fprintp(stdout, matrix, 6, 2);
|
||||
}
|
||||
|
||||
void m4_printp(mat4_t matrix, int width, int precision) {
|
||||
m4_fprintp(stdout, matrix, width, precision);
|
||||
}
|
||||
|
||||
void m4_fprint(FILE* stream, mat4_t matrix) {
|
||||
m4_fprintp(stream, matrix, 6, 2);
|
||||
}
|
||||
|
||||
void m4_fprintp(FILE* stream, mat4_t matrix, int width, int precision) {
|
||||
mat4_t m = matrix;
|
||||
int w = width, p = precision;
|
||||
for(int r = 0; r < 4; r++) {
|
||||
fprintf(stream, "| %*.*f %*.*f %*.*f %*.*f |\n",
|
||||
w, p, m.m[0][r], w, p, m.m[1][r], w, p, m.m[2][r], w, p, m.m[3][r]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MATH_3D_IMPLEMENTATION
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "mesh.h"
|
||||
|
||||
Mesh
|
||||
Mesh_new (size_t vertices_len, float *vertices)
|
||||
{
|
||||
Mesh mesh = { vertices_len, vertices, 0 };
|
||||
glGenBuffers (1, &mesh.vbo);
|
||||
glBindBuffer (GL_ARRAY_BUFFER, mesh.vbo);
|
||||
glBufferData (GL_ARRAY_BUFFER, vertices_len * sizeof (float), vertices, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof (float), 0);
|
||||
glEnableVertexAttribArray (0);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void
|
||||
Mesh_draw (Mesh *mesh)
|
||||
{
|
||||
glBindBuffer (GL_ARRAY_BUFFER, mesh->vbo);
|
||||
glDrawArrays (GL_TRIANGLES, 0, mesh->vertices_len);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_opengles2.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
typedef struct Mesh
|
||||
{
|
||||
size_t vertices_len;
|
||||
float *vertices;
|
||||
GLuint vbo;
|
||||
} Mesh;
|
||||
|
||||
Mesh Mesh_new (size_t vertices_len, float *vertices);
|
||||
void Mesh_draw (Mesh *mesh);
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "program.h"
|
||||
|
||||
void
|
||||
shader_fail (int shader)
|
||||
{
|
||||
int info_len;
|
||||
glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &info_len);
|
||||
int shader_type;
|
||||
glGetShaderiv (shader, GL_SHADER_TYPE, &shader_type);
|
||||
const char *type = shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment";
|
||||
|
||||
if (info_len > 1)
|
||||
{
|
||||
char *info = malloc (sizeof (char) * info_len);
|
||||
glGetShaderInfoLog (shader, info_len, NULL, info);
|
||||
WARN ("Compiling %s shader failed:\n%s", type, info);
|
||||
free (info);
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN ("Compiling %s shader failed without information.", type);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
program_fail (int program)
|
||||
{
|
||||
int info_len;
|
||||
glGetProgramiv (program, GL_INFO_LOG_LENGTH, &info_len);
|
||||
|
||||
if (info_len > 1)
|
||||
{
|
||||
char *info = malloc (sizeof (char) * info_len);
|
||||
glGetProgramInfoLog (program, info_len, NULL, info);
|
||||
WARN ("Linking program failed:\n%s", info);
|
||||
free (info);
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN ("Linking program failed without information.");
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
program_create (const char *v_src, const char *f_src)
|
||||
{
|
||||
int success = 0;
|
||||
|
||||
int vertex = glCreateShader (GL_VERTEX_SHADER);
|
||||
glShaderSource (vertex, 1, &v_src, NULL);
|
||||
glCompileShader (vertex);
|
||||
glGetShaderiv (vertex, GL_COMPILE_STATUS, &success);
|
||||
if (!success) shader_fail (vertex);
|
||||
|
||||
int fragment = glCreateShader (GL_FRAGMENT_SHADER);
|
||||
glShaderSource (fragment, 1, &f_src, NULL);
|
||||
glCompileShader (fragment);
|
||||
glGetShaderiv (fragment, GL_COMPILE_STATUS, &success);
|
||||
if (!success) shader_fail (fragment);
|
||||
|
||||
int program = glCreateProgram ();
|
||||
glAttachShader (program, vertex);
|
||||
glAttachShader (program, fragment);
|
||||
glLinkProgram (program);
|
||||
glGetProgramiv (program, GL_LINK_STATUS, &success);
|
||||
if (!success) program_fail (program);
|
||||
else INFO ("Compiled shaders and linked program %d successfuly.", program);
|
||||
|
||||
glDeleteShader (vertex);
|
||||
glDeleteShader (fragment);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
void
|
||||
program_set_mat4 (int program, const char *name, mat4_t value)
|
||||
{
|
||||
glUniformMatrix4fv (glGetUniformLocation (program, name), 1, GL_FALSE, (float *) &value);
|
||||
}
|
||||
|
||||
static const char *
|
||||
gl_type_to_string (GLenum type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case GL_FLOAT: return "float";
|
||||
case GL_FLOAT_VEC3: return "vec3";
|
||||
case GL_FLOAT_VEC4: return "vec4";
|
||||
case GL_INT: return "int";
|
||||
case GL_INT_VEC3: return "ivec3";
|
||||
case GL_INT_VEC4: return "ivec4";
|
||||
case GL_BOOL: return "bool";
|
||||
case GL_FLOAT_MAT4: return "mat4";
|
||||
case GL_SAMPLER_2D: return "sampler2D";
|
||||
default: return "unknown type";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
program_print (int program)
|
||||
{
|
||||
int bufsize;
|
||||
glGetProgramiv (program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &bufsize);
|
||||
char name[bufsize];
|
||||
|
||||
int uniform_no, attribute_no;
|
||||
glGetProgramiv (program, GL_ACTIVE_UNIFORMS, &uniform_no);
|
||||
glGetProgramiv (program, GL_ACTIVE_ATTRIBUTES, &attribute_no);
|
||||
|
||||
INFO ("== Program %d: %d active attribute(s) / %d active uniform(s) ==", program, attribute_no, uniform_no);
|
||||
|
||||
GLenum type;
|
||||
|
||||
for (GLint i = 0; i < attribute_no; i++) {
|
||||
glGetActiveAttrib (program, i, bufsize, NULL, NULL, &type, name);
|
||||
INFO ("Attribute #%d: %s %s", i, gl_type_to_string (type), name);
|
||||
}
|
||||
|
||||
for (GLint i = 0; i < uniform_no; i++) {
|
||||
glGetActiveUniform (program, i, bufsize, NULL, NULL, &type, name);
|
||||
INFO ("Uniform #%d: %s %s", i, gl_type_to_string (type), name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_opengles2.h>
|
||||
|
||||
#include "math_3d.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Just for clarity sake. */
|
||||
#define program_use glUseProgram
|
||||
|
||||
void shader_fail (int shader);
|
||||
void program_fail (int program);
|
||||
int program_create (const char *v_src, const char *f_src);
|
||||
void program_set_mat4 (int program, const char *name, mat4_t value);
|
||||
void program_print (int program);
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "resources.h"
|
||||
|
||||
Resources resources;
|
||||
|
||||
void
|
||||
Resources_add_program (const char *v_src, const char *f_src)
|
||||
{
|
||||
resources.programs_len++;
|
||||
resources.programs = realloc (resources.programs, sizeof (int) * resources.programs_len);
|
||||
resources.programs[resources.programs_len - 1] = program_create (v_src, f_src);
|
||||
}
|
||||
|
||||
void
|
||||
Resources_load_program (const char *v_path, const char *f_path)
|
||||
{
|
||||
FILE *v_file = fopen (v_path, "r");
|
||||
|
||||
if (v_file == NULL)
|
||||
{
|
||||
WARN ("File `%s' does not exist. Not loading program.", v_path);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek (v_file, 0L, SEEK_END);
|
||||
size_t v_len = ftell (v_file) + 1;
|
||||
char *v_src = calloc (v_len, sizeof (char));
|
||||
rewind (v_file);
|
||||
INFO ("Read %zu bytes from vertex source.", fread (v_src, sizeof (char), v_len, v_file));
|
||||
|
||||
FILE *f_file = fopen (f_path, "r");
|
||||
|
||||
if (f_file == NULL)
|
||||
{
|
||||
WARN ("File `%s' does not exist. Not loading program.", f_path);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek (f_file, 0L, SEEK_END);
|
||||
size_t f_len = ftell (f_file) + 1;
|
||||
char *f_src = calloc (f_len, sizeof (char));
|
||||
rewind (f_file);
|
||||
INFO ("Read %zu bytes from fragment source.", fread (f_src, sizeof (char), f_len, f_file));
|
||||
|
||||
Resources_add_program (v_src, f_src);
|
||||
|
||||
free (v_src);
|
||||
free (f_src);
|
||||
}
|
||||
|
||||
void
|
||||
Resources_init ()
|
||||
{
|
||||
resources.programs_len = 0;
|
||||
resources.programs = malloc (sizeof (int));
|
||||
Resources_load_program ("res/default.vert", "res/default.frag");
|
||||
Resources_load_program ("res/chunk.vert", "res/chunk.frag");
|
||||
}
|
||||
|
||||
void
|
||||
Resources_free ()
|
||||
{
|
||||
free (resources.programs);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "program.h"
|
||||
|
||||
typedef struct Resources {
|
||||
size_t programs_len;
|
||||
int *programs;
|
||||
} Resources;
|
||||
|
||||
void Resources_add_program (const char *v_src, const char *f_src);
|
||||
void Resources_load_program (const char *v_path, const char *f_path);
|
||||
void Resources_init ();
|
||||
void Resources_free ();
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct GameState
|
||||
{
|
||||
int last_time;
|
||||
float delta_time;
|
||||
} GameState;
|
||||
|
||||
GameState game_state;
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define GAME_NAME "Wschód"
|
||||
#define GAME_VERSION "0.1"
|
||||
|
||||
#define COLOR_BLUE "\x1B[34m"
|
||||
#define COLOR_RED "\x1B[31m"
|
||||
#define COLOR_YELLOW "\x1B[33m"
|
||||
#define COLOR_RESET "\x1B[0m"
|
||||
|
||||
#define INFO(fmt, args...) (fprintf (stderr, COLOR_BLUE "[I] %s:%d:%s(): " COLOR_RESET fmt "\n", __FILE__, __LINE__, __func__, ##args))
|
||||
#define WARN(fmt, args...) (fprintf (stderr, COLOR_YELLOW "[W] %s:%d:%s(): " COLOR_RESET fmt "\n", __FILE__, __LINE__, __func__, ##args))
|
||||
#define FATAL(fmt, args...) (fprintf (stderr, COLOR_RED "[!] %s:%d:%s(): " COLOR_RESET fmt "\n", __FILE__, __LINE__, __func__, ##args), exit (-1))
|
||||
|
||||
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
|
||||
@@ -0,0 +1,21 @@
|
||||
SRC=src/mapedit.c
|
||||
|
||||
OBJ=$(SRC:.c=.o)
|
||||
CFLAGS=-g -Werror -Wextra -Wall `pkg-config --cflags gtk4`
|
||||
LDFLAGS=`pkg-config --libs gtk4`
|
||||
TARGET=mapedit
|
||||
CC=gcc
|
||||
LD=gcc
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(LD) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $^ -o $@ $(CFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) $(TARGET) $(OBJ)
|
||||
@@ -0,0 +1,67 @@
|
||||
#define SDL_MAIN_USE_CALLBACKS 1
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "shader.h"
|
||||
|
||||
static SDL_Window *window = NULL;
|
||||
static SDL_GLContext context = NULL;
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppInit (void **appstate, int argc, char **argv)
|
||||
{
|
||||
(void) appstate; (void) argc; (void) argv;
|
||||
|
||||
INFO ("Running game with debugging facilities.");
|
||||
SDL_SetAppMetadata ("Wschód", VERSION, "eur.chopininteractive.wschod-game");
|
||||
|
||||
if (!SDL_Init (SDL_INIT_VIDEO)) {
|
||||
ERROR ("Couldn't initialize SDL: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindow ("Wschód", 640, 480, SDL_WINDOW_OPENGL);
|
||||
|
||||
if (!window) {
|
||||
ERROR ("Couldn't create window: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
context = SDL_GL_CreateContext (window);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppEvent (void *appstate, SDL_Event *event)
|
||||
{
|
||||
(void) appstate;
|
||||
|
||||
if (event->type == SDL_EVENT_QUIT) {
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult
|
||||
SDL_AppIterate (void *appstate)
|
||||
{
|
||||
(void) appstate;
|
||||
|
||||
glClearColor (0, 0, 0, 1);
|
||||
glClear (GL_COLOR_BUFFER_BIT);
|
||||
SDL_GL_SwapWindow (window);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void
|
||||
SDL_AppQuit (void *appstate, SDL_AppResult result)
|
||||
{
|
||||
(void) appstate;
|
||||
(void) result;
|
||||
SDL_GL_DestroyContext (context);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
let
|
||||
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.05";
|
||||
pkgs = import nixpkgs { config = {}; overlays = []; };
|
||||
in
|
||||
|
||||
|
||||
pkgs.mkShellNoCC {
|
||||
packages = with pkgs; [
|
||||
gcc
|
||||
pkg-config
|
||||
gtk4
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user