#include "Sprite.hpp" #include "AppConfig.hpp" #include "CommonVertexIndex.hpp" #include "EditorCore.hpp" #include "EditorUtils.hpp" #include "Image.hpp" #include "RapidJsonHelper.hpp" #include "Rect.hpp" #include #include #include #include using namespace std::literals; bool SpriteDefinition::IsValid() const { return mAtlas != nullptr; } bool IresSpriteFiles::IsValid() const { return !spriteFiles.empty(); } SpriteDefinition* IresSpriteFiles::CreateInstance() const { if (IsValid()) { return nullptr; } std::vector sources; sources.resize(spriteFiles.size()); for (auto& file : spriteFiles) { } Texture::AtlasOutput atlasOut; Texture::AtlasInput atlasIn{ .sources = sources, .packingMode = Texture::PM_KeepSquare, }; atlasIn.sources = sources; auto atlas = std::make_unique(); if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { return nullptr; } auto sprite = std::make_unique(); sprite->mAtlas.Attach(atlas.release()); sprite->mBoundingBox = atlasOut.elements[0].subregionSize; sprite->mFrames.reserve(atlasOut.elements.size()); for (auto& elm : atlasOut.elements) { // Validate bounding box if (sprite->mBoundingBox != elm.subregionSize) { return nullptr; } // Copy frame subregion sprite->mFrames.push_back(elm.subregion); } return sprite.release(); } SpriteDefinition* IresSpriteFiles::GetInstance() { if (mInstance == nullptr) { mInstance.Attach(CreateInstance()); } return mInstance.Get(); } void IresSpriteFiles::InvalidateInstance() { mInstance.Attach(nullptr); } void IresSpriteFiles::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { IresObject::Write(ctx, value, root); value.AddMember("Sprites", rapidjson::WriteVectorPrimitives(root, spriteFiles.begin(), spriteFiles.end()), root.GetAllocator()); } void IresSpriteFiles::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { InvalidateInstance(); IresObject::Read(ctx, value); auto rvFileList = rapidjson::GetProperty(value, rapidjson::kArrayType, "Sprites"sv); if (!rvFileList) return; spriteFiles.clear(); rapidjson::ReadVectorPrimitives(*rvFileList, spriteFiles); } bool IresSpritesheet::IsValid() const { return !spritesheetFile.empty() && sheetWSplit != 0 && sheetHSplit != 0; } void IresSpritesheet::ResplitSpritesheet(SpriteDefinition* sprite, const IresSpritesheet* conf) { ResplitSpritesheet(sprite, conf->sheetWSplit, conf->sheetHSplit, conf->frameCountOverride); } void IresSpritesheet::ResplitSpritesheet(SpriteDefinition* sprite, int wSplit, int hSplit, int frameCount) { auto atlas = sprite->GetAtlas(); auto size = atlas->GetInfo().size; int frameWidth = size.x / wSplit; int frameHeight = size.y / hSplit; sprite->mBoundingBox = { frameWidth, frameHeight }; sprite->mFrames.clear(); sprite->mFrames.reserve(wSplit * hSplit); // Width and height in UV coordinates for each frame float deltaU = 1.0f / wSplit; float deltaV = 1.0f / hSplit; int i = 0; if (frameCount < 0) { frameCount = std::numeric_limits::max(); } for (int y = 0; y < hSplit; ++y) { for (int x = 0; x < wSplit; ++x) { auto& subregion = sprite->mFrames.emplace_back(); // Top left subregion.u0 = deltaU * x; subregion.v0 = deltaV * y; // Bottom right subregion.u1 = subregion.u0 + deltaU; subregion.v1 = subregion.v0 + deltaV; if ((i + 1) >= frameCount) { return; } ++i; } } } SpriteDefinition* IresSpritesheet::CreateInstance() const { if (!IsValid()) { return nullptr; } char path[2048]; snprintf(path, sizeof(path), "%s/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str()); auto atlas = std::make_unique(); if (atlas->InitFromFile(path) != Texture::EC_Success) { return nullptr; } auto sprite = std::make_unique(); sprite->mAtlas.Attach(atlas.release()); ResplitSpritesheet(sprite.get(), this); return sprite.release(); } SpriteDefinition* IresSpritesheet::GetInstance() { if (mInstance == nullptr) { mInstance.Attach(CreateInstance()); } return mInstance.Get(); } void IresSpritesheet::InvalidateInstance() { mInstance.Attach(nullptr); } bool IresSpritesheet::IsFrameCountOverriden() const { return frameCountOverride > 0; } int IresSpritesheet::GetFrameCount() const { if (IsFrameCountOverriden()) { return frameCountOverride; } else { return sheetWSplit * sheetHSplit; } } void IresSpritesheet::ShowEditor(IEditor& editor) { IresObject::ShowEditor(editor); bool doInvalidateInstance = false; auto instance = GetInstance(); // NOTE: may be null if (ImGui::Button("View Sprite", instance == nullptr)) { editor.OpenSpriteViewer(instance); } if (ImGui::InputText("Spritesheet", &spritesheetFile)) { doInvalidateInstance = true; } if (ImGui::InputInt("Horizontal split", &sheetWSplit)) { sheetWSplit = std::max(sheetWSplit, 1); if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); } if (ImGui::InputInt("Vertical split", &sheetHSplit)) { sheetHSplit = std::max(sheetHSplit, 1); if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); } bool frameCountOverriden = frameCountOverride > 0; if (ImGui::Checkbox("##", &frameCountOverriden)) { if (frameCountOverriden) { frameCountOverride = sheetWSplit * sheetHSplit; } else { frameCountOverride = 0; } } ImGui::SameLine(); if (frameCountOverriden) { if (ImGui::InputInt("Frame count", &frameCountOverride)) { frameCountOverride = std::max(frameCountOverride, 1); if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); } } else { int dummy = sheetWSplit * sheetHSplit; ImGui::PushDisabled(); ImGui::InputInt("Frame count", &dummy, ImGuiInputTextFlags_ReadOnly); ImGui::PopDisabled(); } if (instance) { auto atlas = instance->GetAtlas(); auto imageSize = Utils::FitImage(atlas->GetInfo().size); auto imagePos = ImGui::GetCursorScreenPos(); ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), imageSize); auto drawlist = ImGui::GetWindowDrawList(); float deltaX = imageSize.x / sheetWSplit; for (int ix = 0; ix < sheetWSplit + 1; ++ix) { float x = ix * deltaX; ImVec2 start{ imagePos.x + x, imagePos.y }; ImVec2 end{ imagePos.x + x, imagePos.y + imageSize.y }; drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); } float deltaY = imageSize.y / sheetHSplit; for (int iy = 0; iy < sheetHSplit + 1; ++iy) { float y = iy * deltaY; ImVec2 start{ imagePos.x, imagePos.y + y }; ImVec2 end{ imagePos.x + imageSize.x, imagePos.y + y }; drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); } int i = sheetWSplit * sheetHSplit; int frameCount = GetFrameCount(); for (int y = sheetHSplit - 1; y >= 0; --y) { for (int x = sheetWSplit - 1; x >= 0; --x) { if (i > frameCount) { ImVec2 tl{ imagePos.x + x * deltaX + 1.0f, imagePos.y + y * deltaY + 1.0f }; ImVec2 br{ imagePos.x + (x + 1) * deltaX, imagePos.y + (y + 1) * deltaY }; drawlist->AddRectFilled(tl, br, IM_COL32(255, 0, 0, 100)); } --i; } } } else { ImGui::TextUnformatted("Sprite configuration invalid"); } if (doInvalidateInstance) { InvalidateInstance(); } } void IresSpritesheet::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { IresObject::Write(ctx, value, root); value.AddMember("SpriteSheet", spritesheetFile, root.GetAllocator()); value.AddMember("WSplit", sheetWSplit, root.GetAllocator()); value.AddMember("HSplit", sheetHSplit, root.GetAllocator()); if (frameCountOverride > 0) { value.AddMember("FrameCount", frameCountOverride, root.GetAllocator()); } } void IresSpritesheet::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { InvalidateInstance(); IresObject::Read(ctx, value); BRUSSEL_JSON_GET(value, "SpriteSheet", std::string, spritesheetFile, return ); BRUSSEL_JSON_GET(value, "WSplit", int, sheetWSplit, return ); BRUSSEL_JSON_GET(value, "HSplit", int, sheetHSplit, return ); BRUSSEL_JSON_GET_DEFAULT(value, "FrameCount", int, frameCountOverride, 0); } Sprite::Sprite() : mDefinition(nullptr) { } bool Sprite::IsValid() const { return mDefinition != nullptr; } void Sprite::SetDefinition(SpriteDefinition* definition) { mDefinition.Attach(definition); mCurrentFrame = 0; } int Sprite::GetFrame() const { return mCurrentFrame; } const Subregion& Sprite::GetFrameSubregion() const { return mDefinition->GetFrames()[mCurrentFrame]; } void Sprite::SetFrame(int frame) { mCurrentFrame = frame; } void Sprite::PlayFrame() { ++mTimeElapsed; if (mTimeElapsed >= mPlaybackSpeed) { mTimeElapsed -= mPlaybackSpeed; int frameCount = mDefinition->GetFrames().size(); int nextFrame = (mCurrentFrame + 1) % frameCount; SetFrame(nextFrame); } } int Sprite::GetPlaybackSpeed() const { return mPlaybackSpeed; } void Sprite::SetPlaybackSpeed(int speed) { // TODO }