From 5fb34daf523bde66b6ad79ed54da101988c162a4 Mon Sep 17 00:00:00 2001 From: "@clasher113" Date: Sun, 21 Jan 2024 15:23:03 +0200 Subject: [PATCH] Block animation --- src/assets/Assets.cpp | 11 +++ src/assets/Assets.h | 7 ++ src/assets/assetload_funcs.cpp | 150 +++++++++++++++++++++++++----- src/assets/assetload_funcs.h | 6 ++ src/frontend/screens.cpp | 5 + src/frontend/screens.h | 4 +- src/graphics/Atlas.h | 1 + src/graphics/TextureAnimation.cpp | 52 +++++++++++ src/graphics/TextureAnimation.h | 52 +++++++++++ 9 files changed, 264 insertions(+), 24 deletions(-) create mode 100644 src/graphics/TextureAnimation.cpp create mode 100644 src/graphics/TextureAnimation.h diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 497f6a6b..c979ca01 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -54,6 +54,14 @@ void Assets::store(Atlas* atlas, std::string name){ atlases[name].reset(atlas); } +const std::vector& Assets::getAnimations() { + return animations; +} + +void Assets::store(const TextureAnimation& animation) { + animations.emplace_back(animation); +} + void Assets::extend(const Assets& assets) { for (auto entry : assets.textures) { textures[entry.first] = entry.second; @@ -67,4 +75,7 @@ void Assets::extend(const Assets& assets) { for (auto entry : assets.atlases) { atlases[entry.first] = entry.second; } + for (auto entry : assets.animations) { + animations.emplace_back(entry); + } } diff --git a/src/assets/Assets.h b/src/assets/Assets.h index a561682a..6df72ac4 100644 --- a/src/assets/Assets.h +++ b/src/assets/Assets.h @@ -1,9 +1,12 @@ #ifndef ASSETS_ASSETS_H_ #define ASSETS_ASSETS_H_ +#include "../graphics/TextureAnimation.h" + #include #include #include +#include class Texture; class Shader; @@ -15,6 +18,7 @@ class Assets { std::unordered_map> shaders; std::unordered_map> fonts; std::unordered_map> atlases; + std::vector animations; public: ~Assets(); Texture* getTexture(std::string name) const; @@ -29,6 +33,9 @@ public: Atlas* getAtlas(std::string name) const; void store(Atlas* atlas, std::string name); + const std::vector& getAnimations(); + void store(const TextureAnimation& animation); + void extend(const Assets& assets); }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 60131aff..824f1900 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -6,16 +6,17 @@ #include "../files/files.h" #include "../files/engine_paths.h" #include "../coders/png.h" +#include "../coders/json.h" #include "../graphics/Shader.h" #include "../graphics/Texture.h" #include "../graphics/ImageData.h" #include "../graphics/Atlas.h" #include "../graphics/Font.h" - +#include "../graphics/TextureAnimation.h" namespace fs = std::filesystem; -bool assetload::texture(Assets* assets, +bool assetload::texture(Assets* assets, const ResPaths* paths, const std::string filename, const std::string name) { @@ -28,21 +29,21 @@ bool assetload::texture(Assets* assets, return true; } -bool assetload::shader(Assets* assets, +bool assetload::shader(Assets* assets, const ResPaths* paths, const std::string filename, const std::string name) { fs::path vertexFile = paths->find(filename+".glslv"); fs::path fragmentFile = paths->find(filename+".glslf"); - - std::string vertexSource = files::read_string(vertexFile); - std::string fragmentSource = files::read_string(fragmentFile); + + std::string vertexSource = files::read_string(vertexFile); + std::string fragmentSource = files::read_string(fragmentFile); Shader* shader = Shader::loadShader( - vertexFile.string(), + vertexFile.string(), fragmentFile.string(), vertexSource, fragmentSource); - + if (shader == nullptr) { std::cerr << "failed to load shader '" << name << "'" << std::endl; return false; @@ -51,30 +52,39 @@ bool assetload::shader(Assets* assets, return true; } +static bool appendAtlas(AtlasBuilder& atlas, const fs::path& file) { + // png is only supported format + if (file.extension() != ".png") + return false; + std::string name = file.stem().string(); + // skip duplicates + if (atlas.has(name)) { + return false; + } + std::unique_ptr image(png::load_image(file.string())); + if (image == nullptr) { + std::cerr << "could not to load " << file.string() << std::endl; + return false; + } + image->fixAlphaColor(); + atlas.add(name, image.release()); + + return true; +} + bool assetload::atlas(Assets* assets, const ResPaths* paths, const std::string directory, const std::string name) { AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { - // png is only supported format - if (file.extension() != ".png") - continue; - std::string name = file.stem().string(); - // skip duplicates - if (builder.has(name)) { - continue; - } - std::unique_ptr image (png::load_image(file.string())); - if (image == nullptr) { - std::cerr << "could not to load " << file.string() << std::endl; - continue; - } - image->fixAlphaColor(); - builder.add(name, image.release()); + if (!appendAtlas(builder, file)) continue; } Atlas* atlas = builder.build(2); assets->store(atlas, name); + for (const auto& file : builder.getNames()) { + assetload::animation(assets, paths, "textures", file, atlas); + } return true; } @@ -99,3 +109,97 @@ bool assetload::font(Assets* assets, assets->store(font, name); return true; } + +bool assetload::animation(Assets* assets, + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas) { + std::string animsDir = directory + "/animations"; + std::string blocksDir = directory + "/blocks"; + + for (const auto& folder : paths->listdir(animsDir)) { + if (!fs::is_directory(folder)) continue; + if (folder.filename().string() != name) continue; + if (fs::is_empty(folder)) continue; + + AtlasBuilder builder; + appendAtlas(builder, paths->find(blocksDir + "/" + name + ".png")); + + std::string animFile = folder.string() + "/animation.json"; + + std::vector> frameList; + + if (fs::exists(animFile)) { + auto root = files::read_json(animFile); + + auto frameArr = root->list("frames"); + + Frame temp; + float frameDuration = DEFAULT_FRAME_DURATION; + std::string frameName; + + if (frameArr) { + for (size_t i = 0; i < frameArr->size(); i++) { + auto currentFrame = frameArr->list(i); + + frameName = currentFrame->str(0); + if (currentFrame->size() > 1) frameDuration = static_cast(currentFrame->integer(1)) / 1000; + + frameList.emplace_back(frameName, frameDuration); + } + } + } + for (const auto& file : paths->listdir(animsDir + "/" + name)) { + if (!frameList.empty()) { + bool contains = false; + for (const auto& elem : frameList) { + if (file.stem() == elem.first) { + contains = true; + break; + } + } + if (!contains) continue; + } + if (!appendAtlas(builder, file)) continue; + } + + Atlas* srcAtlas = builder.build(2); + + Texture* srcTex = srcAtlas->getTexture(); + Texture* dstTex = dstAtlas->getTexture(); + + TextureAnimation animation(srcTex, dstTex); + Frame frame; + UVRegion region = dstAtlas->get(name); + + frame.dstPos = glm::ivec2(region.u1 * dstTex->width, region.v1 * dstTex->height); + frame.size = glm::ivec2(region.u2 * dstTex->width, region.v2 * dstTex->height) - frame.dstPos; + + if (frameList.empty()) { + for (const auto& elem : builder.getNames()) { + region = srcAtlas->get(elem); + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } + else { + for (const auto& elem : frameList) { + if (!srcAtlas->has(elem.first)) { + std::cerr << "Unknown frame name: " << elem.first << std::endl; + continue; + } + region = srcAtlas->get(elem.first); + frame.duration = elem.second; + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } + + assets->store(srcAtlas, name + "_animation"); + assets->store(animation); + + return true; + } + return true; +} diff --git a/src/assets/assetload_funcs.h b/src/assets/assetload_funcs.h index bcfec06e..67da528c 100644 --- a/src/assets/assetload_funcs.h +++ b/src/assets/assetload_funcs.h @@ -5,6 +5,7 @@ class ResPaths; class Assets; +class Atlas; namespace assetload { bool texture(Assets* assets, @@ -23,6 +24,11 @@ namespace assetload { const ResPaths* paths, const std::string filename, const std::string name); + bool animation(Assets* assets, + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas); } #endif // ASSETS_ASSET_LOADERS_H_ \ No newline at end of file diff --git a/src/frontend/screens.cpp b/src/frontend/screens.cpp index 2a93f9e7..374ef28d 100644 --- a/src/frontend/screens.cpp +++ b/src/frontend/screens.cpp @@ -14,6 +14,7 @@ #include "../graphics/Shader.h" #include "../graphics/Batch2D.h" #include "../graphics/GfxContext.h" +#include "../graphics/TextureAnimation.h" #include "../assets/Assets.h" #include "../world/Level.h" #include "../world/World.h" @@ -92,6 +93,9 @@ LevelScreen::LevelScreen(Engine* engine, Level* level) auto& settings = engine->getSettings(); backlight = settings.graphics.backlight; + + animator.reset(new TextureAnimator()); + animator->addAnimations(engine->getAssets()->getAnimations()); } LevelScreen::~LevelScreen() { @@ -136,6 +140,7 @@ void LevelScreen::update(float delta) { if (!hud->isPause()) { level->world->updateTimers(delta); + animator->update(delta); } controller->update(delta, !inputLocked, hud->isPause()); hud->update(hudVisible); diff --git a/src/frontend/screens.h b/src/frontend/screens.h index 27862d68..0cd0a297 100644 --- a/src/frontend/screens.h +++ b/src/frontend/screens.h @@ -13,6 +13,7 @@ class Camera; class Batch2D; class LevelFrontend; class LevelController; +class TextureAnimator; /* Screen is a mainloop state */ class Screen { @@ -42,7 +43,8 @@ class LevelScreen : public Screen { std::unique_ptr hud; std::unique_ptr worldRenderer; std::unique_ptr controller; - + std::unique_ptr animator; + bool hudVisible = true; void updateHotkeys(); public: diff --git a/src/graphics/Atlas.h b/src/graphics/Atlas.h index 110caa79..fb6ec84e 100644 --- a/src/graphics/Atlas.h +++ b/src/graphics/Atlas.h @@ -40,6 +40,7 @@ public: AtlasBuilder() {} void add(std::string name, ImageData* image); bool has(std::string name) const; + const std::set& getNames() { return names; }; Atlas* build(uint extrusion, uint maxResolution=8192); }; diff --git a/src/graphics/TextureAnimation.cpp b/src/graphics/TextureAnimation.cpp new file mode 100644 index 00000000..266d7559 --- /dev/null +++ b/src/graphics/TextureAnimation.cpp @@ -0,0 +1,52 @@ +#include "TextureAnimation.h" +#include "Texture.h" +#include "Framebuffer.h" + +#include +#include + +TextureAnimator::TextureAnimator() : + frameBuffer(new Framebuffer(1u, 1u)) +{ +} + +TextureAnimator::~TextureAnimator() { + delete frameBuffer; +} + +void TextureAnimator::addAnimations(const std::vector& animations) { + for (const auto& elem : animations) { + addAnimation(elem); + } +} + +void TextureAnimator::update(float delta) { + std::unordered_set changedTextures; + + frameBuffer->bind(); + for (auto& elem : animations) { + if (changedTextures.find(elem.dstTexture->id) == changedTextures.end()) changedTextures.insert(elem.dstTexture->id); + elem.timer -= delta; + Frame& frame = elem.frames[elem.currentFrame]; + if (elem.timer <= 0) { + elem.timer = frame.duration; + elem.currentFrame++; + if (elem.currentFrame >= elem.frames.size()) elem.currentFrame = 0; + + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, elem.srcTexture->id, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, elem.dstTexture->id, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT1); + + float srcPosY = elem.srcTexture->height - frame.size.y - frame.srcPos.y; // vertical flip + + glBlitFramebuffer(frame.srcPos.x, srcPosY, frame.srcPos.x + frame.size.x, srcPosY + frame.size.y, + frame.dstPos.x, frame.dstPos.y, frame.dstPos.x + frame.size.x, frame.dstPos.y + frame.size.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + for (auto& elem : changedTextures) { + glBindTexture(GL_TEXTURE_2D, elem); + glGenerateMipmap(GL_TEXTURE_2D); + } + glBindTexture(GL_TEXTURE_2D, 0); +} diff --git a/src/graphics/TextureAnimation.h b/src/graphics/TextureAnimation.h new file mode 100644 index 00000000..50f82bb1 --- /dev/null +++ b/src/graphics/TextureAnimation.h @@ -0,0 +1,52 @@ +#ifndef TEXTURE_ANIMATION_H +#define TEXTURE_ANIMATION_H + +#include "../typedefs.h" + +#include +#include + +class Assets; +class Texture; +class Framebuffer; + +constexpr float DEFAULT_FRAME_DURATION = 0.150f; + +struct Frame { + glm::ivec2 srcPos; + glm::ivec2 dstPos; + glm::ivec2 size; + float duration = DEFAULT_FRAME_DURATION; +}; + +class TextureAnimation { +public: + TextureAnimation(Texture* srcTex, Texture* dstTex) : srcTexture(srcTex), dstTexture(dstTex) {}; + ~TextureAnimation() {}; + + void addFrame(const Frame& frame) { frames.emplace_back(frame); }; + + size_t currentFrame = 0; + float timer = 0.f; + Texture* srcTexture; + Texture* dstTexture; + std::vector frames; +}; + +class TextureAnimator { +public: + TextureAnimator(); + ~TextureAnimator(); + + void addAnimation(const TextureAnimation& animation) { animations.emplace_back(animation); }; + void addAnimations(const std::vector& animations); + + void update(float delta); +private: + + Framebuffer* frameBuffer; + + std::vector animations; +}; + +#endif // !TEXTURE_ANIMATION_H