From 2f2a13477790a2a61412c65e760e66576f3e3d10 Mon Sep 17 00:00:00 2001 From: MallocNull Date: Thu, 13 Sep 2018 17:11:09 -0500 Subject: [PATCH] emscripten works :evil: --- CMakeLists.txt | 12 +- resources/client/fonts/scape.bmp | Bin 786486 -> 786486 bytes resources/client/shaders/font/font.frag | 10 +- resources/client/shaders/font/font.vert | 7 +- resources/client/shaders/test.frag | 8 +- resources/client/shaders/test.vert | 11 +- src/client/common.hpp | 14 + src/client/main.cpp | 36 ++- src/client/shaders/_shader.cpp | 109 +++++++ src/client/shaders/_shader.hpp | 52 +++ src/client/shaders/test.hpp | 20 ++ src/client/shell.html | 54 ++++ src/client/ui/font.cpp | 412 ++++++++++++++++++++++++ src/client/ui/font.hpp | 100 ++++++ 14 files changed, 818 insertions(+), 27 deletions(-) create mode 100644 src/client/common.hpp create mode 100644 src/client/shaders/_shader.cpp create mode 100644 src/client/shaders/_shader.hpp create mode 100644 src/client/shaders/test.hpp create mode 100644 src/client/shell.html create mode 100644 src/client/ui/font.cpp create mode 100644 src/client/ui/font.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6912209..c0d11fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,18 @@ endif() set(CMAKE_CXX_STANDARD 11) #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") #if(CMAKE_COMPILER_IS_GNUCXX) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -s USE_SDL=2 --preload-file ../resources/client") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='[\"bmp\"]'") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -std=c++11 -s USE_SDL=2") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} --preload-file ../resources/client") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} --shell-file ../src/client/shell.html") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='[\"bmp\"]'") #endif() +set(CMAKE_EXECUTABLE_SUFFIX ".html") + ## CLIENT BUILD ## #find_package(OpenGL REQUIRED) diff --git a/resources/client/fonts/scape.bmp b/resources/client/fonts/scape.bmp index 50b8e2932b79cb8d02b103d1ef618857a4915d16..5a20acca156d9abe9d0ec4ce2d6a009fd2993c7c 100644 GIT binary patch literal 786486 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 D`2jmr literal 786486 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 QPQVE`0Vm)DoB${A|6?{gQ~&?~ diff --git a/resources/client/shaders/font/font.frag b/resources/client/shaders/font/font.frag index 8995e4a..fa77ba0 100644 --- a/resources/client/shaders/font/font.frag +++ b/resources/client/shaders/font/font.frag @@ -1,14 +1,14 @@ -#version 330 core -in vec2 texCoords; -out vec4 fragColor; +precision mediump float; + +varying vec2 texCoords; uniform vec4 fontColor; uniform sampler2D fontBitmap; void main() { - vec4 outColor = texture(fontBitmap, texCoords); + vec4 outColor = texture2D(fontBitmap, texCoords); if(outColor.xyz == vec3(0.0, 0.0, 0.0)) discard; - fragColor = fontColor * outColor; + gl_FragColor = fontColor * outColor; } \ No newline at end of file diff --git a/resources/client/shaders/font/font.vert b/resources/client/shaders/font/font.vert index ed26e42..e8ca390 100644 --- a/resources/client/shaders/font/font.vert +++ b/resources/client/shaders/font/font.vert @@ -1,8 +1,7 @@ -#version 330 core -layout (location = 0) in vec2 aScreenCoords; -layout (location = 1) in vec2 aTexCoords; +attribute vec2 aScreenCoords; +attribute vec2 aTexCoords; -out vec2 texCoords; +varying vec2 texCoords; uniform mat4 transMatrix; uniform mat4 orthoMatrix; diff --git a/resources/client/shaders/test.frag b/resources/client/shaders/test.frag index 1eff33f..2042ad4 100644 --- a/resources/client/shaders/test.frag +++ b/resources/client/shaders/test.frag @@ -1,9 +1,5 @@ -#version 330 core -in vec2 texCoords; -out vec4 fragColor; - -uniform sampler2D fontBitmap; +precision mediump float; void main() { - fragColor = texture(fontBitmap, texCoords); + gl_FragColor = vec4(0.5, 0, 0, 1); } \ No newline at end of file diff --git a/resources/client/shaders/test.vert b/resources/client/shaders/test.vert index 3ad07a7..08171f8 100644 --- a/resources/client/shaders/test.vert +++ b/resources/client/shaders/test.vert @@ -1,12 +1,5 @@ -#version 330 core -layout (location = 0) in vec4 aScreenCoords; -layout (location = 1) in vec2 aTexCoords; - -out vec2 texCoords; - -uniform mat4 orthoMatrix; +attribute vec2 aPosition; void main() { - gl_Position = orthoMatrix * aScreenCoords; - texCoords = aTexCoords; + gl_Position = vec4(aPosition, 0, 1); } \ No newline at end of file diff --git a/src/client/common.hpp b/src/client/common.hpp new file mode 100644 index 0000000..0defa4f --- /dev/null +++ b/src/client/common.hpp @@ -0,0 +1,14 @@ +#ifndef SOSC_CLIENT_COMMON_H +#define SOSC_CLIENT_COMMON_H + +#include + +#ifdef SOSC_DEBUG +#define SOSC_RESOURCE_PATH (std::string("../resources/client/")) +#else +#define SOSC_RESOURCE_PATH (std::string("resources/")) +#endif + +#define SOSC_RESC(X) (SOSC_RESOURCE_PATH + std::string(X)) + +#endif diff --git a/src/client/main.cpp b/src/client/main.cpp index 9ad29f5..1ec0ca3 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -2,15 +2,33 @@ #include #include #include +#include +#include "ui/font.hpp" static struct { SDL_Window* window; SDL_GLContext gl_ctx; + + sosc::ui::Font* font; + sosc::ui::Text* text; } _ctx; void draw(); int main(int argc, char** argv) { + using namespace sosc; + + /*if(argc != 3) + return -1;*/ + + int client_width = std::stoi(argv[1]), + client_height = std::stoi(argv[2]); + + std::cout << "VERSION 9: " << argc << std::endl + << argv[0] << std::endl + << argv[1] << std::endl + << argv[2] << std::endl; + if(SDL_Init(SDL_INIT_VIDEO) < 0) { std::cout << SDL_GetError() << std::endl; return -1; @@ -21,7 +39,7 @@ int main(int argc, char** argv) { _ctx.window = SDL_CreateWindow( "SockScape Client", 0, 0, - 640, 480, + client_width, client_height, SDL_WINDOW_OPENGL ); @@ -32,12 +50,28 @@ int main(int argc, char** argv) { if(glewInit() != GLEW_OK) return -1; + ui::font_init_subsystem(_ctx.window); + _ctx.font = new ui::Font( + SOSC_RESC("fonts/scape.bmp"), + SOSC_RESC("fonts/scape.dat") + ); + ui::font_set_default(_ctx.font); + + _ctx.text = new ui::Text( + 75, glm::vec4(1, 0, 0, 1), "test text", + 100, 100, 400 + ); + emscripten_set_main_loop(draw, 0, 1); + + std::cout << "end test" << std::endl; } void draw() { glClear(GL_COLOR_BUFFER_BIT); glClearColor(.25, .25, .25, 1); + _ctx.text->Render(); + SDL_GL_SwapWindow(_ctx.window); } \ No newline at end of file diff --git a/src/client/shaders/_shader.cpp b/src/client/shaders/_shader.cpp new file mode 100644 index 0000000..194ac54 --- /dev/null +++ b/src/client/shaders/_shader.cpp @@ -0,0 +1,109 @@ +#include "_shader.hpp" + +sosc::shdr::Shader::Shader() : loaded(false) {} + +bool sosc::shdr::Shader::Load() { + if(this->loaded) + return true; + + this->program = glCreateProgram(); + this->PrepareLoad(); + + this->loaded = true; + return true; +} + +void sosc::shdr::Shader::Start() const { + if(!this->loaded) + return; + + glUseProgram(this->program); +} + +void sosc::shdr::Shader::Stop() const { + if(!this->loaded) + return; + + glUseProgram(0); +} + +void sosc::shdr::Shader::Unload() { + if(!this->loaded) + return; + + PrepareUnload(); + glDeleteProgram(this->program); + this->loaded = false; +} + +bool sosc::shdr::Shader::AttachSource + (const std::string& fileName, GLuint shaderType) +{ + if(this->loaded) + return false; + + GLuint shader = glCreateShader(shaderType); + + std::ifstream file(fileName); + std::stringstream ss; + ss << file.rdbuf(); + std::string source = ss.str(); + + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint err_chk; + glGetShaderiv(shader, GL_COMPILE_STATUS, &err_chk); + if(err_chk == GL_FALSE) { + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &err_chk); + auto msg = new char[err_chk]; + + glGetShaderInfoLog(shader, err_chk, nullptr, msg); + std::cerr << "Error in '"<< fileName << "':" << msg << std::endl; + delete[] msg; + + return false; + } + + glAttachShader(this->program, shader); + this->shaders.push_back(shader); + return true; +} + +bool sosc::shdr::Shader::LinkProgram() { + GLint err_chk; + + glLinkProgram(this->program); + glGetProgramiv(this->program, GL_LINK_STATUS, &err_chk); + if(err_chk == GL_FALSE) { + glGetProgramiv(this->program, GL_INFO_LOG_LENGTH, &err_chk); + auto msg = new char[err_chk]; + + glGetProgramInfoLog(this->program, err_chk, nullptr, msg); + std::cerr << "Error linking shader: " << msg << std::endl; + delete[] msg; + + return false; + } + + for(const auto& shader : this->shaders) + glDeleteShader(shader); + return true; +} + +bool sosc::shdr::Shader::LoadUniforms(std::vector names) { + if(this->loaded) + return false; + + GLint id; + this->locations.clear(); + for(const auto& name : names) { + if((id = glGetUniformLocation(this->program, name.c_str())) == -1) + 0; //return false; + + this->locations.push_back(id); + } + + return true; +} \ No newline at end of file diff --git a/src/client/shaders/_shader.hpp b/src/client/shaders/_shader.hpp new file mode 100644 index 0000000..641dd82 --- /dev/null +++ b/src/client/shaders/_shader.hpp @@ -0,0 +1,52 @@ +#ifndef SOSC_SHADER_CORE_H +#define SOSC_SHADER_CORE_H + +#include "common.hpp" +#include +#include +#include +#include +#include +#include + +namespace sosc { +namespace shdr { +class Shader { +public: + Shader(); + + bool Load(); + void Start() const; + + inline GLint operator[] (std::vector::size_type index) { + if(index >= locations.size()) + return -1; + + return locations[index]; + } + + inline GLint Uniform(std::vector::size_type index) { + if(index >= locations.size()) + return -1; + + return locations[index]; + } + + void Stop() const; + void Unload(); +protected: + virtual void PrepareLoad() = 0; + virtual void PrepareUnload() {}; + + bool AttachSource(const std::string& fileName, GLuint shaderType); + bool LinkProgram(); + bool LoadUniforms(std::vector names); +private: + std::vector locations; + std::vector shaders; + GLuint program; + bool loaded; +}; +}} + +#endif diff --git a/src/client/shaders/test.hpp b/src/client/shaders/test.hpp new file mode 100644 index 0000000..df3eabf --- /dev/null +++ b/src/client/shaders/test.hpp @@ -0,0 +1,20 @@ +#ifndef SOSC_SHADER_TEST_H +#define SOSC_SHADER_TEST_H + +#include +#include "common.hpp" +#include "_shader.hpp" + +namespace sosc { +namespace shdr { +class TestShader : public Shader { +protected: + void PrepareLoad() override { + this->AttachSource(SOSC_RESC("shaders/test.vert"), GL_VERTEX_SHADER); + this->AttachSource(SOSC_RESC("shaders/test.frag"), GL_FRAGMENT_SHADER); + this->LinkProgram(); + } +}; +}} + +#endif diff --git a/src/client/shell.html b/src/client/shell.html new file mode 100644 index 0000000..8793667 --- /dev/null +++ b/src/client/shell.html @@ -0,0 +1,54 @@ + + + + + SockScape Client + + + +
+ +
+ + {{{ SCRIPT }}} + + \ No newline at end of file diff --git a/src/client/ui/font.cpp b/src/client/ui/font.cpp new file mode 100644 index 0000000..6b8bb7d --- /dev/null +++ b/src/client/ui/font.cpp @@ -0,0 +1,412 @@ +#include "font.hpp" + +#define LILEND_UNPACK(X,N) \ + (((uint32_t)(X)[N]) | \ + ((((uint32_t)(X)[(N)+1])) << 8u) | \ + ((((uint32_t)(X)[(N)+2])) << 16u) | \ + ((((uint32_t)(X)[(N)+3])) << 24u)) + +namespace sosc { +namespace ui { +class FontShader; +}} + +// STATE STRUCT // + +static struct { + sosc::ui::FontShader* shader; + sosc::ui::Font* default_font; +} _font_ctx; + +// FONT SHADER CLASS // + +namespace sosc { +namespace ui { +class FontShader : public sosc::shdr::Shader { +public: + enum Uniforms { + ORTHO_MATRIX = 0, + TRANSLATION_MATRIX, + FONT_BITMAP, + FONT_COLOR + }; + + void UpdateWindow(SDL_Window *window) { + this->Start(); + + int width, height; + SDL_GetWindowSize(window, &width, &height); + glm::mat4 orthoMatrix = + glm::ortho(0.f, (float)width, (float)height, 0.f); + glUniformMatrix4fv( + (*this)[ORTHO_MATRIX], 1, GL_FALSE, + glm::value_ptr(orthoMatrix) + ); + + this->Stop(); + } + +protected: + void PrepareLoad() override { + this->AttachSource( + SOSC_RESC("shaders/font/font.vert"), GL_VERTEX_SHADER + ); + this->AttachSource( + SOSC_RESC("shaders/font/font.frag"), GL_FRAGMENT_SHADER + ); + + this->LinkProgram(); + this->LoadUniforms({ + "orthoMatrix", + "transMatrix", + "fontBitmap", + "fontColor" + }); + } +}; +}} + +// SUBSYSTEM FUNCS // + +void sosc::ui::font_init_subsystem(SDL_Window* window) { + _font_ctx.shader = new FontShader(); + _font_ctx.shader->Load(); + _font_ctx.shader->UpdateWindow(window); + _font_ctx.default_font = nullptr; +} + +void sosc::ui::font_set_default(Font *font) { + _font_ctx.default_font = font; +} + +void sosc::ui::font_window_changed(SDL_Window* window) { + _font_ctx.shader->UpdateWindow(window); +} + +void sosc::ui::font_deinit_subsystem() { + _font_ctx.shader->Unload(); + delete _font_ctx.shader; +} + +// FONT CLASS // + +sosc::ui::Font::Font + (const std::string& bitmapPath, const std::string& dataPath, + bool useNearest) +{ + this->loaded = false; + this->Load(bitmapPath, dataPath); +} + +bool sosc::ui::Font::Load + (const std::string& bitmapPath, const std::string& dataPath, + bool useNearest) +{ + if(this->loaded) + this->Unload(); + + SDL_RWops* rwop = SDL_RWFromFile(bitmapPath.c_str(), "rb"); + SDL_Surface* image = SDL_LoadBMP_RW(rwop, 1); + if(!image) + return false; + + char buffer[0x111]; + std::ifstream dataFile(dataPath, std::ios::in | std::ios::binary); + if(!dataFile.is_open()) + return false; + dataFile.read(buffer, 0x111); + dataFile.close(); + std::string data(buffer, 0x111); + if(buffer[0x10] != 0) + return false; + + this->width = (uint32_t)image->w; + this->height = (uint32_t)image->h; + + this->cell_width = LILEND_UNPACK(buffer, 0x08); + this->cell_height = LILEND_UNPACK(buffer, 0x0C); + + for(int i = 0; i < 256; ++i) { + auto glyph = &this->glyphs[i]; + auto width = (uint8_t)buffer[0x11 + i]; + + glyph->width = (double)width / (double)this->cell_width; + + int x = (this->cell_width * i) % this->width; + int y = ((this->cell_width * i) / this->width) * this->cell_height; + + glyph->top_left = glm::vec2( + (double)x / (double)this->width, + 1 - (double)y / (double)this->height + ); + glyph->top_right = glm::vec2( + (double)(x + width) / (double)this->width, + 1 - (double)y / (double)this->height + ); + glyph->bottom_left = glm::vec2( + (double)x / (double)this->width, + 1 - (double)(y + this->cell_height) / (double)this->height + ); + glyph->bottom_right = glm::vec2( + (double)(x + width) / (double)this->width, + 1 - (double)(y + this->cell_height) / (double)this->height + ); + } + + glGenTextures(1, &this->texture); + glBindTexture(GL_TEXTURE_2D, this->texture); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB, + this->width, this->height, 0, + GL_RGB, + GL_UNSIGNED_BYTE, image->pixels + ); + + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + useNearest ? GL_NEAREST : GL_LINEAR + ); + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + useNearest ? GL_NEAREST : GL_LINEAR + ); + glBindTexture(GL_TEXTURE_2D, 0); + + SDL_FreeSurface(image); + this->loaded = true; + return true; +} + +void sosc::ui::Font::BindBitmap() const { + if(!this->loaded) + return; + + glBindTexture(GL_TEXTURE_2D, this->texture); +} + +void sosc::ui::Font::UnbindBitmap() const { + if(!this->loaded) + return; + + glBindTexture(GL_TEXTURE_2D, 0); +} + +void sosc::ui::Font::Unload() { + glDeleteTextures(1, &this->texture); + this->loaded = false; +} + +// TEXT CLASS // + +sosc::ui::Text::Text() { + this->font = _font_ctx.default_font; + this->font_size = 0; + this->vertices = nullptr; + this->tex_coords = nullptr; + + glGenVertexArrays(1, &this->vao); + glGenBuffers(2, this->vbos); +} + +sosc::ui::Text::Text + (sosc::ui::Font *font, uint32_t size, const glm::vec4& color) : Text() +{ + this->font = font; + this->font_size = size; + this->font_color = color; +} + +sosc::ui::Text::Text(uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) : Text() +{ + this->Set(size, color, text, x, y, w); +} + +sosc::ui::Text::Text + (sosc::ui::Font *font, uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) : Text() +{ + this->font = font; + this->Set(size, color, text, x, y, w); +} + +void sosc::ui::Text::Set(uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) +{ + this->font_size = size; + this->font_color = color; + this->text = text; + if(w != 0) + this->wrap_width = w; + this->trans_matrix = glm::translate(glm::mat4(1.f), glm::vec3(x, y, 0.f)); + this->Redraw(); +} + +void sosc::ui::Text::SetFont(Font *font, uint32_t size) { + this->font = font; + if(size != 0) + this->font_size = size; + this->Redraw(); +} + +void sosc::ui::Text::SetFontSize(uint32_t size) { + this->font_size = size; + this->Redraw(); +} + +void sosc::ui::Text::SetFontColor(const glm::vec4 &color) { + this->font_color = color; +} + +void sosc::ui::Text::SetText(const std::string &text) { + this->text = text; + this->Redraw(); +} + +void sosc::ui::Text::SetPosition(uint32_t x, uint32_t y) { + this->trans_matrix = glm::translate(glm::mat4(1.f), glm::vec3(x, y, 0.f)); +} + +void sosc::ui::Text::SetWrapWidth(uint32_t w) { + this->wrap_width = w; + this->Redraw(); +} + +uint32_t sosc::ui::Text::GetHeight() const { + return this->GetLineCount() * this->font_size; +} + +uint32_t sosc::ui::Text::GetLineCount() const { + if(this->wrap_width == 0) + return 1; + else { + uint32_t count = 1, topleft_x = 0; + for(const auto c : this->text) { + auto width = (uint32_t)((*this->font)[c].width * this->font_size); + + if(topleft_x + width > this->wrap_width) { + ++count; + topleft_x = 0; + } + + topleft_x += width; + } + return count; + } +} + +void sosc::ui::Text::Render() { + auto shdr = _font_ctx.shader; + + shdr->Start(); + glUniformMatrix4fv( + (*shdr)[shdr->TRANSLATION_MATRIX], + 1, GL_FALSE, + glm::value_ptr(this->trans_matrix) + ); + glUniform4f( + (*shdr)[shdr->FONT_COLOR], + this->font_color.r, this->font_color.g, + this->font_color.b, this->font_color.a + ); + + glActiveTexture(GL_TEXTURE0); + this->font->BindBitmap(); + + glBindVertexArray(this->vao); + glDrawArrays(GL_TRIANGLES, 0, this->vertex_count); + glBindVertexArray(0); + + this->font->UnbindBitmap(); + shdr->Stop(); +} + +void sosc::ui::Text::Destroy() { + glDeleteBuffers(2, this->vbos); + glDeleteVertexArrays(1, &this->vao); + + delete[] this->vertices; + delete[] this->tex_coords; +} + +void sosc::ui::Text::Redraw() { + this->vertex_count = (GLuint)(6 * this->text.length()); + + delete[] this->vertices; + this->vertices = new float[this->vertex_count * 2]; + + delete[]this->tex_coords; + this->tex_coords = new float[this->vertex_count * 2]; + + uint32_t top_x = 0, top_y = 0; + for(int i = 0; i < this->text.length(); ++i) { + auto glyph = (*this->font)[this->text[i]]; + uint32_t width = (uint32_t)(this->font_size * glyph.width), + height = this->font_size; + + if(top_x + width > this->wrap_width && this->wrap_width != 0) { + top_x = 0; + top_y += height; + } + + /// TRIANGLE 1 /// + // TOP LEFT + this->vertices[i*12] = top_x; + this->vertices[i*12 + 1] = top_y; + this->tex_coords[i*12] = glyph.top_left.x; + this->tex_coords[i*12 + 1] = glyph.top_left.y; + // TOP RIGHT + this->vertices[i*12 + 2] = top_x + width; + this->vertices[i*12 + 3] = top_y; + this->tex_coords[i*12 + 2] = glyph.top_right.x; + this->tex_coords[i*12 + 3] = glyph.top_right.y; + // BOTTOM LEFT + this->vertices[i*12 + 4] = top_x; + this->vertices[i*12 + 5] = top_y + height; + this->tex_coords[i*12 + 4] = glyph.bottom_left.x; + this->tex_coords[i*12 + 5] = glyph.bottom_left.y; + + /// TRIANGLE 2 /// + // BOTTOM LEFT + this->vertices[i*12 + 6] = top_x; + this->vertices[i*12 + 7] = top_y + height; + this->tex_coords[i*12 + 6] = glyph.bottom_left.x; + this->tex_coords[i*12 + 7] = glyph.bottom_left.y; + // TOP RIGHT + this->vertices[i*12 + 8] = top_x + width; + this->vertices[i*12 + 9] = top_y; + this->tex_coords[i*12 + 8] = glyph.top_right.x; + this->tex_coords[i*12 + 9] = glyph.top_right.y; + // BOTTOM RIGHT + this->vertices[i*12 + 10] = top_x + width; + this->vertices[i*12 + 11] = top_y + height; + this->tex_coords[i*12 + 10] = glyph.bottom_right.x; + this->tex_coords[i*12 + 11] = glyph.bottom_right.y; + + top_x += width; + } + + glBindVertexArray(this->vao); + + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, this->vbos[0]); + glBufferData( + GL_ARRAY_BUFFER, + this->vertex_count * 2 * sizeof(float), + this->vertices, + GL_STATIC_DRAW + ); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, this->vbos[1]); + glBufferData( + GL_ARRAY_BUFFER, + this->vertex_count * 2 * sizeof(float), + this->tex_coords, + GL_STATIC_DRAW + ); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + + glBindVertexArray(0); +} \ No newline at end of file diff --git a/src/client/ui/font.hpp b/src/client/ui/font.hpp new file mode 100644 index 0000000..74bb1d5 --- /dev/null +++ b/src/client/ui/font.hpp @@ -0,0 +1,100 @@ +#ifndef SOSC_UI_FONT_H +#define SOSC_UI_FONT_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "shaders/_shader.hpp" + +namespace sosc { +namespace ui { +class Font; +void font_init_subsystem(SDL_Window* window); +void font_set_default(sosc::ui::Font* font); +void font_window_changed(SDL_Window* window); +void font_deinit_subsystem(); + +class Font { +public: + struct glyph_t { + double width; + glm::vec2 + top_left, + top_right, + bottom_left, + bottom_right; + }; + + Font() : loaded(false) {} + Font(const std::string& bitmapPath, + const std::string& dataPath, bool useNearest = true); + bool Load(const std::string& bitmapPath, + const std::string& dataPath, bool useNearest = true); + + inline const glyph_t& operator[] (char c) const { + return this->glyphs[(uint8_t)c]; + } + + void Unload(); +private: + void BindBitmap() const; + void UnbindBitmap() const; + + bool loaded; + GLuint texture; + uint32_t + width, height, + cell_width, cell_height; + glyph_t glyphs[256]; + + friend class Text; +}; + +class Text { +public: + Text(); + Text(Font* font, uint32_t size, const glm::vec4& color); + Text(uint32_t size, const glm::vec4& color, const std::string& text, + uint32_t x, uint32_t y, uint32_t w = 0); + Text(Font* font, uint32_t size, const glm::vec4& color, + const std::string& text, uint32_t x, uint32_t y, uint32_t w = 0); + + void Set(uint32_t size, const glm::vec4& color, const std::string& text, + uint32_t x, uint32_t y, uint32_t w = 0); + void SetFont(Font* font, uint32_t size = 0); + void SetFontSize(uint32_t size); + void SetFontColor(const glm::vec4& color); + void SetText(const std::string& text); + void SetPosition(uint32_t x, uint32_t y); + void SetWrapWidth(uint32_t w); + + uint32_t GetHeight() const; + uint32_t GetLineCount() const; + + void Render(); + + void Destroy(); +private: + void Redraw(); + + Font* font; + glm::vec4 font_color; + uint32_t font_size, + wrap_width; + std::string text; + glm::mat4 trans_matrix; + + GLsizei vertex_count; + float* vertices; + float* tex_coords; + GLuint vao, vbos[2]; +}; +}} + +#endif