diff --git a/doc/en/scripting/builtins/libassets.md b/doc/en/scripting/builtins/libassets.md index bd095435..b39239a5 100644 --- a/doc/en/scripting/builtins/libassets.md +++ b/doc/en/scripting/builtins/libassets.md @@ -25,4 +25,12 @@ assets.parse_model( -- Model name after loading name: str ) + +-- Creates a Canvas from a loaded texture. +assets.to_canvas( + -- The name of the loaded texture. + -- Both standalone textures ("texture_name") and + -- those in an atlas ("atlas:texture_name") are supported + name: str +) --> Canvas ``` diff --git a/doc/ru/scripting/builtins/libassets.md b/doc/ru/scripting/builtins/libassets.md index 2c510948..ec069e1a 100644 --- a/doc/ru/scripting/builtins/libassets.md +++ b/doc/ru/scripting/builtins/libassets.md @@ -25,4 +25,12 @@ assets.parse_model( -- Имя модели после загрузки name: str ) + +-- Создаёт холст (Canvas) из загруженной текстуры +assets.to_canvas( + -- Имя загруженной текстуры. + -- Поддерживается как отдельные ("имя_текстуры"), + -- так и находящиеся в атласе ("атлас:имя_текстуры"). + name: str +) --> Canvas ``` diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index d865cc1a..4fda3b0d 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -184,7 +184,7 @@ assetload::postfunc assetload::atlas( if (!append_atlas(builder, file)) continue; } std::set names = builder.getNames(); - Atlas* atlas = builder.build(2, false).release(); + Atlas* atlas = builder.build(ATLAS_EXTRUSION, false).release(); return [=](auto assets) { atlas->prepare(); assets->store(std::unique_ptr(atlas), name); @@ -501,7 +501,7 @@ static bool load_animation( } if (!append_atlas(builder, file)) continue; } - auto srcAtlas = builder.build(2, true); + auto srcAtlas = builder.build(ATLAS_EXTRUSION, true); if (frameList.empty()) { for (const auto& frameName : builder.getNames()) { frameList.emplace_back(frameName, 0); diff --git a/src/constants.hpp b/src/constants.hpp index 6544316e..91e2c180 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -61,6 +61,8 @@ inline constexpr int ITEM_ICON_SIZE = 48; inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8; +inline constexpr int ATLAS_EXTRUSION = 2; + inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string TEXTURES_FOLDER = "textures"; inline const std::string FONTS_FOLDER = "fonts"; 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..04285aa8 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -51,6 +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); } 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/graphics/render/BlocksPreview.cpp b/src/graphics/render/BlocksPreview.cpp index 4b8bfee9..ad025d3c 100644 --- a/src/graphics/render/BlocksPreview.cpp +++ b/src/graphics/render/BlocksPreview.cpp @@ -137,5 +137,5 @@ std::unique_ptr BlocksPreview::build( builder.add(def.name, draw(cache, shader, fbo, batch, def, iconSize)); } fbo.unbind(); - return builder.build(2); + return builder.build(ATLAS_EXTRUSION); } diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 9924069a..76e824e2 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,47 @@ static int l_parse_model(lua::State* L) { return 0; } +static int l_to_canvas(lua::State* L) { + auto& assets = *engine->getAssets(); + + auto alias = lua::require_lstring(L, 1); + size_t sep = alias.rfind(':'); + if (sep == std::string::npos) { + auto texture = assets.getShared(std::string(alias)); + if (texture != nullptr) { + auto image = texture->readData(); + return lua::newuserdata( + L, texture, std::move(image) + ); + } + return 0; + } + auto atlasName = alias.substr(0, sep); + + 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..e1e9c3b6 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -6,6 +6,8 @@ #include #include "lua_commons.hpp" +#include "constants.hpp" +#include "maths/UVRegion.hpp" struct fnl_state; class Heightmap; @@ -81,8 +83,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 +93,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(int extrusion = ATLAS_EXTRUSION); + 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..8f5d3e00 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -10,14 +10,59 @@ 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(int extrusion) { + 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); + + if (extrusion > 0) { + auto extruded = std::make_unique( + data->getFormat(), + w + extrusion * 2, + h + extrusion * 2 + ); + extruded->blit(*data, extrusion, extrusion); + extruded->extrude(0, 0, w + extrusion * 2, h + extrusion * 2); + texture->reloadPartial( + *extruded, + x - extrusion, + y - extrusion, + w + extrusion * 2, + h + extrusion * 2 + ); + } else { + 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 +86,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 +142,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 +167,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 +180,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 +211,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 +245,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 +274,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; + } };