From d9f2d54bf0b891ee01678191b2a7a3aa2428c8a8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 00:20:58 +0300 Subject: [PATCH 1/7] feat: canvas from texture atlas element --- src/graphics/core/Atlas.cpp | 8 +++ src/graphics/core/Atlas.hpp | 7 ++- src/graphics/core/ImageData.cpp | 11 ++++ src/graphics/core/ImageData.hpp | 2 + src/graphics/core/Texture.cpp | 6 +++ src/graphics/core/Texture.hpp | 1 + src/logic/scripting/lua/libs/libassets.cpp | 31 +++++++++++ src/logic/scripting/lua/lua_custom_types.hpp | 25 +++++---- .../lua/usertypes/lua_type_canvas.cpp | 54 ++++++++++++++----- src/maths/UVRegion.hpp | 9 +++- 10 files changed, 126 insertions(+), 28 deletions(-) diff --git a/src/graphics/core/Atlas.cpp b/src/graphics/core/Atlas.cpp index be205d1e..d06a20b9 100644 --- a/src/graphics/core/Atlas.cpp +++ b/src/graphics/core/Atlas.cpp @@ -49,6 +49,14 @@ ImageData* Atlas::getImage() const { return image.get(); } +std::shared_ptr Atlas::shareTexture() const { + return texture; +} + +std::shared_ptr Atlas::shareImageData() const { + return image; +} + void AtlasBuilder::add(const std::string& name, std::unique_ptr image) { entries.push_back(atlasentry{name, std::shared_ptr(image.release())}); names.insert(name); diff --git a/src/graphics/core/Atlas.hpp b/src/graphics/core/Atlas.hpp index af3e4721..adea16a4 100644 --- a/src/graphics/core/Atlas.hpp +++ b/src/graphics/core/Atlas.hpp @@ -14,8 +14,8 @@ class ImageData; class Texture; class Atlas { - std::unique_ptr texture; - std::unique_ptr image; + std::shared_ptr texture; + std::shared_ptr image; std::unordered_map regions; public: /// @param image atlas raster @@ -36,6 +36,9 @@ public: Texture* getTexture() const; ImageData* getImage() const; + + std::shared_ptr shareTexture() const; + std::shared_ptr shareImageData() const; }; struct atlasentry { diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index 44a1936e..3a5e3cb6 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -95,6 +95,17 @@ void ImageData::blit(const ImageData& image, int x, int y) { throw std::runtime_error("mismatching format"); } +std::unique_ptr ImageData::cropped(int x, int y, int width, int height) const { + width = std::min(width, this->width - x); + height = std::min(height, this->height - y); + if (width <= 0 || height <= 0) { + throw std::runtime_error("invalid crop dimensions"); + } + auto subImage = std::make_unique(format, width, height); + subImage->blitMatchingFormat(*this, -x, -y); + return subImage; +} + static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { const int left = 0; const int right = width; diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index d09a0411..86396568 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -33,6 +33,8 @@ public: void extrude(int x, int y, int w, int h); void fixAlphaColor(); + std::unique_ptr cropped(int x, int y, int width, int height) const; + ubyte* getData() const { return data.get(); } diff --git a/src/graphics/core/Texture.cpp b/src/graphics/core/Texture.cpp index 1dc18a65..46f153e8 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -54,6 +54,12 @@ void Texture::reload(const ubyte* data) { glBindTexture(GL_TEXTURE_2D, 0); } +void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { + glBindTexture(GL_TEXTURE_2D, id); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); + glBindTexture(GL_TEXTURE_2D, 0); +} + std::unique_ptr Texture::readData() { auto data = std::make_unique(width * height * 4); glBindTexture(GL_TEXTURE_2D, id); diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index 59568619..5e67890c 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -19,6 +19,7 @@ public: virtual void bind() const; virtual void unbind() const; void reload(const ubyte* data); + void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h); void setNearestFilter(); diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 9924069a..6018a12c 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -7,7 +7,9 @@ #include "engine/Engine.hpp" #include "graphics/commons/Model.hpp" #include "graphics/core/Texture.hpp" +#include "graphics/core/Atlas.hpp" #include "util/Buffer.hpp" +#include "../lua_custom_types.hpp" using namespace scripting; @@ -64,8 +66,37 @@ static int l_parse_model(lua::State* L) { return 0; } +static int l_to_canvas(lua::State* L) { + auto alias = lua::require_lstring(L, 1); + size_t sep = alias.rfind(':'); + if (sep == std::string::npos) { + return 0; + } + auto atlasName = alias.substr(0, sep); + auto& assets = *engine->getAssets(); + if (auto atlas = assets.get(std::string(atlasName))) { + auto textureName = std::string(alias.substr(sep + 1)); + auto image = atlas->shareImageData(); + auto texture = atlas->shareTexture(); + if (auto region = atlas->getIf(textureName)) { + UVRegion uvRegion = *region; + int atlasWidth = static_cast(image->getWidth()); + int atlasHeight = static_cast(image->getHeight()); + int x = static_cast(uvRegion.u1 * atlasWidth); + int y = static_cast(uvRegion.v1 * atlasHeight); + int w = static_cast(uvRegion.getWidth() * atlasWidth); + int h = static_cast(uvRegion.getHeight() * atlasHeight); + return lua::newuserdata( + L, std::move(texture), image->cropped(x, y, w, h), uvRegion + ); + } + } + return 0; +} + const luaL_Reg assetslib[] = { {"load_texture", lua::wrap}, {"parse_model", lua::wrap}, + {"to_canvas", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp index c17a78bc..bf2c4f87 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -6,6 +6,7 @@ #include #include "lua_commons.hpp" +#include "maths/UVRegion.hpp" struct fnl_state; class Heightmap; @@ -81,8 +82,9 @@ namespace lua { class LuaCanvas : public Userdata { public: explicit LuaCanvas( - std::shared_ptr inTexture, - std::shared_ptr inData + std::shared_ptr texture, + std::shared_ptr data, + UVRegion region = UVRegion(0, 0, 1, 1) ); ~LuaCanvas() override = default; @@ -90,29 +92,32 @@ namespace lua { return TYPENAME; } - [[nodiscard]] auto& texture() const { - return *mTexture; + [[nodiscard]] auto& getTexture() const { + return *texture; } - [[nodiscard]] auto& data() const { - return *mData; + [[nodiscard]] auto& getData() const { + return *data; } [[nodiscard]] bool hasTexture() const { - return mTexture != nullptr; + return texture != nullptr; } auto shareTexture() const { - return mTexture; + return texture; } + void update(); + void createTexture(); static int createMetatable(lua::State*); inline static std::string TYPENAME = "Canvas"; private: - std::shared_ptr mTexture; // nullable - std::shared_ptr mData; + std::shared_ptr texture; // nullable + std::shared_ptr data; + UVRegion region; }; static_assert(!std::is_abstract()); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index ec086904..fcb27779 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -10,14 +10,42 @@ using namespace lua; LuaCanvas::LuaCanvas( - std::shared_ptr inTexture, std::shared_ptr inData + std::shared_ptr texture, + std::shared_ptr data, + UVRegion region ) - : mTexture(std::move(inTexture)), mData(std::move(inData)) { + : texture(std::move(texture)), + data(std::move(data)), + region(std::move(region)) { +} + +void LuaCanvas::update() { + if (!hasTexture()) { + return; + } + if (region.isFull()) { + texture->reload(*data); + } else { + uint texWidth = texture->getWidth(); + uint texHeight = texture->getHeight(); + uint imgWidth = data->getWidth(); + uint imgHeight = data->getHeight(); + + uint x = static_cast(region.u1 * texWidth); + uint y = static_cast(region.v1 * texHeight); + uint w = static_cast((region.u2 - region.u1) * texWidth); + uint h = static_cast((region.v2 - region.v1) * texHeight); + + w = std::min(w, imgWidth); + h = std::min(h, imgHeight); + + texture->reloadPartial(*data, x, y, w, h); + } } void LuaCanvas::createTexture() { - mTexture = Texture::from(mData.get()); - mTexture->setMipMapping(false, true); + texture = Texture::from(data.get()); + texture->setMipMapping(false, true); } union RGBA { @@ -41,7 +69,7 @@ static RGBA* get_at(const ImageData& data, uint x, uint y) { static RGBA* get_at(State* L, uint x, uint y) { if (auto canvas = touserdata(L, 1)) { - return get_at(canvas->data(), x, y); + return get_at(canvas->getData(), x, y); } return nullptr; } @@ -97,7 +125,7 @@ static LuaCanvas& require_canvas(State* L, int idx) { static int l_clear(State* L) { auto& canvas = require_canvas(L, 1); - auto& image = canvas.data(); + auto& image = canvas.getData(); ubyte* data = image.getData(); RGBA rgba {}; if (gettop(L) == 1) { @@ -122,7 +150,7 @@ static int l_line(State* L) { RGBA rgba = get_rgba(L, 6); if (auto canvas = touserdata(L, 1)) { - auto& image = canvas->data(); + auto& image = canvas->getData(); image.drawLine( x1, y1, x2, y2, glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a} ); @@ -135,13 +163,13 @@ static int l_blit(State* L) { auto& src = require_canvas(L, 2); int dst_x = tointeger(L, 3); int dst_y = tointeger(L, 4); - dst.data().blit(src.data(), dst_x, dst_y); + dst.getData().blit(src.getData(), dst_x, dst_y); return 0; } static int l_set_data(State* L) { auto& canvas = require_canvas(L, 1); - auto& image = canvas.data(); + auto& image = canvas.getData(); auto data = image.getData(); if (lua::isstring(L, 2)) { @@ -166,9 +194,7 @@ static int l_set_data(State* L) { static int l_update(State* L) { if (auto canvas = touserdata(L, 1)) { - if (canvas->hasTexture()) { - canvas->texture().reload(canvas->data()); - } + canvas->update(); } return 0; } @@ -202,7 +228,7 @@ static int l_meta_index(State* L) { if (texture == nullptr) { return 0; } - auto& data = texture->data(); + auto& data = texture->getData(); if (isnumber(L, 2)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { return pushinteger(L, pixel->rgba); @@ -231,7 +257,7 @@ static int l_meta_newindex(State* L) { if (texture == nullptr) { return 0; } - auto& data = texture->data(); + auto& data = texture->getData(); if (isnumber(L, 2) && isnumber(L, 3)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { pixel->rgba = static_cast(tointeger(L, 3)); diff --git a/src/maths/UVRegion.hpp b/src/maths/UVRegion.hpp index 354c939b..f7e398d8 100644 --- a/src/maths/UVRegion.hpp +++ b/src/maths/UVRegion.hpp @@ -1,8 +1,7 @@ #pragma once #include -#include -#include +#include struct UVRegion { float u1; @@ -69,4 +68,10 @@ struct UVRegion { copy.scale(scale); return copy; } + + bool isFull() const { + const auto e = 1e-7; + return glm::abs(u1 - 0.0) < e && glm::abs(v1 - 0.0) < e && + glm::abs(u2 - 1.0) < e && glm::abs(v2 - 1.0) < e; + } }; From 643ae11d5c31e759609365df9da34457f960fbc1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 01:30:47 +0300 Subject: [PATCH 2/7] feat: rebuild mip-maps on texture reload --- src/graphics/core/Texture.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphics/core/Texture.cpp b/src/graphics/core/Texture.cpp index 46f153e8..04285aa8 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -51,12 +51,14 @@ void Texture::reload(const ubyte* data) { glBindTexture(GL_TEXTURE_2D, id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast(data)); + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { glBindTexture(GL_TEXTURE_2D, id); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } From 7b45b4083baa035694e1ed3912bed0326f2dd608 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 01:47:42 +0300 Subject: [PATCH 3/7] feat: keep previous values in debug panel if cursor is not locked --- src/frontend/debug_panel.cpp | 39 +++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 10c6b85c..c42b2930 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -9,6 +9,7 @@ #include "graphics/ui/elements/TextBox.hpp" #include "graphics/ui/elements/TrackBar.hpp" #include "graphics/ui/elements/InputBindBox.hpp" +#include "graphics/ui/GUI.hpp" #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" @@ -43,10 +44,15 @@ static std::shared_ptr