Key mappings, basic text rendering

This commit is contained in:
MrLetsplay 2022-10-16 14:30:12 +02:00
parent e71ffe3f2a
commit ff1ad05ba4
16 changed files with 724 additions and 65 deletions

View File

@ -59,8 +59,13 @@ add_library(kekengine OBJECT ${KEKENGINE_SOURCE_FILES})
set_property(TARGET kekengine PROPERTY POSITION_INDEPENDENT_CODE 1) set_property(TARGET kekengine PROPERTY POSITION_INDEPENDENT_CODE 1)
add_dependencies(kekengine kekengine_res) add_dependencies(kekengine kekengine_res)
set_property(TARGET microtar PROPERTY POSITION_INDEPENDENT_CODE 1) target_link_libraries(kekengine PUBLIC microtar_static)
target_link_libraries(kekengine PUBLIC microtar)
# Freetype
find_package(PkgConfig REQUIRED)
pkg_check_modules(FREETYPE REQUIRED freetype2)
target_link_libraries(kekengine PUBLIC ${FREETYPE_LIBRARIES})
target_include_directories(kekengine PRIVATE ${FREETYPE_INCLUDE_DIRS})
if(UNIX) if(UNIX)
target_link_libraries(kekengine PUBLIC glfw GLEW GL) target_link_libraries(kekengine PUBLIC glfw GLEW GL)
@ -118,7 +123,6 @@ if(${KEKENGINE_BUILD_KEKGAME})
add_executable(kekgame ${KEKGAME_SOURCE_FILES}) add_executable(kekgame ${KEKGAME_SOURCE_FILES})
add_dependencies(kekgame kekgame_res) add_dependencies(kekgame kekgame_res)
if(WIN32) if(WIN32)
target_link_options(kekgame PUBLIC -static-libgcc -static-libstdc++ -static) target_link_options(kekgame PUBLIC -static-libgcc -static-libstdc++ -static)
endif() endif()

@ -1 +1 @@
Subproject commit b240d953b12dc87c625743a9fb55634b0f0f3608 Subproject commit 66dbf3cf8623b522d6413c6e994ec45f66288bd5

View File

