From 6652194f2fbd22d7c764d0e2a43e18397dec8646 Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Thu, 5 Jun 2025 19:18:21 +0200 Subject: [PATCH] Basic UI system, font --- game/Makefile | 7 +- game/res/shaders/default.frag | 2 +- game/res/shaders/uielement.frag | 14 ++++ game/res/shaders/uielement.vert | 31 +++++++++ game/res/textures/font.png | Bin 0 -> 1708 bytes game/res/textures/font.xcf | Bin 0 -> 35644 bytes game/res/textures/grass0.png | Bin 14061 -> 11232 bytes game/src/chunk/chunkman.c | 12 ++-- game/src/chunk/chunkman.h | 6 +- game/src/entity/entman.c | 14 ++-- game/src/entity/entman.h | 2 +- game/src/entity/meshes.h | 20 ------ game/src/event/handlers.h | 2 +- game/src/{entity/meshes.c => gl/mesh.c} | 24 +++---- game/src/gl/mesh.h | 22 +++++++ game/src/{ => gl}/program.c | 0 game/src/{ => gl}/program.h | 2 +- game/src/gl/quad.c | 46 +++++++++++++ game/src/{ => gl}/texture.c | 16 ++++- game/src/{ => gl}/texture.h | 1 + game/src/graphics.c | 31 ++++----- game/src/graphics.h | 3 +- game/src/main.c | 2 + game/src/resources.c | 73 +++++++++++++++++---- game/src/resources.h | 7 +- game/src/state.c | 10 +++ game/src/state.h | 1 + game/src/ui/uielement.c | 83 ++++++++++++++++++++++++ game/src/ui/uielement.h | 34 ++++++++++ game/src/ui/uiman.c | 37 +++++++++++ game/src/ui/uiman.h | 6 ++ 31 files changed, 424 insertions(+), 84 deletions(-) create mode 100644 game/res/shaders/uielement.frag create mode 100644 game/res/shaders/uielement.vert create mode 100644 game/res/textures/font.png create mode 100644 game/res/textures/font.xcf delete mode 100644 game/src/entity/meshes.h rename game/src/{entity/meshes.c => gl/mesh.c} (94%) create mode 100644 game/src/gl/mesh.h rename game/src/{ => gl}/program.c (100%) rename game/src/{ => gl}/program.h (96%) create mode 100644 game/src/gl/quad.c rename game/src/{ => gl}/texture.c (50%) rename game/src/{ => gl}/texture.h (59%) create mode 100644 game/src/ui/uielement.c create mode 100644 game/src/ui/uielement.h create mode 100644 game/src/ui/uiman.c create mode 100644 game/src/ui/uiman.h diff --git a/game/Makefile b/game/Makefile index 67b93ed..b4d592c 100644 --- a/game/Makefile +++ b/game/Makefile @@ -1,7 +1,8 @@ -SRC=src/main.c src/program.c src/resources.c src/camera.c src/chunk/chunk.c \ - src/chunk/chunkmesh.c src/flags.c src/texture.c src/chunk/chunkman.c \ +SRC=src/main.c src/gl/program.c src/resources.c src/camera.c src/chunk/chunk.c \ + src/chunk/chunkmesh.c src/flags.c src/gl/texture.c src/chunk/chunkman.c \ src/event/manager.c src/event/handlers.c src/state.c src/graphics.c \ - src/entity/meshes.c src/entity/entman.c src/entity/templates.c + src/gl/mesh.c src/entity/entman.c src/entity/templates.c src/ui/uielement.c \ + src/ui/uiman.c OBJ=$(SRC:.c=.o) CFLAGS=-g -pg -Werror -Wextra -Wall `pkg-config --cflags sdl3 sdl3-image cglm gl glib-2.0` diff --git a/game/res/shaders/default.frag b/game/res/shaders/default.frag index 07bea22..d90b2ec 100644 --- a/game/res/shaders/default.frag +++ b/game/res/shaders/default.frag @@ -8,7 +8,7 @@ varying vec2 f_uv; void main () { - if(texture2D (texture, f_uv).a < 0.2) + if(texture2D (texture, f_uv).a < 0.5) { discard; } diff --git a/game/res/shaders/uielement.frag b/game/res/shaders/uielement.frag new file mode 100644 index 0000000..46b2d27 --- /dev/null +++ b/game/res/shaders/uielement.frag @@ -0,0 +1,14 @@ +#version 100 + +precision mediump float; + +uniform vec3 color; +uniform sampler2D texture; + +varying vec2 f_uv; + +void main () +{ + vec4 tex_color = texture2D (texture, f_uv); + gl_FragColor = tex_color * vec4 (color, 1.0); +} diff --git a/game/res/shaders/uielement.vert b/game/res/shaders/uielement.vert new file mode 100644 index 0000000..2601637 --- /dev/null +++ b/game/res/shaders/uielement.vert @@ -0,0 +1,31 @@ +#version 100 + +attribute vec4 position; +attribute vec2 uv; + +uniform mat4 model; +uniform float time; +uniform float character; + +varying vec2 f_uv; + +void main () +{ + float tex_w = 128.0; + float tex_h = 128.0; + + float char_w = 8.0; + float char_h = 16.0; + + float chars_per_row = tex_w / char_w; + + float x = mod (character, chars_per_row); + float y = floor (character / chars_per_row); + + vec2 char_offset = vec2 (x * char_w / tex_w, y * char_h / tex_h); + vec2 char_size = vec2 (char_w / tex_w, char_h / tex_h); + + f_uv = char_offset + uv * char_size; + + gl_Position = model * position; +} diff --git a/game/res/textures/font.png b/game/res/textures/font.png new file mode 100644 index 0000000000000000000000000000000000000000..9dea6b7835c5ddc3dee7ea4c93e9360f6e787e7b GIT binary patch literal 1708 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_R+7>k44ofvPP)Tw7+U|>mi z^mSxl*x1kgCy|wbfk7eJBgmJ5p-PQ`p`nF=;TK5r3kHT#0|tgy2@DKYGZ+}e3+C(! zv|(Ui)ADq245_&FHum&Un*aY#uZ~}~T1Kphy>ZUxYrHEG-nCTJ+RZ=DKS5lC zv8Jx^Tk&o0)y4S&VsG|*`}C(U_PXodKd*PKlWSa1d;VKc`JXbTq6Hq-MJ+62T{R3k9*@4+7c#t=t}M@RB8%matJge!h6B7Tn}ivhmj1k@ z$f&@`Aiyv|%sgh{owppU>iO*~9{2ic7#J8@Uc8*~>wnn(Uq38W`0qF{urSCtGHfl4 z{QpkYrd7&AiLuK4e4)%+1*Qi}7&fTo=^T{1H@)Sh14Db#5|dVO25ANcMxQH*1$Qf;eBT6N9#N}E7=!NPaS zoh^aizxPew@_s>F)cN&VqNo4<{~{E1eQ}?D^&WwD|L%Rf^m_fjPtoV&m*mA=X(^96 zprIX;yfpjT@y*l9-!sJ=WbCz=(sBCzUzN!h@8!4cSf|Q1UHIV2n;!g4yTT_H>E02x z3^cpEzLn9SQ&vnfRFIuRjp^0V!nf0==c_j5|Nrpz!95kFl6_kyt!!=1`WsXH=7omF z*H24-GMs-tUhvc$f9Ma>YmG-tfN9YmeMXKJ@N8lbe5w^mXGY zi{9|}cYNI=a^Cy02g7NZ_b;Z)tIC#{SlNo&Xedos*P8nFQ15}sds$b`dp4u-amH%t zjL82=h0#hH_mvgPZnRC?Q8}yY%`}GzDt1BNXZ}08V6N*QotNso9hvX%Mpa)-n-H4v zRi}Ps_oJI@7XFGb+pN^_J@=9Q%%2Pl1}kTC2xi>Oe;(3NW->DC1$=qjtlqcW zJ1sI5P8R)_ba@*&UH`GG(Snz^4+?G1IHEcI;;!27Euq|rQ>qLOwLY(6T*GqW!zK3> zn!1-OmLz|%_L?s8OKsx%6A4S*7cLf7@ITN0;fCbApPt3mCl)6<$VorRlWY*o6PX^i z*7NnXp68$5>U^Gj%r&g*=lg^PCWa5Yd@l*}&iea9@NqO-@Rj#bv0wNCGmgDa`Z_tT zK}9~{NZr1Y{k7BTde+5n;AMRC;&|Vu^P63q@^#A##LC^AKFzDX2=2UQ$a_f_ob?fUjM?Rl*Lpm_DS01f#))dt;Y&0 zCtvnEv@S913I|sY^Ba!`s|nKi6p!#k$7E zF}rtB@Q$h-To?2Tx{cSg3hcD94r*HP>O+M0fd>h^3=9rcr*9d}-~KGxuPSjq`zGN# zB}_N2GM~`fc}j=Rx|3tYPkCw18aes<6PuMd%^&@)TP<+FcJk^a$9JpV*SRDk_*}?l zG3VPKC7bR!a`HbdzFt#QpkJi^_r_H9PtP|#Gd=Hd_HGgHc9xC@?$#!^*j> za)RsDB)bbtFyr;z>|fMB>kCKW`<>6UgjTBVQ+=%vnt01}SGwMkXC(%%DYGUYymj=6 z-Hs<~MeGjEUeLF-RjA?nWR1A12VcJLIJ*6g^R5TiX8j9W?`?8_zKcZa)9kDB!zOLf zsq?R87GYp!U|6=q>eD!rPLEXthic|YH%$CxP%d0wkN`7IVlG*ycbwbN- zK3igO=hVzeds)`C^_$FZ%z8X8b^DYJi((gaT;9BS;oZgGYyMih3b;+az5M0llP@pF zG`~)^{j;-n;tGwzH>yX>`?Bqyt9qVa)RFGppO_Ms6Y_KF%rLX5Q)`5-RG*nP>oNfF)tsnX4UXa$wol*~~VHiAJ{an^LB{Ts5vc4`b literal 0 HcmV?d00001 diff --git a/game/res/textures/font.xcf b/game/res/textures/font.xcf new file mode 100644 index 0000000000000000000000000000000000000000..7d2810d882e0798cfca0c80c254303ba1928400e GIT binary patch literal 35644 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 ZT9W-g()f^M4NA%2kO8GTNQy;D@&I`mf=mDa literal 0 HcmV?d00001 diff --git a/game/res/textures/grass0.png b/game/res/textures/grass0.png index b15ebef7ef7c4448394d2d00dcbaeacc8cef0860..ade585909e9adfad5f30ac77be222d9fa62d36ae 100644 GIT binary patch literal 11232 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 GLK6VYKAW%r literal 14061 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[1] = chunk->heightmap [INDEX (x, z)]; tree->position[2] = z + zoffset; tree->rotation[1] = rand () % 180; + tree->scale[1] = (rand() % 5) + 5; ENTADD (tree); } diff --git a/game/src/chunk/chunkman.h b/game/src/chunk/chunkman.h index ac26702..4d4ecf6 100644 --- a/game/src/chunk/chunkman.h +++ b/game/src/chunk/chunkman.h @@ -5,15 +5,15 @@ #include "chunk.h" #include "chunkmesh.h" -#include "../program.h" +#include "../gl/program.h" #include "../entity/entman.h" #include "../entity/templates.h" #define RENDER_DISTANCE 4 void chunkman_init (); -void chunkman_update (vec3 camera_position); -void chunkman_render (vec3 camera_position, int program); +void chunkman_update (vec3 position); +void chunkman_render (vec3 position); float chunkman_get_height_at (float x, float z); diff --git a/game/src/entity/entman.c b/game/src/entity/entman.c index c1b0bf4..346500f 100644 --- a/game/src/entity/entman.c +++ b/game/src/entity/entman.c @@ -10,7 +10,7 @@ extern int last_time; Entity *player_entity, *sky_entity; static GSequence *entities; -static EntityMesh mesh_quad1, mesh_quad2, mesh_quad_x; +static SimpleMesh mesh_quad1, mesh_quad2, mesh_quad_x; static unsigned int last_program = -1; static unsigned int last_type = -1; static unsigned int last_texture = -1; @@ -25,9 +25,9 @@ void entman_init () { entities = g_sequence_new (NULL); - mesh_quad1 = EntityMesh_make_quad1 (); - mesh_quad2 = EntityMesh_make_quad2 (); - mesh_quad_x = EntityMesh_make_quad_x (); + mesh_quad1 = SimpleMesh_make_quad1 (); + mesh_quad2 = SimpleMesh_make_quad2 (); + mesh_quad_x = SimpleMesh_make_quad_x (); player_entity = malloc (sizeof (Entity)); *player_entity = (Entity) { @@ -111,7 +111,7 @@ entman_render_internal (gpointer data, gpointer userdata) } #endif - EntityMesh to_render; + SimpleMesh to_render; unsigned int render_type = entity->render_type; switch (entity->mesh_type) @@ -201,13 +201,13 @@ entman_render_internal (gpointer data, gpointer userdata) if (entity->mesh_type != last_type) { - EntityMesh_bind (&to_render); + SimpleMesh_bind (&to_render); } last_program = entity->program; last_type = entity->mesh_type; last_texture = entity->texture; - EntityMesh_render (&to_render); + SimpleMesh_render (&to_render); } void diff --git a/game/src/entity/entman.h b/game/src/entity/entman.h index d610247..2bd1d98 100644 --- a/game/src/entity/entman.h +++ b/game/src/entity/entman.h @@ -4,7 +4,7 @@ #include #include "entity.h" -#include "meshes.h" +#include "../gl/mesh.h" #include "../util.h" #include "../resources.h" #include "../state.h" diff --git a/game/src/entity/meshes.h b/game/src/entity/meshes.h deleted file mode 100644 index 4cd03e1..0000000 --- a/game/src/entity/meshes.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -#include "../flags.h" - -typedef struct EntityMesh -{ - size_t indices_len; - unsigned int pbo; - unsigned int uvbo; - unsigned int ebo; -} EntityMesh; - -void EntityMesh_bind (EntityMesh *mesh); -void EntityMesh_render (EntityMesh *mesh); -EntityMesh EntityMesh_make_quad1 (); -EntityMesh EntityMesh_make_quad2 (); -EntityMesh EntityMesh_make_quad_x (); diff --git a/game/src/event/handlers.h b/game/src/event/handlers.h index 656c304..d45bc21 100644 --- a/game/src/event/handlers.h +++ b/game/src/event/handlers.h @@ -6,7 +6,7 @@ #include "manager.h" #include "../camera.h" #include "../flags.h" -#include "../program.h" +#include "../gl/program.h" #include "../state.h" void evt_handlers_init (); diff --git a/game/src/entity/meshes.c b/game/src/gl/mesh.c similarity index 94% rename from game/src/entity/meshes.c rename to game/src/gl/mesh.c index ecb25de..22c0cd5 100644 --- a/game/src/entity/meshes.c +++ b/game/src/gl/mesh.c @@ -1,4 +1,4 @@ -#include "meshes.h" +#include "mesh.h" /* TODO: Theoretically, we could fit all the transforms into one vec4. The caveat? It would be for every vertex. There seems to be a @@ -6,7 +6,7 @@ every subsequent vertex. */ void -EntityMesh_bind (EntityMesh *mesh) +SimpleMesh_bind (SimpleMesh *mesh) { glBindBuffer (GL_ARRAY_BUFFER, mesh->pbo); glEnableVertexAttribArray (0); @@ -20,7 +20,7 @@ EntityMesh_bind (EntityMesh *mesh) } void -EntityMesh_render (EntityMesh *mesh) +SimpleMesh_render (SimpleMesh *mesh) { if (flags_get ("wireframe_enabled")) { @@ -32,8 +32,8 @@ EntityMesh_render (EntityMesh *mesh) } } -EntityMesh -EntityMesh_make_quad1 () +SimpleMesh +SimpleMesh_make_quad1 () { /* The positions are trivial. */ float positions[12] = { @@ -54,7 +54,7 @@ EntityMesh_make_quad1 () 1.0, 0.0, }; - EntityMesh mesh; + SimpleMesh mesh; mesh.indices_len = 6; glGenBuffers (1, &mesh.pbo); glGenBuffers (1, &mesh.uvbo); @@ -77,8 +77,8 @@ EntityMesh_make_quad1 () return mesh; } -EntityMesh -EntityMesh_make_quad2 () +SimpleMesh +SimpleMesh_make_quad2 () { /* The positions are trivial. */ float positions[12] = { @@ -100,7 +100,7 @@ EntityMesh_make_quad2 () 1.0, 0.0, }; - EntityMesh mesh; + SimpleMesh mesh; mesh.indices_len = 12; glGenBuffers (1, &mesh.pbo); glGenBuffers (1, &mesh.uvbo); @@ -123,8 +123,8 @@ EntityMesh_make_quad2 () return mesh; } -EntityMesh -EntityMesh_make_quad_x () +SimpleMesh +SimpleMesh_make_quad_x () { /* The positions are trivial. */ float positions[24] = { @@ -158,7 +158,7 @@ EntityMesh_make_quad_x () 1.0, 0.0 }; - EntityMesh mesh; + SimpleMesh mesh; mesh.indices_len = 24; glGenBuffers (1, &mesh.pbo); glGenBuffers (1, &mesh.uvbo); diff --git a/game/src/gl/mesh.h b/game/src/gl/mesh.h new file mode 100644 index 0000000..55bf4cc --- /dev/null +++ b/game/src/gl/mesh.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "../flags.h" + +typedef struct SimpleMesh +{ + size_t indices_len; + unsigned int pbo; + unsigned int uvbo; + unsigned int ebo; +} SimpleMesh; + +void SimpleMesh_bind (SimpleMesh *mesh); +void SimpleMesh_render (SimpleMesh *mesh); + +SimpleMesh SimpleMesh_make_quad1 (); +SimpleMesh SimpleMesh_make_quad2 (); +SimpleMesh SimpleMesh_make_quad_x (); + diff --git a/game/src/program.c b/game/src/gl/program.c similarity index 100% rename from game/src/program.c rename to game/src/gl/program.c diff --git a/game/src/program.h b/game/src/gl/program.h similarity index 96% rename from game/src/program.h rename to game/src/gl/program.h index c6a5456..4759e6c 100644 --- a/game/src/program.h +++ b/game/src/gl/program.h @@ -3,7 +3,7 @@ #include #include -#include "util.h" +#include "../util.h" /* Just for clarity sake. */ #define program_use glUseProgram diff --git a/game/src/gl/quad.c b/game/src/gl/quad.c new file mode 100644 index 0000000..e6b971c --- /dev/null +++ b/game/src/gl/quad.c @@ -0,0 +1,46 @@ +#include "quad.h" + +EntityMesh +EntityMesh_make_quad1 () +{ + /* The positions are trivial. */ + float positions[12] = { + -1.0, -1.0, 0.0, + +1.0, -1.0, 0.0, + -1.0, 1.0, 0.0, + +1.0, 1.0, 0.0 + }; + + unsigned int indices[12] = { + 3, 2, 0, 0, 1, 3, + }; + + float uvs[8] = { + 0.0, 1.0, + 1.0, 1.0, + 0.0, 0.0, + 1.0, 0.0, + }; + + EntityMesh mesh; + mesh.indices_len = 6; + glGenBuffers (1, &mesh.pbo); + glGenBuffers (1, &mesh.uvbo); + glGenBuffers (1, &mesh.ebo); + + glBindBuffer (GL_ARRAY_BUFFER, mesh.pbo); + glBufferData (GL_ARRAY_BUFFER, sizeof (float) * 12, positions, GL_STATIC_DRAW); + glEnableVertexAttribArray (0); + glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, sizeof (float) * 3, 0); + + glBindBuffer (GL_ARRAY_BUFFER, mesh.uvbo); + glBufferData (GL_ARRAY_BUFFER, sizeof (float) * 8, uvs, GL_STATIC_DRAW); + glEnableVertexAttribArray (1); + glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, sizeof (float) * 2, 0); + + + glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, mesh.ebo); + glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (unsigned int) * mesh.indices_len, indices, GL_STATIC_DRAW); + + return mesh; +} diff --git a/game/src/texture.c b/game/src/gl/texture.c similarity index 50% rename from game/src/texture.c rename to game/src/gl/texture.c index 09f91df..b60a04c 100644 --- a/game/src/texture.c +++ b/game/src/gl/texture.c @@ -11,7 +11,21 @@ texture_create (int width, int height, unsigned char *data) glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap (GL_TEXTURE_2D); - glBindTexture (GL_TEXTURE_2D, 0); + + return texture; +} + +unsigned int +texture_create_no_interp (int width, int height, unsigned char *data) +{ + unsigned int texture; + glGenTextures (1, &texture); + glBindTexture (GL_TEXTURE_2D, texture); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glGenerateMipmap (GL_TEXTURE_2D); + return texture; } diff --git a/game/src/texture.h b/game/src/gl/texture.h similarity index 59% rename from game/src/texture.h rename to game/src/gl/texture.h index 587abdd..a5beb46 100644 --- a/game/src/texture.h +++ b/game/src/gl/texture.h @@ -3,3 +3,4 @@ #include unsigned int texture_create (int width, int height, unsigned char *data); +unsigned int texture_create_no_interp (int width, int height, unsigned char *data); diff --git a/game/src/graphics.c b/game/src/graphics.c index 4cee223..6485d32 100644 --- a/game/src/graphics.c +++ b/game/src/graphics.c @@ -5,6 +5,9 @@ extern vec3 camera_position; static SDL_Window *sdl_window; static SDL_GLContext gl_context; +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 + void graphics_init () { @@ -13,7 +16,7 @@ graphics_init () FATAL ("Couldn't initialize SDL: %s", SDL_GetError ()); } - sdl_window = SDL_CreateWindow (GAME_NAME, 640, 480, SDL_WINDOW_OPENGL); + sdl_window = SDL_CreateWindow (GAME_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL); if (!sdl_window) { @@ -21,7 +24,9 @@ graphics_init () } /* We use OpenGL ES 2.0. It should support everything, and it's more - than enough for the game. */ + than enough for the game. Note that regardless of what you set, + the version returned by glGetString can differ. Doesn't + matter. */ 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); @@ -41,7 +46,7 @@ graphics_init () INFO ("GLSL version: %s", glGetString (GL_SHADING_LANGUAGE_VERSION)); INFO ("Max vertex attributes: %d", GL_MAX_VERTEX_ATTRIBS); - glViewport (0, 0, 640, 480); + glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glClearColor (0, 0.3, 0.8, 1); glEnable (GL_CULL_FACE); @@ -54,12 +59,11 @@ graphics_render () glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mat4 projection, view; - camera_get_projection (640.0 / 480.0, projection); + camera_get_projection (WINDOW_WIDTH / WINDOW_HEIGHT, projection); camera_get_view (view); - program_use (RESPRG ("chunk")); - program_set_mat4 (RESPRG ("chunk"), "projection", projection); - program_set_mat4 (RESPRG ("chunk"), "view", view); + resources_programs_set_projection (projection); + resources_programs_set_view (view); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain0")); @@ -72,16 +76,13 @@ graphics_render () glActiveTexture (GL_TEXTURE4); glBindTexture (GL_TEXTURE_2D, RESTEX ("terrain4")); - chunkman_render (camera_position, RESPRG ("chunk")); - - program_use (RESPRG ("clouds")); - program_set_mat4 (RESPRG ("clouds"), "projection", projection); - program_set_mat4 (RESPRG ("clouds"), "view", view); - program_use (RESPRG ("default")); - program_set_mat4 (RESPRG ("default"), "projection", projection); - program_set_mat4 (RESPRG ("default"), "view", view); + chunkman_render (camera_position); entman_render (view); + glDisable (GL_DEPTH_TEST); + uiman_render (); + glEnable (GL_DEPTH_TEST); + SDL_GL_SwapWindow (sdl_window); } diff --git a/game/src/graphics.h b/game/src/graphics.h index 4ff3a70..40c61b2 100644 --- a/game/src/graphics.h +++ b/game/src/graphics.h @@ -3,11 +3,12 @@ #include #include -#include "util.h" +#include "ui/uiman.h" #include "chunk/chunkman.h" #include "entity/entman.h" #include "state.h" #include "resources.h" +#include "util.h" void graphics_init (); void graphics_render (); diff --git a/game/src/main.c b/game/src/main.c index e08a691..e4aecfb 100644 --- a/game/src/main.c +++ b/game/src/main.c @@ -7,6 +7,7 @@ #include "event/handlers.h" #include "chunk/chunkman.h" #include "entity/templates.h" +#include "ui/uiman.h" #include "state.h" #include "resources.h" #include "graphics.h" @@ -22,6 +23,7 @@ SDL_AppInit (void **appstate, int argc, char **argv) graphics_init (); evt_handlers_init (); resources_init (); + uiman_init (); chunkman_init (); entman_init (); ent_templates_init (); diff --git a/game/src/resources.c b/game/src/resources.c index 9d4842d..c99236e 100644 --- a/game/src/resources.c +++ b/game/src/resources.c @@ -55,6 +55,21 @@ resources_load_texture (const char *path) return texture; } +unsigned int +resources_load_texture_no_interp (const char *path) +{ + SDL_Surface *surface = IMG_Load (path); + + if (surface == NULL) + { + WARN ("Failed to load texture from path %s: %s", path, SDL_GetError ()); + return 0; + } + + unsigned int texture = texture_create_no_interp (surface->w, surface->h, surface->pixels); + return texture; +} + static inline void resources_add_program (char *key, unsigned int program) { @@ -85,6 +100,38 @@ resources_get_texture (char *key) return GPOINTER_TO_INT (g_hash_table_lookup (textures, key)); } +void +res_proj_internal (gpointer key, gpointer value, gpointer userdata) +{ + UNUSED (key); + unsigned int program = GPOINTER_TO_INT (value); + mat4 *projection = userdata; + program_use (program); + program_set_mat4 (program, "projection", *projection); +} + +void +res_view_internal (gpointer key, gpointer value, gpointer userdata) +{ + UNUSED (key); + unsigned int program = GPOINTER_TO_INT (value); + mat4 *view = userdata; + program_use (program); + program_set_mat4 (program, "view", *view); +} + +void +resources_programs_set_projection (mat4 projection) +{ + g_hash_table_foreach (programs, res_proj_internal, projection); +} + +void +resources_programs_set_view (mat4 view) +{ + g_hash_table_foreach (programs, res_view_internal, view); +} + void resources_init () { @@ -95,19 +142,23 @@ resources_init () #define RLP resources_load_program #define RAT resources_add_texture #define RLT resources_load_texture + #define RLTNI resources_load_texture_no_interp - RAP ("default", RLP ("res/shaders/default.vert", "res/shaders/default.frag")); - RAP ("chunk", RLP ("res/shaders/chunk.vert", "res/shaders/chunk.frag")); - RAP ("clouds", RLP ("res/shaders/clouds.vert", "res/shaders/clouds.frag")); + RAP ("default", RLP ("res/shaders/default.vert", "res/shaders/default.frag")); + RAP ("chunk", RLP ("res/shaders/chunk.vert", "res/shaders/chunk.frag")); + RAP ("clouds", RLP ("res/shaders/clouds.vert", "res/shaders/clouds.frag")); + RAP ("uielement", RLP ("res/shaders/uielement.vert", "res/shaders/uielement.frag")); - RAT ("terrain0", RLT ("res/textures/dirt.png")); - RAT ("terrain1", RLT ("res/textures/drygrass.png")); - RAT ("terrain2", RLT ("res/textures/grass.png")); - RAT ("terrain3", RLT ("res/textures/rock_cement.png")); - RAT ("terrain4", RLT ("res/textures/asphalt_gravel.png")); - RAT ("tree0", RLT ("res/textures/tree.png")); - RAT ("grass0", RLT ("res/textures/grass0.png")); - RAT ("clouds", RLT ("res/textures/clouds.png")); + RAT ("terrain0", RLT ("res/textures/dirt.png")); + RAT ("terrain1", RLT ("res/textures/drygrass.png")); + RAT ("terrain2", RLT ("res/textures/grass.png")); + RAT ("terrain3", RLT ("res/textures/rock_cement.png")); + RAT ("terrain4", RLT ("res/textures/asphalt_gravel.png")); + RAT ("tree0", RLT ("res/textures/tree.png")); + RAT ("grass0", RLT ("res/textures/grass0.png")); + RAT ("clouds", RLT ("res/textures/clouds.png")); + + RAT ("font0", RLTNI ("res/textures/font.png")); #undef RAP #undef RLP diff --git a/game/src/resources.h b/game/src/resources.h index 17c9fe9..8cd0634 100644 --- a/game/src/resources.h +++ b/game/src/resources.h @@ -2,13 +2,16 @@ #include #include +#include -#include "program.h" -#include "texture.h" +#include "gl/program.h" +#include "gl/texture.h" #define RESPRG(key) (resources_get_program (key)) #define RESTEX(key) (resources_get_texture (key)) +void resources_programs_set_projection (mat4 projection); +void resources_programs_set_view (mat4 view); unsigned int resources_get_program (char *key); unsigned int resources_get_texture (char *key); void resources_init (); diff --git a/game/src/state.c b/game/src/state.c index c77a7e6..3ceac97 100644 --- a/game/src/state.c +++ b/game/src/state.c @@ -1,7 +1,9 @@ #include "state.h" extern vec3 camera_position; +extern vec3 camera_rotation; extern Entity *player_entity, *sky_entity; +extern UIElement *player_pos; float delta_time; int last_time; @@ -21,8 +23,16 @@ game_state_tick (int ticks) glm_vec3_copy (player_entity->position, camera_position); camera_position[1] += player_entity->scale[1] - 0.2; + player_entity->rotation[1] = camera_rotation[1]; sky_entity->position[0] = camera_position[0]; sky_entity->position[2] = camera_position[2]; + + /* Put the player position into the UI. */ + char *buf = malloc (sizeof (char) * 32); + snprintf (buf, 32, "X:%2.1f Y:%2.1f Z:%2.1f", player_entity->position[0], + player_entity->position[1], player_entity->position[2]); + player_pos->text = buf; + if (flags_get ("moving_forward")) { diff --git a/game/src/state.h b/game/src/state.h index a3919a6..8f94fa0 100644 --- a/game/src/state.h +++ b/game/src/state.h @@ -3,6 +3,7 @@ #include "camera.h" #include "flags.h" #include "entity/entity.h" +#include "ui/uielement.h" void game_state_init (int ticks); void game_state_tick (int ticks); diff --git a/game/src/ui/uielement.c b/game/src/ui/uielement.c new file mode 100644 index 0000000..b9bcbb2 --- /dev/null +++ b/game/src/ui/uielement.c @@ -0,0 +1,83 @@ +#include "uielement.h" + +/* TODO: Convert to mat3's and 2D affine transformations. */ + +static SimpleMesh quad; + +void +ui_element_mesh_init () +{ + quad = SimpleMesh_make_quad1 (); +} + +void +UIElement_add_child (UIElement *parent, UIElement *child) +{ + g_sequence_append (parent->children, child); +} + +void +uiel_render_internal (gpointer data, gpointer userdata) +{ + UNUSED (userdata); + UIElement *element = data; + UIElement_render (element); +} + +void +UIElement_render (UIElement *element) +{ + if (element->parent == NULL) + { + SimpleMesh_bind (&quad); + program_use (RESPRG ("uielement")); + } + + if (element->visible) + { + /* First, we render the background as a quad of + `background_color'. */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, 0); + + mat4 model = GLM_MAT4_IDENTITY_INIT; + glm_translate (model, element->position); + glm_scale (model, element->scale); + + program_set_mat4 (RESPRG ("uielement"), "model", model); + /* TODO: rename this function to vec3_individual or + something. */ + program_set_vec3 (RESPRG ("uielement"), "color", + element->background_color[0], element->background_color[1], + element->background_color[2]); + SimpleMesh_render (&quad); + + /* Now, the tedious, boring, and so on task of rendering the + text, the worst fate of an OpenGL programmer. */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, RESTEX ("font0")); + program_set_vec3 (RESPRG ("uielement"), "color", + element->text_color[0], element->text_color[1], + element->text_color[2]); + + for (size_t i = 0; i < strlen (element->text); ++i) + { + vec3 pos; + float x = -element->scale[0] + (element->scale[1] / 2) + (element->scale[1] * i); + glm_vec3_add (element->position, (vec3) { x, 0.0, 0.0 }, pos); + + glm_mat4_copy (GLM_MAT4_IDENTITY, model); + glm_translate (model, pos); + glm_scale (model, (vec3) { element->scale[1] / 2, element->scale[1], 1.0 }); + + program_set_mat4 (RESPRG ("uielement"), "model", model); + program_set_float (RESPRG ("uielement"), "character", element->text[i]); + SimpleMesh_render (&quad); + } + } + + if (element->children != NULL) + { + g_sequence_foreach (element->children, uiel_render_internal, NULL); + } +} diff --git a/game/src/ui/uielement.h b/game/src/ui/uielement.h new file mode 100644 index 0000000..671ccb3 --- /dev/null +++ b/game/src/ui/uielement.h @@ -0,0 +1,34 @@ +#pragma once + +/* TODO: Patch up with GLib's GTree */ + +#include + +#include +#include + +#include "../util.h" +#include "../resources.h" +#include "../gl/mesh.h" +#include "../gl/program.h" + +typedef struct UIElement +{ + struct UIElement *parent; + GSequence *children; + + void (*on_left_click) (struct UIElement *self); + void (*on_right_click) (struct UIElement *self); + + bool visible; + vec3 text_color; + vec3 background_color; + + vec3 position; + vec3 scale; + char *text; /* Maybe switch to GString? */ +} UIElement; + +void ui_element_mesh_init (); +void UIElement_render (UIElement *element); +void UIElement_add_child (UIElement *parent, UIElement *child); diff --git a/game/src/ui/uiman.c b/game/src/ui/uiman.c new file mode 100644 index 0000000..0023752 --- /dev/null +++ b/game/src/ui/uiman.c @@ -0,0 +1,37 @@ +#include "uiman.h" + +UIElement *player_pos; + +static UIElement ui_root; + +void +uiman_init () +{ + ui_root = (UIElement) { + .parent = NULL, + .children = g_sequence_new (NULL), + .visible = false, + }; + + player_pos = malloc (sizeof (UIElement)); + *player_pos = (UIElement) { + .parent = &ui_root, + .children = NULL, + .visible = true, + .background_color = { 0.0, 0.0, 0.0 }, + .text_color = { 1.0, 1.0, 1.0 }, + .position = { -0.65, 0.92, 0.0 }, + .scale = { 0.3, 0.03, 1.0 }, + .text = "POSITION: 0,0,0" + }; + + UIElement_add_child (&ui_root, player_pos); + + ui_element_mesh_init (); +} + +void +uiman_render () +{ + UIElement_render (&ui_root); +} diff --git a/game/src/ui/uiman.h b/game/src/ui/uiman.h new file mode 100644 index 0000000..af4b179 --- /dev/null +++ b/game/src/ui/uiman.h @@ -0,0 +1,6 @@ +#pragma once + +#include "uielement.h" + +void uiman_init (); +void uiman_render ();