Key mappings, basic text rendering
This commit is contained in:
parent
e71ffe3f2a
commit
ff1ad05ba4
@ -59,8 +59,13 @@ add_library(kekengine OBJECT ${KEKENGINE_SOURCE_FILES})
|
||||
set_property(TARGET kekengine PROPERTY POSITION_INDEPENDENT_CODE 1)
|
||||
add_dependencies(kekengine kekengine_res)
|
||||
|
||||
set_property(TARGET microtar PROPERTY POSITION_INDEPENDENT_CODE 1)
|
||||
target_link_libraries(kekengine PUBLIC microtar)
|
||||
target_link_libraries(kekengine PUBLIC microtar_static)
|
||||
|
||||
# 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)
|
||||
target_link_libraries(kekengine PUBLIC glfw GLEW GL)
|
||||
@ -118,7 +123,6 @@ if(${KEKENGINE_BUILD_KEKGAME})
|
||||
add_executable(kekgame ${KEKGAME_SOURCE_FILES})
|
||||
add_dependencies(kekgame kekgame_res)
|
||||
|
||||
|
||||
if(WIN32)
|
||||
target_link_options(kekgame PUBLIC -static-libgcc -static-libstdc++ -static)
|
||||
endif()
|
||||
|
2
dependencies/microtar
vendored
2
dependencies/microtar
vendored
@ -1 +1 @@
|
||||
Subproject commit b240d953b12dc87c625743a9fb55634b0f0f3608
|
||||
Subproject commit 66dbf3cf8623b522d6413c6e994ec45f66288bd5
|
53
src/kekengine/cpp/defaults.cpp
Normal file
53
src/kekengine/cpp/defaults.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
@ -21,11 +21,14 @@
|
||||
#include "constants.h"
|
||||
#include "gameobject.h"
|
||||
#include "scene.h"
|
||||
#include "defaults.h"
|
||||
|
||||
kek::KekData kek::kekData;
|
||||
|
||||
namespace kek::Engine {
|
||||
|
||||
static TextObject *fpsText;
|
||||
|
||||
static void framebufferSizeCallback(GLFWwindow *window, int w, int h) {
|
||||
glViewport(0, 0, w, h);
|
||||
kekData.screenWidth = w;
|
||||
@ -160,10 +163,21 @@ int init() {
|
||||
kekData.shader = new Shader("shader/mesh/vertex.glsl", "shader/mesh/fragment.glsl");
|
||||
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;
|
||||
}
|
||||
|
||||
int start() {
|
||||
int prevTime = 0;
|
||||
while(!glfwWindowShouldClose(kekData.window)) {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@ -175,32 +189,6 @@ int start() {
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
@ -243,39 +231,6 @@ int start() {
|
||||
int numPointLights = 0, numDirectionalLights = 0, numSpotLights = 0;
|
||||
|
||||
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;
|
||||
//shLight.color = light->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);
|
||||
|
||||
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
|
||||
glfwSwapBuffers(kekData.window);
|
||||
glfwPollEvents();
|
||||
@ -328,8 +295,8 @@ int start() {
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<float> secsTaken = end - start;
|
||||
kekData.lastFrameTime = secsTaken.count();
|
||||
std::cout << "FT: " << kekData.lastFrameTime << std::endl;
|
||||
std::cout << "FR: " << (1.0f / kekData.lastFrameTime) << std::endl;
|
||||
//std::cout << "FT: " << kekData.lastFrameTime << '\n';
|
||||
//std::cout << "FR: " << (1.0f / kekData.lastFrameTime) << '\n';
|
||||
}
|
||||
|
||||
return KEK_SUCCESS;
|
||||
|
344
src/kekengine/cpp/fonts.cpp
Normal file
344
src/kekengine/cpp/fonts.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -38,4 +38,34 @@ void removeMouseListener(InputListener 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
38
src/kekengine/include/color.h
Normal file
38
src/kekengine/include/color.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -23,3 +23,15 @@
|
||||
|
||||
#define KEK_LIGHT_MAX_DISTANCE 30
|
||||
#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
|
||||
|
7
src/kekengine/include/defaults.h
Normal file
7
src/kekengine/include/defaults.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace kek::Defaults {
|
||||
|
||||
void init();
|
||||
|
||||
}
|
159
src/kekengine/include/fonts.h
Normal file
159
src/kekengine/include/fonts.h
Normal 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);
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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 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);
|
||||
|
||||
KeyMapping createKeyMapping(std::string name, GLFWKey defaultKey);
|
||||
|
||||
void reassignKeyMapping(KeyMapping mapping, GLFWKey key);
|
||||
|
||||
KeyMappingData getKeyMapping(KeyMapping mapping);
|
||||
|
||||
GLFWKeyState getKeyState(KeyMapping mapping);
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "camera.h"
|
||||
#include "scene.h"
|
||||
#include "texture.h"
|
||||
#include "fonts.h"
|
||||
|
||||
namespace kek {
|
||||
|
||||
@ -15,6 +16,7 @@ struct KekData {
|
||||
std::map<InputListener, PeriodicCallback> periodicCallbacks;
|
||||
std::map<InputListener, KeyCallback> keyCallbacks;
|
||||
std::map<InputListener, MouseCallback> mouseCallbacks;
|
||||
std::map<KeyMapping, KeyMappingData> keyMappings;
|
||||
|
||||
GLFWwindow *window;
|
||||
Shader *shader;
|
||||
@ -28,6 +30,8 @@ struct KekData {
|
||||
float lastFrameTime;
|
||||
|
||||
std::map<std::string, std::weak_ptr<Texture>> loadedTextures;
|
||||
|
||||
FT_Library freetype;
|
||||
};
|
||||
|
||||
extern KekData kekData;
|
||||
|
@ -16,6 +16,8 @@ struct generic_callable_t {
|
||||
generic_function_t<Args..., void *> function;
|
||||
void *data;
|
||||
|
||||
generic_callable_t(generic_function_t<Args..., void *> function, void *data): function(function), data(data) {}
|
||||
|
||||
void operator()(Args... args) {
|
||||
function(args..., data);
|
||||
}
|
||||
|
BIN
src/kekengine/res/font/MaredivRegular-yeg3.ttf
Normal file
BIN
src/kekengine/res/font/MaredivRegular-yeg3.ttf
Normal file
Binary file not shown.
10
src/kekengine/res/shader/text/fragment.glsl
Normal file
10
src/kekengine/res/shader/text/fragment.glsl
Normal 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);
|
||||
}
|
13
src/kekengine/res/shader/text/vertex.glsl
Normal file
13
src/kekengine/res/shader/text/vertex.glsl
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user