CoriEngine
Loading...
Searching...
No Matches
AnimationPack.cpp
Go to the documentation of this file.
1#include "AnimationPack.hpp"
3#include "Graphics/Image.hpp"
5#include <nlohmann/json.hpp>
8
9using json = nlohmann::json;
10
11namespace {
12 int32_t ExtractFrameNumber(const std::string& key) {
13 const size_t startPos = key.find(' ');
14 const size_t endPos = key.find('.');
15 if (startPos != std::string::npos && endPos != std::string::npos && endPos > startPos) {
16 return std::stoi(key.substr(startPos + 1, endPos - (startPos + 1)));
17 }
18 return -1;
19 }
20}
21
22namespace Cori {
23 namespace Graphics {
24 std::shared_ptr<AnimationPack> AnimationPack::Create(const std::filesystem::path& jsonPath, ConfigType type, const std::string& name) {
25 std::ifstream f(jsonPath);
27
28 if (type == ASEPRITE) {
29 try {
31 if (!f.good()) {
32 throw Core::CoriError(std::format("Failed to open json file {}", jsonPath.string()));
33 }
34
35 json data = json::parse(f);
36
37 const json& framesObject = data["frames"];
38 const glm::u16vec2 frameResolution = {framesObject.items().begin().value()["frame"]["w"], framesObject.items().begin().value()["frame"]["h"]};
39
40 const std::filesystem::path atlasPath = jsonPath.parent_path() / data["meta"]["image"];
41 auto image = Image::Create(atlasPath);
42 if (!image->GetSuccessStatus()) {
43 throw Core::CoriError(std::format("Failed to load image: {}", atlasPath.string()));
44 }
45
46 const glm::uvec2 initialImageResolution = { image->GetWidth(), image->GetHeight() };
47
48 auto atlas = SpriteAtlas::Create(data["meta"]["image"], image, frameResolution);
49 if (!atlas->GetSuccessStatus()) {
50 throw Core::CoriError(std::format("Failed to load Sprite Atlas from: {}", atlasPath.string()));
51 }
52
53 std::vector<std::pair<uint32_t, json>> sortedFrameItems;
54
55 for (const auto& [key, frameData] : framesObject.items()) {
56 int32_t frameNum = ExtractFrameNumber(key);
57 if (frameNum != -1) {
58 sortedFrameItems.emplace_back( frameNum, frameData );
59 }
60 else {
62 }
63 }
64
65 std::ranges::sort(sortedFrameItems, [](const auto& a, const auto& b) {
66 return a.first < b.first;
67 });
68
69 std::vector<Internal::AnimationFrame> frames;
70 std::vector<Internal::AnimationData> animations;
71 animations.reserve(initialImageResolution.y / frameResolution.y);
72
73 glm::uvec2 oldPos;
74 glm::uvec2 pos{ 0.0f, 0.0f };
75
76 for (const auto& [frameIndex, frameData] : sortedFrameItems) {
77 glm::u16vec2 currentFrameResolution = { frameData["frame"]["w"], frameData["frame"]["h"] };
78 if (frameResolution != currentFrameResolution) {
79 throw Core::CoriError(std::format("All animation frames should have the same resolution. Mismatch between frame '0' resolution: ({}, {}), and frame '{}' resolution: ({}, {})", frameResolution.x, frameResolution.y, frameIndex, currentFrameResolution.x, currentFrameResolution.y));
80 }
81
82 oldPos = pos;
83 pos = { frameData["frame"]["x"], frameData["frame"]["y"] };
84 if (pos.y - oldPos.y == frameResolution.y) {
85 Internal::AnimationData anim(frameResolution, frames);
86 animations.push_back(anim);
87 frames.clear();
88 }
89
90 uint32_t col = pos.x / frameResolution.x;
91 uint32_t row = pos.y / frameResolution.y;
92
94
95 frame.m_UVs = atlas->GetSpriteUVsAtPosition({col, row});
96 frame.m_TickDuration = std::round(static_cast<float>(frameData["duration"]) / (timeStep * 1000.0f));
97
98 frames.push_back(frame);
99
100 if (frameIndex + 1 == sortedFrameItems.size()) {
101 Internal::AnimationData anim(frameResolution, frames);
102 animations.push_back(anim);
103 }
104 }
105
106 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Loaded Animation Pack '{}' from ASEPRITE config: {}", name, jsonPath.string());
107 return std::shared_ptr<AnimationPack>(new AnimationPack(animations, atlas, name, type));
108 }
109 catch (std::exception& e) {
110 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Failed to parse json file: {1}, and create the Animation Pack '{3}'. \n{0}Error: {2}. \n{0}Created a placeholder Animation Pack.", CORI_SECOND_LINE_SPACING, jsonPath.string(), e.what(), name);
111 return std::shared_ptr<AnimationPack>(new AnimationPack());
112 }
113 }
114 if (type == CORI_UNIFORM) {
115 try {
117 if (!f.good()) {
118 throw Core::CoriError(std::format("Failed to open json file {}", jsonPath.string()));
119 }
120
121 json data = json::parse(f);
122
123 const json& meta = data["meta"];
124 const glm::u16vec2 frameResolution = {meta["frameSizeX"], meta["frameSizeY"]};
125 const std::string& textureFile = meta["textureFile"];
126 const std::filesystem::path atlasPath = jsonPath.parent_path() / textureFile;
127
128 auto image = Image::Create(atlasPath);
129 if (!image->GetSuccessStatus()) {
130 throw Core::CoriError(std::format("Failed to load image: {}", atlasPath.string()));
131 }
132
133 auto atlas = SpriteAtlas::Create(textureFile, image, frameResolution);
134 if (!atlas->GetSuccessStatus()) {
135 throw Core::CoriError(std::format("Failed to load Sprite Atlas from: {}", atlasPath.string()));
136 }
137
138 std::vector<Internal::AnimationData> animations;
139 const json& animationsArray = data["animations"];
140
141 for (const auto& animJson : animationsArray) {
142 std::vector<Internal::AnimationFrame> frames;
143 const json& framesArray = animJson["frames"];
144
145 for (const auto& frameJson : framesArray) {
146 const glm::uvec2 pos = {frameJson["x"], frameJson["y"]};
147
148 const uint32_t col = pos.x / frameResolution.x;
149 const uint32_t row = pos.y / frameResolution.y;
150
152 frame.m_UVs = atlas->GetSpriteUVsAtPosition({col, row});
153 frame.m_TickDuration = std::round(static_cast<float>(frameJson["ms"]) / (timeStep * 1000.0f));
154 frames.push_back(frame);
155 }
156 animations.emplace_back(frameResolution, frames);
157 }
158
159 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Loaded Animation Pack '{}' from CORI config: {}", name, jsonPath.string());
160 return std::shared_ptr<AnimationPack>(new AnimationPack(animations, atlas, name, type));
161 }
162 catch (std::exception& e) {
163 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Failed to parse json file: {1}, and create the Animation Pack '{3}'. \n{0}Error: {2}. \n{0}Created a placeholder Animation Pack.", CORI_SECOND_LINE_SPACING, jsonPath.string(), e.what(), name);
164 return std::shared_ptr<AnimationPack>(new AnimationPack());
165 }
166 }
167
168 if (type == CORI_VARYING) {
169 try {
171 if (!f.good()) {
172 throw Core::CoriError(std::format("Failed to open json file {}", jsonPath.string()));
173 }
174
175 json data = json::parse(f);
176
177 const json& meta = data["meta"];
178 const std::string& textureFile = meta["textureFile"];
179 const std::filesystem::path atlasPath = jsonPath.parent_path() / textureFile;
180
181 auto image = Image::Create(atlasPath);
182 if (!image->GetSuccessStatus()) {
183 throw Core::CoriError(std::format("Failed to load image: {}", atlasPath.string()));
184 }
185
186 auto atlas = Texture2D::Create(image);
187 glm::vec2 atlasSize = { atlas->GetWidth(), atlas->GetHeight() };
188
189 std::vector<Internal::AnimationData> animations;
190 const json& animationsArray = data["animations"];
191
192 for (const auto& animJson : animationsArray) {
193 std::vector<Internal::AnimationFrame> frames;
194 const json& framesArray = animJson["frames"];
195 const glm::u16vec2 frameResolution = {animJson["frameSizeX"], animJson["frameSizeY"]};
196
197 for (const auto& frameJson : framesArray) {
198 const glm::vec2 pos = {frameJson["x"], frameJson["y"]};
199
201 const glm::vec2 UVmin = pos / atlasSize;
202 const glm::vec2 UVmax = (pos + glm::vec2(frameResolution)) / atlasSize;
203 frame.m_UVs = { UVmin, UVmax };
204 frame.m_TickDuration = std::round(static_cast<float>(frameJson["ms"]) / (timeStep * 1000.0f));
205 frames.push_back(frame);
206 }
207 animations.emplace_back(frameResolution, frames);
208 }
209
210 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Loaded Animation Pack '{}' from CORI config: {}", name, jsonPath.string());
211 return std::shared_ptr<AnimationPack>(new AnimationPack(animations, atlas, name, type));
212 }
213 catch (std::exception& e) {
214 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Failed to parse json file: {1}, and create the Animation Pack '{3}'. \n{0}Error: {2}. \n{0}Created a placeholder Animation Pack.", CORI_SECOND_LINE_SPACING, jsonPath.string(), e.what(), name);
215 return std::shared_ptr<AnimationPack>(new AnimationPack());
216 }
217 }
218
219 return std::shared_ptr<AnimationPack>(new AnimationPack());
220 }
221
222 std::shared_ptr<AnimationPack> AnimationPack::Create(const Descriptor& descriptor) {
223 return Create(descriptor.m_JsonPath, descriptor.m_ConfigType, descriptor.m_Name);
224 }
225
227 if (m_Valid) {
228 if (CORI_CORE_CHECK(index + 1 <= m_Animations.size(), "Animation Pack '{}' doesn't have an animation at index '{}', returning animation at index '0'. Total animation cound '{}'", m_Name, index, m_Animations.size())) { return Animation{ shared_from_this(), 0 }; }
229 return Animation{ shared_from_this(), index };
230 }
231
232 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::AnimationPack }, "Animation Pack '{}', was created as a placeholder. Requested animation at '{}' doesn't exist, returning a placeholder.", m_Name, index);
233 std::vector<Internal::AnimationFrame> frames;
234 constexpr Internal::AnimationFrame frame { UVs{}, 2 };
235 frames.push_back(frame);
236 const Internal::AnimationData data({ 8, 8 }, frames);
237 return Animation{ shared_from_this(), 0 };
238
239 }
240
241 AnimationPack::AnimationPack(std::vector<Internal::AnimationData> animations, const std::shared_ptr<SpriteAtlas>& spriteAtlas, std::string name, const ConfigType type) : m_Animations(std::move(animations)), m_TextureOrAtlas(spriteAtlas), m_Name(std::move(name)), m_Type(type) {
242 m_Valid = true;
243 }
244
245 AnimationPack::AnimationPack(std::vector<Internal::AnimationData> animations, const std::shared_ptr<Texture2D>& spriteAtlas, std::string name, const ConfigType type) : m_Animations(std::move(animations)), m_TextureOrAtlas(spriteAtlas), m_Name(std::move(name)), m_Type(type) {
246 m_Valid = true;
247 }
248
249 AnimationPack::AnimationPack() {
250 m_Valid = false;
251 std::vector<Internal::AnimationFrame> frames;
252 constexpr Internal::AnimationFrame frame{ UVs{}, 2 };
253 frames.push_back(frame);
254 const Internal::AnimationData data({ 8, 8 }, frames);
255 m_Animations.clear();
256 m_Animations.push_back(data);
257 m_TextureOrAtlas = SpriteAtlas::Create("placeholder for AnimationPack", Image::Create(FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / "placeholders/missing_texture32.png"), glm::u16vec2(32));
258 m_Type = INVALID;
259 }
260 }
261}
262
const std::string CORI_SECOND_LINE_SPACING
Definition Logger.hpp:1001
#define CORI_CORE_CHECK(x,...)
Definition Logger.hpp:1042
#define CORI_CORE_ERROR_TAGGED(...)
Definition Logger.hpp:1039
#define CORI_CORE_WARN_TAGGED(...)
Definition Logger.hpp:1038
#define CORI_CORE_INFO_TAGGED(...)
Definition Logger.hpp:1027
nlohmann::json json
static GameTimer & GetGameTimer()
Getter for the GameTimer.
Custom error class mainly used in std::expected.
Definition Error.hpp:27
float GetTimestep() const
Returns the current timeStep, scale is in seconds.
Definition Time.hpp:31
static std::filesystem::path GetAliasedPath(const std::string &alias)
Retries the full aliased path defined in fsgame.json.
std::variant< std::shared_ptr< SpriteAtlas >, std::shared_ptr< Texture2D > > m_TextureOrAtlas
Animation GetAnimation(const uint32_t index)
ConfigType
Available animation pack config types.
std::vector< Internal::AnimationData > m_Animations
static std::shared_ptr< AnimationPack > Create(const std::filesystem::path &jsonPath, ConfigType type, const std::string &name)
Creates an AnimationPack.
static std::shared_ptr< Image > Create(const std::filesystem::path &path)
Creates an Image from the picture at the specified path.
Definition Image.cpp:189
static std::shared_ptr< SpriteAtlas > Create(std::string name, const std::shared_ptr< Image > &image, const glm::u16vec2 spriteResolution)
Creates a SpriteAtlas.
static std::shared_ptr< Texture2D > Create(const std::shared_ptr< Image > &image)
Creates a Texture2D from the Image.
Definition Texture.cpp:7
Almost everything connected to graphics is in this namespace.
Definition Window.hpp:7
Global engine namespace.
static constexpr char Self[]
Definition Logger.hpp:47
static constexpr char AnimationPack[]
Definition Logger.hpp:71
static constexpr char Self[]
Definition Logger.hpp:127
static constexpr char QuadAnimator[]
Definition Logger.hpp:132
static constexpr char Self[]
Definition Logger.hpp:119