aboutsummaryrefslogtreecommitdiff
path: root/source/Game/Sprite.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Game/Sprite.cpp')
-rw-r--r--source/Game/Sprite.cpp328
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
+}