@ -0,0 +1,53 @@
#include "input.h"
#include "internal.h"
namespace kek::Defaults {
static int
keyForward,
keyBackward,
keyLeft,
keyRight,
keyUp,
keyDown;
void defaultInput(GLFWwindow *window, void *data) {
if(Input::getKeyState(keyForward) == GLFW_PRESS) {
kekData.activeCamera->translate(kekData.activeCamera->direction * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(Input::getKeyState(keyBackward) == GLFW_PRESS) {
kekData.activeCamera->translate(kekData.activeCamera->direction * -KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(Input::getKeyState(keyLeft) == GLFW_PRESS) {
glm::vec3 camRight = glm::normalize(glm::cross(kekData.activeCamera->direction, glm::vec3(0.0f, 1.0f, 0.0f)));
kekData.activeCamera->translate(-camRight * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(Input::getKeyState(keyRight) == GLFW_PRESS) {
glm::vec3 camRight = glm::normalize(glm::cross(kekData.activeCamera->direction, glm::vec3(0.0f, 1.0f, 0.0f)));
kekData.activeCamera->translate(camRight * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(Input::getKeyState(keyUp) == GLFW_PRESS) {
kekData.activeCamera->translateY(KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(Input::getKeyState(keyDown) == GLFW_PRESS) {
kekData.activeCamera->translateY(-KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
}
void init() {
keyForward = Input::createKeyMapping("Forward", GLFW_KEY_W);
keyBackward = Input::createKeyMapping("Backward", GLFW_KEY_S);
keyLeft = Input::createKeyMapping("Left", GLFW_KEY_A);
keyRight = Input::createKeyMapping("Right", GLFW_KEY_D);
keyUp = Input::createKeyMapping("Up", GLFW_KEY_SPACE);
keyDown = Input::createKeyMapping("Down", GLFW_KEY_LEFT_CONTROL);
Input::addPeriodicCallback(PeriodicCallback(defaultInput, nullptr));
}
}

View File

@ -21,11 +21,14 @@
#include "constants.h" #include "constants.h"
#include "gameobject.h" #include "gameobject.h"
#include "scene.h" #include "scene.h"
#include "defaults.h"
kek::KekData kek::kekData; kek::KekData kek::kekData;
namespace kek::Engine { namespace kek::Engine {
static TextObject *fpsText;
static void framebufferSizeCallback(GLFWwindow *window, int w, int h) { static void framebufferSizeCallback(GLFWwindow *window, int w, int h) {
glViewport(0, 0, w, h); glViewport(0, 0, w, h);
kekData.screenWidth = w; kekData.screenWidth = w;
@ -160,10 +163,21 @@ int init() {
kekData.shader = new Shader("shader/mesh/vertex.glsl", "shader/mesh/fragment.glsl"); kekData.shader = new Shader("shader/mesh/vertex.glsl", "shader/mesh/fragment.glsl");
kekData.shader->initLighting(); kekData.shader->initLighting();
FT_Error error = FT_Init_FreeType(&kekData.freetype);
if(error) {
ErrorDialog::showError("Failed to initialize FreeType: " + std::string(FT_Error_String(error)));
glfwTerminate();
return KEK_ERROR;
}
Defaults::init();
fpsText = new TextObject(new Font("font/MaredivRegular-yeg3.ttf"), "HELLO WORLD!");
return KEK_SUCCESS; return KEK_SUCCESS;
} }
int start() { int start() {
int prevTime = 0;
while(!glfwWindowShouldClose(kekData.window)) { while(!glfwWindowShouldClose(kekData.window)) {
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
@ -175,32 +189,6 @@ int start() {
cb.second(kekData.window); cb.second(kekData.window);
} }
if(glfwGetKey(kekData.window, GLFW_KEY_W) == GLFW_PRESS) {
kekData.activeCamera->translate(kekData.activeCamera->direction * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_S) == GLFW_PRESS) {
kekData.activeCamera->translate(kekData.activeCamera->direction * -KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_A) == GLFW_PRESS) {
glm::vec3 camRight = glm::normalize(glm::cross(kekData.activeCamera->direction, glm::vec3(0.0f, 1.0f, 0.0f)));
kekData.activeCamera->translate(-camRight * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_D) == GLFW_PRESS) {
glm::vec3 camRight = glm::normalize(glm::cross(kekData.activeCamera->direction, glm::vec3(0.0f, 1.0f, 0.0f)));
kekData.activeCamera->translate(camRight * KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_SPACE) == GLFW_PRESS) {
kekData.activeCamera->translateY(KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) {
kekData.activeCamera->translateY(-KEK_NOCLIP_SPEED * kekData.lastFrameTime);
}
if(glfwGetKey(kekData.window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { if(glfwGetKey(kekData.window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
break; break;
} }
@ -243,39 +231,6 @@ int start() {
int numPointLights = 0, numDirectionalLights = 0, numSpotLights = 0; int numPointLights = 0, numDirectionalLights = 0, numSpotLights = 0;
for(Light *light : shaderLights) { for(Light *light : shaderLights) {
/*std::string prefix = "lights[" + std::to_string(i) + "].";
switch(light->getType()) {
case LightType::POINT:
{
PointLight *l = (PointLight *) light;
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "color").c_str()), 1, glm::value_ptr(l->color));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "position").c_str()), 1, glm::value_ptr(l->getPosition()));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "attenuation").c_str()), 1, glm::value_ptr(glm::vec3(l->constant, l->linear, l->quadratic)));
numPointLights++;
break;
}
case LightType::DIRECTIONAL:
{
DirectionalLight *l = (DirectionalLight *) light;
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "color").c_str()), 1, glm::value_ptr(l->color));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "direction").c_str()), 1, glm::value_ptr(l->direction));
numDirectionalLights++;
break;
}
case LightType::SPOT:
{
SpotLight *l = (SpotLight *) light;
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "color").c_str()), 1, glm::value_ptr(l->color));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "position").c_str()), 1, glm::value_ptr(l->getPosition()));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "direction").c_str()), 1, glm::value_ptr(l->direction));
glUniform3fv(glGetUniformLocation(kekData.shader->id, (prefix + "attenuation").c_str()), 1, glm::value_ptr(glm::vec3(l->constant, l->linear, l->quadratic)));
glUniform2fv(glGetUniformLocation(kekData.shader->id, (prefix + "cutoff").c_str()), 1, glm::value_ptr(glm::vec2(glm::cos(l->innerCutoff), glm::cos(l->outerCutoff))));
numSpotLights++;
break;
}
}*/
ShaderLight shLight; ShaderLight shLight;
//shLight.color = light->color; //shLight.color = light->color;
memcpy(shLight.color, glm::value_ptr(light->color), sizeof(shLight.color)); memcpy(shLight.color, glm::value_ptr(light->color), sizeof(shLight.color));
@ -321,6 +276,18 @@ int start() {
if(kekData.activeScene) kekData.activeScene->draw(kekData.shader); if(kekData.activeScene) kekData.activeScene->draw(kekData.shader);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
int time = (int) (glfwGetTime() * 10);
if(time != prevTime) fpsText->setText(std::to_string((int) floor(1.0f / kekData.lastFrameTime)) + " (" + std::to_string(kekData.lastFrameTime * 1000) + ")");
prevTime = time;
fpsText->getFont()->drawText(fpsText, 0, fpsText->getMetrics(24).height, 24, Colors::RED);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
// Swap buffers and poll window events // Swap buffers and poll window events
glfwSwapBuffers(kekData.window); glfwSwapBuffers(kekData.window);
glfwPollEvents(); glfwPollEvents();
@ -328,8 +295,8 @@ int start() {
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> secsTaken = end - start; std::chrono::duration<float> secsTaken = end - start;
kekData.lastFrameTime = secsTaken.count(); kekData.lastFrameTime = secsTaken.count();
std::cout << "FT: " << kekData.lastFrameTime << std::endl; //std::cout << "FT: " << kekData.lastFrameTime << '\n';
std::cout << "FR: " << (1.0f / kekData.lastFrameTime) << std::endl; //std::cout << "FR: " << (1.0f / kekData.lastFrameTime) << '\n';
} }
return KEK_SUCCESS; return KEK_SUCCESS;

344
src/kekengine/cpp/fonts.cpp Normal file
View File

@ -0,0 +1,344 @@
#include "fonts.h"
#include <stb/stb_image_write.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/gtc/type_ptr.hpp>
#include <codecvt>
#include <locale>
#include "shader.h"
#include "engine.h"
#include "constants.h"
#include "internal.h"
namespace kek {
Shader *fontShader = nullptr;
TextObject::TextObject(Font *font, std::string text) {
assert(text.length() > 0);
this->font = font;
this->text = text;
loadChars();
}
TextObject::~TextObject() {
destroy();
}
void TextObject::destroy() {
for(auto o : blocks) {
glDeleteVertexArrays(1, &o.second.vao);
glDeleteBuffers(1, &o.second.vbo);
}
blocks.clear();
}
TextObject::TextObject(TextObject &&other): blocks(other.blocks) {
other.blocks.clear();
}
TextObject &TextObject::operator=(TextObject &&other) {
if(this != &other) {
destroy();
std::swap(blocks, other.blocks);
}
return *this;
}
Font *TextObject::getFont() {
return font;
}
void TextObject::setText(std::string text) {
this->text = text;
loadChars();
}
std::string TextObject::getText() {
return text;
}
TextMetrics TextObject::getMetrics(int sizePixels) {
float sizeRatio = (float) sizePixels / KEK_FONT_RESOLUTION;
return TextMetrics((int) (sizeRatio * offsetX), (int) (sizeRatio * offsetY), (int) (sizeRatio * width), (int) (sizeRatio * height));
}
static std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32cvt;
struct RenderChar {
float data[24];
RenderChar(float xPos, float yPos, float w, float h, float texL, float texR, float texU, float texD) {
float coords[] = {
xPos, yPos, texL, texU,
xPos, yPos + h, texL, texD,
xPos + w, yPos, texR, texU,
xPos + w, yPos, texR, texU,
xPos, yPos + h, texL, texD,
xPos + w, yPos + h, texR, texD
};
for(int i = 0; i < 24; i++) data[i] = coords[i];
}
};
void TextObject::allocateBuffer(TextBlock *block, int numChars) {
unsigned int targetSize = (numChars + KEK_TEXT_BLOCK_SIZE - 1) / KEK_TEXT_BLOCK_SIZE * KEK_TEXT_BLOCK_SIZE; // ceil(numChars / KEK_TEXT_BLOCK_SIZE) * KEK_TEXT_BLOCK_SIZE
block->bufferSize = targetSize;
glGenVertexArrays(1, &block->vao);
glGenBuffers(1, &block->vbo);
glBindVertexArray(block->vao);
glBindBuffer(GL_ARRAY_BUFFER, block->vbo);
glBufferData(GL_ARRAY_BUFFER, targetSize * sizeof(RenderChar), NULL, GL_DYNAMIC_DRAW); // 6 verts/char, 4 floats/vertex
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) (2 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void TextObject::loadChars() {
std::map<unsigned int, std::vector<RenderChar>> chars;
std::u32string str = utf32cvt.from_bytes(text);
float x = 0, y = 0;
int lineHeight = font->getDefaultMetrics().lineHeight;
int offX = -1, offY = 0, maxH = 0;
for(char32_t codepoint : str) {
if(codepoint == U'\n') {
y += lineHeight;
x = 0;
continue;
}
unsigned int charBlock = codepoint >> KEK_FONT_BITMAP_CHAR_BITS;
CharacterBlock block = font->getCharacterBlock(charBlock);
Character ch = block.characters[codepoint & KEK_FONT_BITMAP_CHAR_MASK];
float xPos = x + ch.bearing.x;
float yPos = y - ch.bearing.y;
if(offX == -1) offX = ch.bearing.x;
if(ch.bearing.y > offY) offY = ch.bearing.y;
float w = ch.size.x;
float h = ch.size.y;
if(h > maxH) maxH = h;
float texL = (ch.textureX) / (float) KEK_FONT_BITMAP_WIDTH;
float texR = (ch.textureX + ch.size.x) / (float) KEK_FONT_BITMAP_WIDTH;
float texU = (ch.textureY) / (float) KEK_FONT_BITMAP_HEIGHT;
float texD = (ch.textureY + ch.size.y) / (float) KEK_FONT_BITMAP_HEIGHT;
RenderChar rCh = RenderChar(xPos, yPos, w, h, texL, texR, texU, texD);
auto charsForBlock = chars.find(charBlock);
if(charsForBlock != chars.end()) {
charsForBlock->second.push_back(rCh);
}else {
std::vector<RenderChar> chs;
chs.push_back(rCh);
chars[charBlock] = chs;
}
x += (ch.advance >> 6); // == ch.advance / 64
}
this->offsetX = offX;
this->offsetY = offY;
this->width = x;
this->height = maxH;
auto it = chars.begin();
while(it != chars.end()) {
auto oldTextBlock = blocks.find(it->first);
int textLength = it->second.size();
if(oldTextBlock != blocks.end()) {
// Update buffer
TextBlock *oldBlock = &oldTextBlock->second;
if(textLength != 0 && (textLength > (int) oldBlock->bufferSize || textLength < (int) oldBlock->bufferSize - 2 * KEK_TEXT_BLOCK_SIZE)) {
// String is either too large for the current buffer or at least one block smaller, reallocate
// TODO: no need to delete, just call glBufferData
glDeleteVertexArrays(1, &oldBlock->vao);
glDeleteBuffers(1, &oldBlock->vbo);
allocateBuffer(oldBlock, text.length());
}
oldBlock->length = textLength;
glNamedBufferSubData(oldBlock->vbo, 0, textLength * sizeof(RenderChar), &it->second[0]);
}else {
// Allocate Buffer
TextBlock block;
allocateBuffer(&block, textLength);
block.length = textLength;
block.characterBlock = it->first;
glNamedBufferSubData(block.vbo, 0, textLength * sizeof(RenderChar), &it->second[0]);
blocks[it->first] = block;
}
it++;
}
auto it2 = blocks.begin();
while(it2 != blocks.end()) {
if(chars.find(it2->first) == chars.end()) {
it2 = blocks.erase(it2);
}else {
it2++;
}
}
}
Font::Font(std::string path) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
fileMemory = Resource::loadResource(path);
if(!fileMemory) {
std::cerr << "Failed to load font: " << path << std::endl;
throw std::exception();
}
if(FT_Error err = FT_New_Memory_Face(kekData.freetype, (FT_Byte *) fileMemory->buffer, fileMemory->length, 0, &face)) {
std::cout << "Failed to load font: " << FT_Error_String(err) << std::endl;
return;
}
FT_Set_Pixel_Sizes(face, 0, KEK_FONT_RESOLUTION);
FT_Select_Charmap(face, FT_Encoding::FT_ENCODING_UNICODE);
lineHeight = face->size->metrics.height;
ascender = face->size->metrics.ascender;
descender = face->size->metrics.descender;
if(!fontShader) fontShader = new Shader("shader/text/vertex.glsl", "shader/text/fragment.glsl");
}
Font::~Font() {
FT_Done_Face(face);
delete fileMemory;
}
CharacterBlock Font::getCharacterBlock(unsigned int block) {
auto b = characterBlocks.find(block);
if(b == characterBlocks.end()) return generateCharacterBlock(block);
return b->second;
}
CharacterBlock Font::generateCharacterBlock(unsigned int block) {
CharacterBlock chBlock;
unsigned char *textureBytes = new unsigned char[KEK_FONT_BITMAP_WIDTH * KEK_FONT_BITMAP_HEIGHT]; // grayscale (1 byte per pixel) texture
memset(textureBytes, 0, KEK_FONT_BITMAP_WIDTH * KEK_FONT_BITMAP_HEIGHT);
for(unsigned int c = 0; c < KEK_FONT_BITMAP_WIDTH_BLOCKS * KEK_FONT_BITMAP_HEIGHT_BLOCKS; c++) {
if(FT_Error err = FT_Load_Char(face, (block << KEK_FONT_BITMAP_CHAR_BITS) + c, FT_LOAD_RENDER)) {
std::cout << "Failed to load glyph: " << FT_Error_String(err) << std::endl;
continue;
}
int col = c % KEK_FONT_BITMAP_WIDTH_BLOCKS;
int row = c / KEK_FONT_BITMAP_WIDTH_BLOCKS;
for(unsigned int r = 0; r < face->glyph->bitmap.rows; r++) {
memcpy(
textureBytes + row * KEK_FONT_RESOLUTION * KEK_FONT_BITMAP_WIDTH + col * KEK_FONT_RESOLUTION + r * KEK_FONT_BITMAP_WIDTH,
face->glyph->bitmap.buffer + r * face->glyph->bitmap.width,
face->glyph->bitmap.width);
}
Character ch;
ch.textureX = col * KEK_FONT_RESOLUTION;
ch.textureY = row * KEK_FONT_RESOLUTION;
ch.size = glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows);
ch.bearing = glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top);
ch.advance = face->glyph->advance.x;
chBlock.characters[c] = ch;
}
// stbi_write_png(("/home/mr/Desktop/bitmap_" + std::to_string(block) + ".png").c_str(), KEK_FONT_BITMAP_WIDTH, KEK_FONT_BITMAP_HEIGHT, 1, textureBytes, KEK_FONT_BITMAP_WIDTH);
glGenTextures(1, &chBlock.textureID);
glBindTexture(GL_TEXTURE_2D, chBlock.textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, KEK_FONT_BITMAP_WIDTH, KEK_FONT_BITMAP_HEIGHT, 0, GL_RED, GL_UNSIGNED_BYTE, textureBytes);
glGenerateMipmap(GL_TEXTURE_2D);
delete[] textureBytes;
characterBlocks[block] = chBlock;
return chBlock;
}
TextObject *Font::createText(std::string text) {
return new TextObject(this, text);
}
FontMetrics Font::getDefaultMetrics() {
return FontMetrics(lineHeight >> 6, ascender >> 6, descender >> 6);
}
FontMetrics Font::getMetrics(int sizePixels) {
float sizeRatio = (float) sizePixels / KEK_FONT_RESOLUTION;
return FontMetrics(((int) (sizeRatio * lineHeight)) >> 6, ((int) (sizeRatio * ascender)) >> 6, ((int) (sizeRatio * descender)) >> 6);
}
void Font::drawText(TextObject *textObject, int x, int y, int sizePixels, Color color) {
glm::mat4 projection = glm::ortho(0.0f, (float) kekData.screenWidth, (float) kekData.screenHeight, 0.0f);
Font::drawText(textObject, projection, x, y, sizePixels, color);
}
void Font::drawText(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color) {
fontShader->use();
for(auto b : textObject->blocks) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, getCharacterBlock(b.second.characterBlock).textureID);
glUniform4fv(glGetUniformLocation(fontShader->id, "textColor"), 1, color.valuePointer());
glUniform1i(glGetUniformLocation(fontShader->id, "charTexture"), 0);
glBindVertexArray(b.second.vao);
glUniformMatrix4fv(glGetUniformLocation(fontShader->id, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniform2fv(glGetUniformLocation(fontShader->id, "offset"), 1, glm::value_ptr(glm::vec2(x, y)));
glUniform1f(glGetUniformLocation(fontShader->id, "scale"), (float) sizePixels / KEK_FONT_RESOLUTION);
glDrawArrays(GL_TRIANGLES, 0, 6 * b.second.length);
}
}
void Font::drawTextFromOrigin(TextObject *textObject, int x, int y, int sizePixels, Color color) {
TextMetrics metrics = textObject->getMetrics(sizePixels);
Font::drawText(textObject, x - metrics.offsetX, y + metrics.offsetY, sizePixels, color);
}
void Font::drawTextFromOrigin(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color) {
TextMetrics metrics = textObject->getMetrics(sizePixels);
Font::drawText(textObject, projection, x - metrics.offsetX, y + metrics.offsetY, sizePixels, color);
}
void Font::drawTextCentered(TextObject *textObject, int x, int y, int sizePixels, Color color) {
TextMetrics metrics = textObject->getMetrics(sizePixels);
Font::drawText(textObject, x - metrics.offsetX + metrics.width / 2, y + metrics.offsetY - metrics.height / 2, sizePixels, color);
}
void Font::drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color) {
TextMetrics metrics = textObject->getMetrics(sizePixels);
Font::drawText(textObject, projection, x - metrics.offsetX + metrics.width / 2, y + metrics.offsetY - metrics.height / 2, sizePixels, color);
}
}

View File

@ -38,4 +38,34 @@ void removeMouseListener(InputListener listener) {
kekData.mouseCallbacks.erase(listener); kekData.mouseCallbacks.erase(listener);
} }
KeyMapping createKeyMapping(std::string name, GLFWKey defaultKey) {
if(name == KEK_INVALID_KEY_MAPPING_NAME) return KEK_INVALID_KEY_MAPPING;
KeyMapping id = nextID++;
KeyMappingData d;
d.name = name;
d.key = defaultKey;
kekData.keyMappings.emplace(id, d);
return id;
}
void reassignKeyMapping(KeyMapping mapping, GLFWKey key) {
auto it = kekData.keyMappings.find(mapping);
if(it == kekData.keyMappings.end()) return;
KeyMappingData d = it->second;
d.key = key;
it->second = d;
}
KeyMappingData getKeyMapping(KeyMapping mapping) {
auto it = kekData.keyMappings.find(mapping);
if(it == kekData.keyMappings.end()) return {.name = KEK_INVALID_KEY_MAPPING_NAME};
return it->second;
}
GLFWKeyState getKeyState(KeyMapping mapping) {
auto it = kekData.keyMappings.find(mapping);
if(it == kekData.keyMappings.end()) return -1;
return glfwGetKey(kekData.window, it->second.key);
}
} }

View File

@ -0,0 +1,38 @@
#pragma once
namespace kek {
struct Color {
float r, g, b, a;
constexpr Color(float r, float g, float b, float a): r(r), g(g), b(b), a(a) {}
constexpr Color(float r, float g, float b): Color(r, g, b, 1.0) {}
constexpr Color(): Color(0,0,0,1) {}
float *valuePointer() {
return &r;
}
};
class Colors {
public:
static constexpr Color RED = Color(1.0, 0.0, 0.0);
static constexpr Color ORANGE = Color(1.0, 0.5, 0.0);
static constexpr Color YELLOW = Color(1.0, 1.0, 0.0);
static constexpr Color GREEN = Color(0.0, 1.0, 0.0);
static constexpr Color CYAN = Color(0.0, 1.0, 1.0);
static constexpr Color BLUE = Color(0.0, 0.0, 1.0);
static constexpr Color PURPLE = Color(0.5, 0.0, 0.5);
static constexpr Color MAGENTA = Color(1.0, 0.0, 1.0);
static constexpr Color GRAY = Color(0.5, 0.5, 0.5);
static constexpr Color WHITE = Color(1.0, 1.0, 1.0);
static constexpr Color BLACK = Color(0.0, 0.0, 0.0);
Colors() = delete;
};
}

View File

@ -23,3 +23,15 @@
#define KEK_LIGHT_MAX_DISTANCE 30 #define KEK_LIGHT_MAX_DISTANCE 30
#define KEK_LIGHT_MAX_DISTANCE_SQUARED (KEK_LIGHT_MAX_DISTANCE * KEK_LIGHT_MAX_DISTANCE) #define KEK_LIGHT_MAX_DISTANCE_SQUARED (KEK_LIGHT_MAX_DISTANCE * KEK_LIGHT_MAX_DISTANCE)
#define KEK_INVALID_KEY_MAPPING_NAME "INVALID"
#define KEK_INVALID_KEY_MAPPING -1
#define KEK_FONT_RESOLUTION 64
#define KEK_FONT_BITMAP_WIDTH_BLOCKS 16
#define KEK_FONT_BITMAP_HEIGHT_BLOCKS 16
#define KEK_FONT_BITMAP_WIDTH (KEK_FONT_BITMAP_WIDTH_BLOCKS * KEK_FONT_RESOLUTION)
#define KEK_FONT_BITMAP_HEIGHT (KEK_FONT_BITMAP_HEIGHT_BLOCKS * KEK_FONT_RESOLUTION)
#define KEK_FONT_BITMAP_CHAR_BITS 8 // = log2(KEK_FONT_BITMAP_WIDTH_BLOCKS * KEK_FONT_BITMAP_HEIGHT_BLOCKS)
#define KEK_FONT_BITMAP_CHAR_MASK 0xFF // = KEK_FONT_BITMAP_CHAR_BITS 1s in binary
#define KEK_TEXT_BLOCK_SIZE 8

View File

@ -0,0 +1,7 @@
#pragma once
namespace kek::Defaults {
void init();
}

View File

@ -0,0 +1,159 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include <glm/glm.hpp>
#include <string>
#include <map>
#include "color.h"
#include "resource.h"
namespace kek {
struct Character {
unsigned int textureX;
unsigned int textureY;
glm::ivec2 size;
glm::ivec2 bearing;
unsigned int advance;
};
struct CharacterBlock {
unsigned int textureID;
std::map<unsigned int, Character> characters;
};
struct TextMetrics {
int offsetX;
int offsetY;
int width;
int height;
TextMetrics(int offsetX, int offsetY, unsigned int width, unsigned int height) {
this->offsetX = offsetX;
this->offsetY = offsetY;
this->width = width;
this->height = height;
}
};
struct FontMetrics {
int lineHeight;
int ascender;
int descender;
FontMetrics(int lineHeight, int ascender, int descender) {
this->lineHeight = lineHeight;
this->ascender = ascender;
this->descender = descender;
}
};
class Font;
struct TextBlock {
unsigned int characterBlock;
unsigned int length;
unsigned int bufferSize;
unsigned int vao;
unsigned int vbo;
};
class TextObject {
private:
Font *font;
std::string text;
int offsetX;
int offsetY;
int width;
int height;
public:
std::map<unsigned int, TextBlock> blocks;
TextObject(Font *font, std::string text);
~TextObject();
TextObject(const TextObject &) = delete;
TextObject &operator=(const TextObject &) = delete;
TextObject(TextObject &&other);
TextObject &operator=(TextObject &&other);
Font *getFont();
void setText(std::string text);
std::string getText();
TextMetrics getMetrics(int sizePixels);
private:
void destroy();
void allocateBuffer(TextBlock *block, int numChars);
void loadChars();
};
class Font {
private:
int lineHeight;
int ascender;
int descender;
MemoryBuffer *fileMemory;
FT_Face face;
std::map<unsigned int, CharacterBlock> characterBlocks;
public:
Font(std::string path);
~Font();
CharacterBlock getCharacterBlock(unsigned int block);
TextObject *createText(std::string text);
FontMetrics getDefaultMetrics();
FontMetrics getMetrics(int sizePixels);
// Draws text normally.
// y specifies the baseline of the drawn text
void drawText(TextObject *textObject, int x, int y, int sizePixels, Color color);
// Draws text normally.
// y specifies the baseline of the drawn text
void drawText(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);
// Draws text, with (x,y) being the top-left corner of the text drawn
void drawTextFromOrigin(TextObject *textObject, int x, int y, int sizePixels, Color color);
// Draws text, with (x,y) being the top-left corner of the text drawn
void drawTextFromOrigin(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);
// Draws text centered around (x,y)
void drawTextCentered(TextObject *textObject, int x, int y, int sizePixels, Color color);
// Draws text centered around (x,y)
void drawTextCentered(TextObject *textObject, glm::mat4 projection, int x, int y, int sizePixels, Color color);
private:
CharacterBlock generateCharacterBlock(unsigned int block);
};
}

View File

@ -12,6 +12,14 @@ typedef generic_callable_t<GLFWwindow *, int, int, int, int> KeyCallback; // key
typedef generic_callable_t<GLFWwindow *, double, double> MouseCallback; // mouseCallback(GLFWwindow *window, double x, double y) typedef generic_callable_t<GLFWwindow *, double, double> MouseCallback; // mouseCallback(GLFWwindow *window, double x, double y)
typedef unsigned int InputListener; typedef unsigned int InputListener;
typedef unsigned int GLFWKey;
typedef unsigned int GLFWKeyState;
typedef unsigned int KeyMapping;
struct KeyMappingData {
std::string name;
GLFWKey key;
};
} }
@ -29,4 +37,12 @@ InputListener addMouseListener(MouseCallback callback);
void removeMouseListener(InputListener listener); void removeMouseListener(InputListener listener);
KeyMapping createKeyMapping(std::string name, GLFWKey defaultKey);
void reassignKeyMapping(KeyMapping mapping, GLFWKey key);
KeyMappingData getKeyMapping(KeyMapping mapping);
GLFWKeyState getKeyState(KeyMapping mapping);
} }

View File

@ -8,6 +8,7 @@
#include "camera.h" #include "camera.h"
#include "scene.h" #include "scene.h"
#include "texture.h" #include "texture.h"
#include "fonts.h"
namespace kek { namespace kek {
@ -15,6 +16,7 @@ struct KekData {
std::map<InputListener, PeriodicCallback> periodicCallbacks; std::map<InputListener, PeriodicCallback> periodicCallbacks;
std::map<InputListener, KeyCallback> keyCallbacks; std::map<InputListener, KeyCallback> keyCallbacks;
std::map<InputListener, MouseCallback> mouseCallbacks; std::map<InputListener, MouseCallback> mouseCallbacks;
std::map<KeyMapping, KeyMappingData> keyMappings;
GLFWwindow *window; GLFWwindow *window;
Shader *shader; Shader *shader;
@ -28,6 +30,8 @@ struct KekData {
float lastFrameTime; float lastFrameTime;
std::map<std::string, std::weak_ptr<Texture>> loadedTextures; std::map<std::string, std::weak_ptr<Texture>> loadedTextures;
FT_Library freetype;
}; };
extern KekData kekData; extern KekData kekData;

View File

@ -16,6 +16,8 @@ struct generic_callable_t {
generic_function_t<Args..., void *> function; generic_function_t<Args..., void *> function;
void *data; void *data;
generic_callable_t(generic_function_t<Args..., void *> function, void *data): function(function), data(data) {}
void operator()(Args... args) { void operator()(Args... args) {
function(args..., data); function(args..., data);
} }

Binary file not shown.

View File

@ -0,0 +1,10 @@
#version 330 core
in vec2 textureCoordinate;
out vec4 color;
uniform sampler2D charTexture;
uniform vec4 textColor;
void main() {
color = vec4(textColor.xyz, textColor.w * texture(charTexture, textureCoordinate).r);
}

View File

@ -0,0 +1,13 @@
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoord;
out vec2 textureCoordinate;
uniform mat4 projection;
uniform vec2 offset;
uniform float scale;
void main() {
gl_Position = projection * vec4(position * scale + offset, 0.0, 1.0);
textureCoordinate = texCoord;
}