diff options
author | rtk0c <[email protected]> | 2022-06-03 23:26:44 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-06-03 23:26:44 -0700 |
commit | 60ccc62f4934e44ad5b905fdbcf458302b8d8a09 (patch) | |
tree | 02ec83cc8387abfd08bd5ee7ea4e8115f1bfb8d0 /source/Game/Sprite.cpp | |
parent | c2ef7737536bf1f8c81fcfae95c0183b21c9753f (diff) |
Changeset: 63 [WIP] Rename directories
Diffstat (limited to 'source/Game/Sprite.cpp')
-rw-r--r-- | source/Game/Sprite.cpp | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/source/Game/Sprite.cpp b/source/Game/Sprite.cpp new file mode 100644 index 0000000..2b4923c --- /dev/null +++ b/source/Game/Sprite.cpp @@ -0,0 +1,328 @@ +#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 <imgui.h> +#include <misc/cpp/imgui_stdlib.h> +#include <rapidjson/document.h> +#include <memory> + +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<Texture::AtlasSource> 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<Texture>(); + if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { + return nullptr; + } + + auto sprite = std::make_unique<SpriteDefinition>(); + 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<decltype(spriteFiles)>(*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<int>::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<Texture>(); + if (atlas->InitFromFile(path) != Texture::EC_Success) { + return nullptr; + } + + auto sprite = std::make_unique<SpriteDefinition>(); + 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 +} |