diff --git a/CMakeLists.txt b/CMakeLists.txt index 2075315..9eff55b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,7 +148,7 @@ else() endif() # stb -target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE dependencies/stb) +target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC dependencies/stb) # assimp set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "" FORCE) diff --git a/include/resources/font.h b/include/resources/font.h index e2320d1..82bf736 100644 --- a/include/resources/font.h +++ b/include/resources/font.h @@ -2,19 +2,33 @@ #define ENGINE_INCLUDE_RESOURCES_FONT_H_ #include +#include +#include +#include + +#include namespace engine { namespace resources { class Font { public: - Font(const std::string& path); ~Font(); Font(const Font&) = delete; Font& operator=(const Font&) = delete; + std::unique_ptr> GetTextBitmap(const std::string& text, + float height_px, + int& width_out, + int& height_out); + private: + std::unique_ptr font_info_{}; + std::unique_ptr> font_buffer_{}; + std::map unicode_to_glyph_{}; + + int GetGlyphIndex(int unicode_codepoint); }; } // namespace resources diff --git a/include/resources/texture.h b/include/resources/texture.h index 20c7d2d..cb0f74d 100644 --- a/include/resources/texture.h +++ b/include/resources/texture.h @@ -20,6 +20,9 @@ class Texture { Texture(RenderData* render_data, const std::string& path, Filtering filtering); + Texture(RenderData* render_data, const uint8_t* bitmap, int width, int height, + Filtering filtering); + ~Texture(); Texture(const Texture&) = delete; Texture& operator=(const Texture&) = delete; diff --git a/src/resources/font.cpp b/src/resources/font.cpp index c612299..0b8a360 100644 --- a/src/resources/font.cpp +++ b/src/resources/font.cpp @@ -1,6 +1,7 @@ #include "resources/font.h" #include +#include #include @@ -10,17 +11,117 @@ namespace engine::resources { Font::Font(const std::string& path) { - // auto font_buffer = util::ReadBinaryFile(path); + font_buffer_ = util::ReadBinaryFile(path); + font_info_ = std::make_unique(); - // constexpr int FIRST_CHAR = 32; - // constexpr int NUM_CHARS = 96; - // constexpr float SCALE = 128.0f; - - // TODO finish this + if (stbtt_InitFont(font_info_.get(), font_buffer_->data(), 0) == 0) { + throw std::runtime_error("Failed to initialise font!"); + } LOG_INFO("Loaded font: {}", path); } Font::~Font() {} +std::unique_ptr> Font::GetTextBitmap( + const std::string& text, float height_px, int& width_out, int& height_out) { + const float sf = stbtt_ScaleForPixelHeight(font_info_.get(), height_px); + + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(font_info_.get(), &ascent, &descent, &line_gap); + + struct CharacterRenderInfo { + int advance; // bitmap advance + bool isEmpty; + int xoff, yoff, width, height; + unsigned char* bitmap; + }; + + std::vector characterRenderInfos{}; + + int width = 0; + for (size_t i = 0; i < text.size(); i++) { + const int glyph_index = GetGlyphIndex(static_cast(text.at(i))); + + int advanceWidth, leftSideBearing; + stbtt_GetGlyphHMetrics(font_info_.get(), glyph_index, &advanceWidth, + &leftSideBearing); + + if (i == 0 && leftSideBearing < 0) { + // if the character extends before the current point + width -= leftSideBearing; + } + width += advanceWidth; + + CharacterRenderInfo renderInfo{}; + + renderInfo.advance = advanceWidth * sf; + + if (stbtt_IsGlyphEmpty(font_info_.get(), glyph_index) == 0) { + renderInfo.isEmpty = false; + } else { + renderInfo.isEmpty = true; + } + + if (!renderInfo.isEmpty) { + renderInfo.bitmap = stbtt_GetGlyphBitmap( + font_info_.get(), sf, sf, glyph_index, &renderInfo.width, + &renderInfo.height, &renderInfo.xoff, &renderInfo.yoff); + } + + characterRenderInfos.push_back(renderInfo); + } + + const size_t bitmap_width = std::ceil(static_cast(width) * sf); + const size_t bitmap_height = + std::ceil(static_cast(ascent - descent) * sf); + auto bitmap = + std::make_unique>(bitmap_width * bitmap_height * 4); + + int top_left_x = 0; + for (const auto& renderInfo : characterRenderInfos) { + if (renderInfo.isEmpty == false) { + top_left_x += renderInfo.xoff; + const int top_left_y = static_cast(ascent * sf) + renderInfo.yoff; + const int char_bitmap_width = renderInfo.width; + const int char_bitmap_height = renderInfo.height; + + // copy the 8bpp char bitmap to the 4bpp output + for (int y = 0; y < char_bitmap_height; y++) { + for (int x = 0; x < char_bitmap_width; x++) { + const int out_index = + ((bitmap_width * (top_left_y + y)) + (top_left_x + x)) * 4; + const uint8_t pixel = renderInfo.bitmap[y * char_bitmap_width + x]; + (*bitmap)[out_index + 0] = pixel; + (*bitmap)[out_index + 1] = pixel; + (*bitmap)[out_index + 2] = pixel; + (*bitmap)[out_index + 3] = 0xFF; + } + } + + stbtt_FreeBitmap(renderInfo.bitmap, nullptr); + } + + top_left_x += renderInfo.advance - renderInfo.xoff; + } + + width_out = bitmap_width; + height_out = bitmap_height; + return bitmap; +} + +int Font::GetGlyphIndex(int unicode_codepoint) { + if (unicode_to_glyph_.contains(unicode_codepoint)) { + return unicode_to_glyph_.at(unicode_codepoint); + } else { + const int glyph_index = + stbtt_FindGlyphIndex(font_info_.get(), unicode_codepoint); + if (glyph_index == 0) { + throw std::runtime_error("Glyph not found in font!"); + } + unicode_to_glyph_.emplace(std::make_pair(unicode_codepoint, glyph_index)); + return glyph_index; + } +} + } // namespace engine::resources diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index 659d463..75b75e4 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -8,54 +8,96 @@ namespace engine::resources { -Texture::Texture(RenderData* renderData, const std::string& path, Filtering filtering) - : gfx_(renderData->gfxdev.get()) -{ +Texture::Texture(RenderData* renderData, const std::string& path, + Filtering filtering) + : gfx_(renderData->gfxdev.get()) { + int width, height; + std::unique_ptr> texbuf = + util::ReadImageFile(path, &width, &height); - int width, height; - std::unique_ptr> texbuf = util::ReadImageFile(path, &width, &height); + gfx::SamplerInfo samplerInfo{}; - gfx::SamplerInfo samplerInfo{}; + samplerInfo.magnify = gfx::Filter::kLinear; - samplerInfo.magnify = gfx::Filter::kLinear; + switch (filtering) { + case Filtering::kOff: + samplerInfo.minify = gfx::Filter::kNearest; + samplerInfo.mipmap = gfx::Filter::kNearest; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kBilinear: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kNearest; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kTrilinear: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kLinear; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kAnisotropic: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kLinear; + samplerInfo.anisotropic_filtering = true; + } - switch (filtering) { - case Filtering::kOff: - samplerInfo.minify = gfx::Filter::kNearest; - samplerInfo.mipmap = gfx::Filter::kNearest; - samplerInfo.anisotropic_filtering = false; - break; - case Filtering::kBilinear: - samplerInfo.minify = gfx::Filter::kLinear; - samplerInfo.mipmap = gfx::Filter::kNearest; - samplerInfo.anisotropic_filtering = false; - break; - case Filtering::kTrilinear: - samplerInfo.minify = gfx::Filter::kLinear; - samplerInfo.mipmap = gfx::Filter::kLinear; - samplerInfo.anisotropic_filtering = false; - break; - case Filtering::kAnisotropic: - samplerInfo.minify = gfx::Filter::kLinear; - samplerInfo.mipmap = gfx::Filter::kLinear; - samplerInfo.anisotropic_filtering = true; - } + if (renderData->samplers.contains(samplerInfo) == false) { + renderData->samplers.insert( + std::make_pair(samplerInfo, gfx_->CreateSampler(samplerInfo))); + } - if (renderData->samplers.contains(samplerInfo) == false) { - renderData->samplers.insert(std::make_pair(samplerInfo, gfx_->CreateSampler(samplerInfo))); - } - - image_ = gfx_->CreateImage(width, height, texbuf->data()); - descriptor_set_ = gfx_->AllocateDescriptorSet(renderData->material_set_layout); - gfx_->UpdateDescriptorCombinedImageSampler(descriptor_set_, 0, image_, renderData->samplers.at(samplerInfo)); - - LOG_INFO("Loaded texture: {}, width: {} height: {}", path, width, height); + image_ = gfx_->CreateImage(width, height, texbuf->data()); + descriptor_set_ = + gfx_->AllocateDescriptorSet(renderData->material_set_layout); + gfx_->UpdateDescriptorCombinedImageSampler( + descriptor_set_, 0, image_, renderData->samplers.at(samplerInfo)); + LOG_INFO("Loaded texture: {}, width: {} height: {}", path, width, height); } -Texture::~Texture() -{ - gfx_->DestroyImage(image_); +Texture::Texture(RenderData* render_data, const uint8_t* bitmap, int width, + int height, Filtering filtering) + : gfx_(render_data->gfxdev.get()) { + gfx::SamplerInfo samplerInfo{}; + + samplerInfo.magnify = gfx::Filter::kLinear; + + switch (filtering) { + case Filtering::kOff: + samplerInfo.minify = gfx::Filter::kNearest; + samplerInfo.mipmap = gfx::Filter::kNearest; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kBilinear: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kNearest; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kTrilinear: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kLinear; + samplerInfo.anisotropic_filtering = false; + break; + case Filtering::kAnisotropic: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kLinear; + samplerInfo.anisotropic_filtering = true; + } + + if (render_data->samplers.contains(samplerInfo) == false) { + render_data->samplers.insert( + std::make_pair(samplerInfo, gfx_->CreateSampler(samplerInfo))); + } + + image_ = gfx_->CreateImage(width, height, bitmap); + descriptor_set_ = + gfx_->AllocateDescriptorSet(render_data->material_set_layout); + gfx_->UpdateDescriptorCombinedImageSampler( + descriptor_set_, 0, image_, render_data->samplers.at(samplerInfo)); + + LOG_INFO("Loaded texture: BITMAP, width: {} height: {}", width, height); } -} +Texture::~Texture() { gfx_->DestroyImage(image_); } + +} // namespace engine::resources diff --git a/test/src/game.cpp b/test/src/game.cpp index 325d7db..9bb812b 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -7,6 +7,7 @@ #include "components/transform.h" #include "input_manager.h" #include "meshgen.hpp" +#include "resources/font.h" #include "resources/material.h" #include "resources/texture.h" #include "scene.h" @@ -86,6 +87,7 @@ void PlayGame(GameSettings settings) { auto grass_texture = std::make_shared( &app.render_data_, app.GetResourcePath("textures/grass.jpg"), engine::resources::Texture::Filtering::kAnisotropic); + auto space_texture = std::make_shared( &app.render_data_, app.GetResourcePath("textures/space2.png"), engine::resources::Texture::Filtering::kAnisotropic); @@ -99,8 +101,8 @@ void PlayGame(GameSettings settings) { my_scene->AddComponent(cube); cube_renderable->material = std::make_shared( app.GetResource("builtin.standard")); - cube_renderable->material->texture_ = - app.GetResource("builtin.white"); + cube_renderable->material->texture_ = grass_texture; +// app.GetResource("builtin.white"); cube_renderable->mesh = GenCuboidMesh(app.gfxdev(), 1.0f, 1.0f, 1.0f, 1); auto cube_collider = my_scene->AddComponent(cube); @@ -127,8 +129,8 @@ void PlayGame(GameSettings settings) { floor_collider->aabb = {{0.0f, 0.0f, 0.0f}, {10000.0f, 1.0f, 10000.0f}}; } - engine::util::LoadMeshFromFile( - my_scene, app.GetResourcePath("models/astronaut/astronaut.dae")); + //engine::util::LoadMeshFromFile( + // my_scene, app.GetResourcePath("models/astronaut/astronaut.dae")); /* skybox */ {